DEV Community

Cover image for Stop drawing ERD, but just auto-generate by `prisma-markdown`
Jeongho Nam
Jeongho Nam

Posted on

Stop drawing ERD, but just auto-generate by `prisma-markdown`

Preface

Automatic markdown documents generator for Prisma.

prisma-markdown-example

Example markdown document generated by prisma-markdown

Until last year, I always drew ERDs by hand when developing applications.

I have been drawing ERDs by hand for nearly 20 years, but whenever there was a change in the DB while developing the application, it was always difficult for me to reflect it in the ERD, including documentation and sharing with team members.

It was possible to use automatic ERD generation tools, but these automatic ERD generation tools could not handle documentation. Also, they could not draw proper diagrams when the number of tables increased to hundreds, either.

Therefore, my 20-year development history has always been stained with the pain of drawing ERDs by hand and duplication and missync with actual application code.

To escape from this handwritten ERD hell, I've created a new library called prisma-markdown. It can automatically generate ERD while covering documentation. Also, by utilizing the pagination concept, prisma-markdown can draw diagrams properly even if the number of tables exceeds hundreds/thousands.

Let's see how prisma-markdown made me happy.

Setup

At first, install NPM package.

npm i -D prisma-markdown
Enter fullscreen mode Exit fullscreen mode

At next, add the generator to the schema file, and write documentation comments on models and columns with /// symbols.

generator markdown {
  provider = "prisma-markdown"
  title    = "Shopping Mall"
  output   = "./ERD.md"
}

/// Describe table.
///
/// @namespace Actors
model some_users {
    /// Describe column.
    id String @id @db.Uuid
}
Enter fullscreen mode Exit fullscreen mode

At last, run below command, than ERD.md file would be generated.

npx prisma generate
Enter fullscreen mode Exit fullscreen mode

Comment Tags

If your database has over hundreds of models, none of automatic ERD generators can express them perfect. In that case, prisma-markdown recommends you to separate hundreds of models to multiple paginated diagrams by using /// @namepsace <name> comments.

When you write /// @namepsace <name> comment on models, they would be separated to proper sections of markdown document. For reference, you can assign multiple @namepsaces to a model, and if you do not assign any @namepsace to a model, it would be assigned to default tag.

Also, if you use @erd <name> instead of @namespace <name>, target model would be expressed only at ERD. It would not be appeared to the markdown content section. Otherwise, @describe <name> tag will show the model only at markdown content section, not at ERD.

  • @namespace <name>: Both ERD and markdown content
  • @erd <name>: Only ERD
  • @describe <name>: Only markdown content
  • @hidden: Neither ERD nor markdown content
/// Both description and ERD on Actors chatper.
///
/// Also, only ERD on Articles and Orders chapters.
///
/// @namespace Actors
/// @erd Articles
/// @erd Orders
model shopping_customers {}

/// Only description on Actors chapter.
///
/// @describe Actors
model shopping_customer_login_histories {}

/// Only ERD on Articles chapter.
///
/// @erd Articles
model shopping_sale_reviews {}

/// Never be shown.
///
/// @hidden
model shopping_sale_hits {}
Enter fullscreen mode Exit fullscreen mode

Demonstration

To show how prisma-markdown works, I desinged a shopping mall database, well-known to everyone. When defining a model scheme, write description comment with /// symbol like below. After that, run npx prisma generate command, then markdown document with ERD diagrams would be automatically generated.

Prisma Schema File

generator markdown {
  provider = "prisma-markdown"
  title    = "Shopping Mall"
  output   = "./ERD.md"
}

/// Product composition information handled in the sale snapshot.
/// 
/// `shopping_sale_snapshot_units` is an entity that embodies the 
/// "individual product" information handled in the 
/// {@link shopping_sale_snapshots sale snapshot}.
/// 
/// For reference, the reason why `shopping_sale_snapshot_units` is separated 
/// from {@link shopping_sale_snapshots} by an algebraic relationship of 
/// 1: N is because there are often cases where multiple products are sold 
/// in one listing. This is the case with so-called "bundled products".
/// 
/// - Bundle from regular product (laptop set)
///   - main body
///   - keyboard
///   - mouse
///   - Apple Care (Free A/S Voucher)
/// 
/// And again, `shopping_sale_snapshot_units` does not in itself refer to 
/// the final {@link shopping_sale_snapshot_unit_stocks stock} that the 
/// customer will purchase. 
/// The {@link shopping_sale_snapshot_unit_stocks final stock} can be 
/// found only after selecting all given 
/// {@link shopping_sale_snapshot_unit_options options} and their 
/// {@link shopping_sale_snapshot_unit_option_candidates candidate} values.
/// 
/// For example, even if you buy a laptop, the 
/// {@link shopping_sale_snapshot_unit_stocks final stocks} are determined 
/// only after selecting all the 
/// {@link shopping_sale_snapshot_unit_options options} (CPU / RAM / SSD), etc.
///
/// @namespace Sales
/// @erd Carts
/// @author Samchon
model shopping_sale_snapshot_units {
    //----
    // COLUMNS
    //----
    /// @format uuid
    id String @id @db.Uuid

    /// Belonged snapshot's {@link shopping_sale_snapshots.id}
    ///
    /// @format uuid
    shopping_sale_snapshot_id String @db.Uuid

    /// Representative name of the unit.
    name String @db.VarChar

    /// Whether the unit is primary or not.
    ///
    /// Just a labeling value.
    primary Boolean @db.Boolean

    /// Whether the unit is required or not.
    ///
    /// When the unit is required, the customer must select the unit. If do
    /// not select, customer can't buy it.
    required Boolean @db.Boolean

    /// Sequence order in belonged snapshot.
    sequence Int @db.Integer

    //----
    // RELATIONS
    //----
    /// Belonged snapshot.
    snapshot shopping_sale_snapshots @relation(fields: [shopping_sale_snapshot_id], references: [id], onDelete: Cascade)

    /// List of options.
    options shopping_sale_snapshot_unit_options[]

    /// List of stocks.
    stocks                    shopping_sale_snapshot_unit_stocks[]

    /// List of stocks contained in cart item
    cart_item_stocks shopping_cart_item_stocks[]
}
Enter fullscreen mode Exit fullscreen mode

Generated Markdown Document

shopping_sale_snapshot_units

Product composition information handled in the sale snapshot.

shopping_sale_snapshot_units is an entity that embodies the "individual product" information handled in the sale snapshot.

For reference, the reason why shopping_sale_snapshot_units is separated from shopping_sale_snapshots by an algebraic relationship of 1: N is because there are often cases where multiple products are sold in one listing. This is the case with so-called "bundled products".

  • Bundle from regular product (laptop set)
  • main body
  • keyboard
  • mouse
  • Apple Care (Free A/S Voucher)

And again, shopping_sale_snapshot_units does not in itself refer to the final stock that the customer will purchase. The final stock can be found only after selecting all given options and their candidate values.

For example, even if you buy a laptop, the final stocks are determined only after selecting all the options (CPU / RAM / SSD), etc.

Properties

  • id:
  • shopping_sale_snapshot_id: Belonged snapshot's shopping_sale_snapshots.id
  • name: Representative name of the unit.
  • primary > Whether the unit is primary or not. > > Just a labeling value.
  • required > Whether the unit is required or not. > > When the unit is required, the customer must select the unit. If do > not select, customer can't buy it.
  • sequence: Sequence order in belonged snapshot.

Top comments (1)

Collapse
 
glen profile image
치와와

he is korea best of best developer.