Skip to content

Your first diagram

We will draw how a photo is uploaded: a browser, an API, object storage, a queue, a worker that makes thumbnails and a database. Each step adds one idea to the same file, and the code blocks mark the lines it adds. The files on this page are real: the diagrams are compiled from them when the site is built.

Create the diagram first:

Terminal window
pnpm docspp new photos

Then replace the contents of diagrams/photos/diagram.yaml as you follow along.

A node is a box. Give it a kind, which picks a colour and a default icon, and an icon when you want a brand logo. The key (browser, api) is its id. You use ids in edges, groups and scenarios.

An edge is an arrow. a -> b: label is the short form. Use the object form when you need more, such as a kind that changes how the line is drawn.

diagrams/photos/diagram.yaml
title: Photo uploads
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API }
store: { kind: storage, icon: aws-s3, title: Storage }
queue: { kind: queue, icon: rabbitmq, title: Events }
worker: { kind: worker, icon: python, title: Thumbnailer }
db: { kind: database, icon: postgresql, title: Metadata }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { direction: DOWN }
HTTP request / responseData accessQueue / broker
Loading diagram…

Nothing here says where a box goes. Hover a box to follow its connections, or click one to open its drawer.

The views: block at the end asks for a top-to-bottom reading direction, which suits a narrow column. Views explains it.

A group is a labelled zone. Add one line to define it and in: on each node that belongs to it.

diagrams/photos/diagram.yaml
title: Photo uploads
groups:
app: { label: Photo service, caption: Kubernetes }
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API, in: app }
store: { kind: storage, icon: aws-s3, title: Storage, in: app }
queue: { kind: queue, icon: rabbitmq, title: Events, in: app }
worker: { kind: worker, icon: python, title: Thumbnailer, in: app }
db: { kind: database, icon: postgresql, title: Metadata, in: app }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { direction: DOWN }
HTTP request / responseData accessQueue / broker
Loading diagram…

Each zone is laid out on its own, so the diagram stays tidy as it grows. Groups can nest: a group with in: is drawn as a dashed frame inside its parent. See groups.

A scenario is a list of steps. Each step flies a packet along your edges.

diagrams/photos/diagram.yaml
title: Photo uploads
groups:
app: { label: Photo service, caption: Kubernetes }
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API, in: app }
store: { kind: storage, icon: aws-s3, title: Storage, in: app }
queue: { kind: queue, icon: rabbitmq, title: Events, in: app }
worker: { kind: worker, icon: python, title: Thumbnailer, in: app }
db: { kind: database, icon: postgresql, title: Metadata, in: app }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { direction: DOWN }
scenarios:
upload:
title: Upload a photo
steps:
- browser -> api: POST /photos
- api -> store: { label: PUT original, kind: lookup }
- api -> queue: { label: photo.uploaded, kind: event }
- api -> browser: 202 Accepted
- queue -> worker: photo.uploaded
- worker -> store: PUT thumbnails
- worker -> db: { label: mark ready, kind: lookup }
Scenario
HTTP request / responseData accessQueue / broker
Loading diagram…
1
browser→Upload APIPOST /photos
2
Upload API→StoragePUT original
3
Upload API→Eventsphoto.uploaded
4
Upload API→browser202 Accepted
5
Events→Thumbnailerphoto.uploaded
6
Thumbnailer→StoragePUT thumbnails
7
Thumbnailer→Metadatamark ready

A few things to notice while it plays:

  • browser -> api follows the edge you declared. api -> browser flies against the edge, so docspp draws it as a response without being told.
  • kind: lookup marks a quick read of a store. kind: event marks something published for others to react to.
  • Click any line in the step list to jump to it. The address bar follows the step, so you can link a colleague to it.

Scenarios covers steps, phases, notes and the other ways to read the same story.

A view shows part of the model. The new processing view keeps four boxes, and a scenario with view: processing plays on it.

diagrams/photos/diagram.yaml
title: Photo uploads
groups:
app: { label: Photo service, caption: Kubernetes }
nodes:
browser: { kind: client, icon: react }
api: { kind: service, icon: nodejs, title: Upload API, in: app }
store: { kind: storage, icon: aws-s3, title: Storage, in: app }
queue: { kind: queue, icon: rabbitmq, title: Events, in: app }
worker: { kind: worker, icon: python, title: Thumbnailer, in: app }
db: { kind: database, icon: postgresql, title: Metadata, in: app }
edges:
- browser -> api: POST /photos
- api -> store: { kind: data, label: PUT }
- api -> db: { kind: data, label: SQL }
- api -> queue: { kind: queue, label: photo.uploaded }
- queue -> worker: { kind: queue }
- worker -> store: { kind: data, label: PUT }
- worker -> db: { kind: data }
views:
overview: { title: Everything, direction: DOWN }
processing:
title: Processing only
include: [queue, worker, store, db]
scenarios:
upload:
title: Upload a photo
steps:
- browser -> api: POST /photos
- api -> store: { label: PUT original, kind: lookup }
- api -> queue: { label: photo.uploaded, kind: event }
- api -> browser: 202 Accepted
- queue -> worker: photo.uploaded
- worker -> store: PUT thumbnails
- worker -> db: { label: mark ready, kind: lookup }
thumbnails:
title: Make thumbnails
view: processing
steps:
- queue -> worker: photo.uploaded
- worker -> store: { label: GET original, kind: lookup }
- worker -> store: PUT 3 sizes
- worker -> db: { label: mark ready, kind: lookup }
Scenario
Queue / brokerData access
Loading diagram…
1
Events→Thumbnailerphoto.uploaded
2
Thumbnailer→StorageGET original
3
Thumbnailer→StoragePUT 3 sizes
4
Thumbnailer→Metadatamark ready

Use the tabs above the diagram to switch back to the full picture. Nothing was drawn twice: both views come from the same six nodes.

Create diagrams/photos/nodes/api.md:

The only service that talks to the browser.
| Route | What it does |
|---|---|
| `POST /photos` | Stores the original and queues thumbnailing. |
It never resizes images itself. See the Thumbnailer.

Click Upload API in the diagram and the drawer shows this, followed by the box’s connections. Node docs lists the other ways to document a node.

Terminal window
pnpm check

Then add it to a page in src/content/docs/:

import Diagram from '@packagelab/docspp-astro/Diagram.astro'
<Diagram name="photos" scenario="upload" />
  • Add status: planned to a node that does not exist yet. It is drawn dashed, and readers get a Hide planned toggle.
  • Write the failure as its own scenario, such as an upload that is too large.
  • Read the format reference for everything a node, group, edge and view accepts.