D2 Basics Tutorial

D2 tutorial by Mayank Kapri

What is D2? Is it that robot from star wars?

No! D2 is a diagram scripting language that turns text to diagrams. It stands for Declarative Diagramming. Declarative, as in, you describe what you want diagrammed, it generates an image (SVG)

For example, you provide this input on the right, and you get back the output on the left.

Getting Started

To install D2 on you local machine you can use a script or an installer

  • Use script to install D2
# With --dry-run the install script will print the commands it will use
# to install without actually installing so you know what it's going to do.
curl -fsSL https://d2lang.com/install.sh | sh -s -- --dry-run
# If things look good, install for real.
curl -fsSL https://d2lang.com/install.sh | sh -s --
  • Install from source using go

      go install oss.terrastruct.com/d2
    
  • For windows installation you can check out this link https://github.com/terrastruct/d2/releases for releases and installers

  • To check if D2 is installed correctly run command d2 in your terminal you should see something like this

Hello World D2

Let's start with the basics of D2 i.e a hello world example

  1. Create a file named hello.d2 in any editor of your choice and paste this text in that file
x -> y: hello world
  1. Navigate to that file in your terminal and run
d2 -w hello.d2 out.svg

will start a web server and serve an SVG file like this

Shapes

What are shapes? basically in every diagram that we need we need some shapes to be linked. For example in the hello world example we needed 2 boxes "x" and "y" to be linked to each other, these boxes are called as shapes in D2

Naming of shapes can be pretty simple below are a few examples of shapes

imAShape
im_a_shape
im a shape
i'm a shape
# notice that one-hyphen is not a connection
# whereas, `a--shape` would be a connection
a-shape

Avoid using double hyphen "--" while naming as that will create a connection instead.

You can also use semicolons to define multiple shapes on the same line for example this code

SQLite; Cassandra

Will generate

By default, a shape's label is the same as the shape's key. But if you want it to be different, assign a new label like

pg: PostgreSQL

will generate a shape pg with label "PostgreSQL"

By default, a shape's type is rectangle. To specify otherwise, provide the field shape

pg: PostgreSQL
pg.shape: cloud

Will generate a cloud shape instead

Here i have listed all the available shapes and how they look in the below image

pg1: cloud
pg1.shape: cloud

pg2: rectangle
pg2.shape: rectangle

pg3: square
pg3.shape: square

pg4: page
pg4.shape: page

pg5: parallelogram
pg5.shape: parallelogram

pg6: document
pg6.shape: document

pg7: cylinder
pg7:shape: cylinder

pg8: queue
pg8.shape: queue

pg9: package
pg9.shape: package

pg10: step
pg10.shape: step

pg11: callout
pg11.shape: callout

pg12: stored_data
pg12.shape: stored_data

pg13: person
pg13.shape: person

pg14: diamond
pg14.shape: diamond

pg15: oval
pg15.shape: oval

pg17: circle
pg17.shape: circle

pg18: hexagon
pg18.shape: hexagon

pg19: cloud
pg19.shape: cloud

pg19: text
pg19.shape: text

pg20: code
pg20.shape: code

pg21: class
pg21.shape: class

pg22: sql_table
pg22.shape: sql_table

pg23: image
pg23.shape: image
pg23.icon: https://icons.terrastruct.com/infra/019-network.svg

pg24: sequence_diagram
pg24.shape: sequence_diagram

Connections

Hyphens/arrows in between shapes define a connection. For example

Write Replica Canada <-> Write Replica Australia

Read Replica <- Master
Write Replica -> Master

Read Replica 1 -- Read Replica 2

There are 4 valid ways to define a connection:

  • -- Creates a connection between shapes

  • -> Creates a uni directional connection example a -> b

  • <- Creates a uni directional connection example b -> a

  • <-> Creates a bi directional connection

Connection labels

Use colon to give a label to a connection for example

Read Replica 1 -- Read Replica 2: Kept in sync

Connections must reference a shape's key, not its label.

#shape be with label Backend
be: Backend 

#shape fe with label Frontend
fe: Frontend 

# This would create new shapes
Backend -> Frontend

# This would define a connection over existing shapes
be -> fe

Repeated connections

Repeated connections do not override existing connections. They declare new ones.

# Connection from Database to S3 with label "backup"
Database -> S3: backup 
# Connection from Database to S3
Database -> S3
# Connection from Database to S3 with label "backup"
Database -> S3: backup # Will create another connection

Will generate something like this

Connection chaining

For readability, it may look more natural to define multiple connection in a single line.

# The label applies to each connection in the chain.
Instance1 -> EC2 <- Instance2: Hosted By

Connection Cycles

We can also do chained connection cycles

Stage One -> Stage Two -> Stage Three -> Stage Four
Stage Four -> Stage One: repeat

Arrowheads

To override the default arrowhead shape or give a label next to arrowheads, define a special shape on connections named source-arrowhead and/or target-arrowhead.

# Defined a shape with label
a: The best way to avoid responsibility is to say, "I've got responsibilities"
# Defined b shape with label
b: Whether weary or unweary, O man, do not rest
# Defined c shape with label
c: I still maintain the point that designing a monolithic kernel in 1991 is a

# Defined uni connection between a and b shape with label and custom connection arrowhead
a -> b: To err is human, to moo bovine {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: diamond
  }
}

# Defined bi connection between b and c shape with label and custom connection arrowhead
b <-> c: "Reality is just a crutch for people who can't handle science fiction" {
  source-arrowhead.label: 1
  target-arrowhead: * {
    shape: diamond
    style.filled: true
  }
}

# Defined d shape with label
d: A black cat crossing your path signifies that the animal is going somewhere

d -> a -> c

ARROWHEAD OPTIONS

  • triangle (default)

  • arrow (like triangle but pointier)

  • diamond

    • Can be further styled as style.filled: true.
  • circle

    • Can be further styled as style.filled: true.
  • cf-one, cf-one-required (cf stands for crows foot)

  • cf-many, cf-many-required

Sample Example

a -> b: triangle {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: triangle
  }
}

c -> d: arrow {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: arrow
  }
}

e -> f: circle {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: circle
  }
}
g -> h: cf-one {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: cf-one
  }
}
i -> j: cf-one-required {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: cf-one-required
  }
}
k -> l: cf-many {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: cf-many
  }
}

m -> n: cf-many-required {
  source-arrowhead: 1
  target-arrowhead: * {
    shape: cf-many-required
  }
}

Containers

Containers are shapes that can contain other shapes within them for example

server
# Declares a shape inside of another shape
server.process

# Can declare the container and child in same line
parent.child

# Since connections can also declare keys, this works too
apartment.Bedroom.Bathroom -> office.Spareroom.Bathroom: Portal

The above code will generate output like this

Nested syntax

You can avoid repeating containers by creating nested maps.

clouds: {
  aws: {
    load_balancer -> api
    api -> db
  }
  gcloud: {
    auth -> db
  }

  gcloud -> aws
}

The above code will generate an output like

Container labels

There are two ways define container labels.

1. Shorthand container labels

gcloud: Google Cloud {
...
}

2. Reserved keyword label

gcloud: {
   label: Google Cloud
   ...
}

Example

clouds: {
  aws: AWS {
    load_balancer -> api
    api -> db
  }
  gcloud: Google Cloud {
    auth -> db
  }

  gcloud -> aws
}

users -> clouds.aws.load_balancer
users -> clouds.gcloud.auth

ci.deploys -> clouds

Reference parent

Sometimes you want to reference something outside of the container from within. The underscore (_) refers to parent.

christmas: {
  presents
}
birthdays: {
  presents
  _.christmas.presents -> presents: regift
  _.christmas.style.fill: "#ACE1AF"
}