DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 967,611 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Typed express with generated swagger docs
Jakub Ε vehla
Jakub Ε vehla

Posted on

Typed express with generated swagger docs

Typed Express swagger-docs

TLDR:

I was missing some simple and minimalist libraries that would enhance Express API and solve some common problems with defining interfaces of my backend applications. I wanted to solve static type analysis, API documentation, and runtime validations from a single source of truth.

So I did a proof of concept for my dream library now it's published in the alpha version).
The library enables you to just simply enhance your existing Express application endpoint handler and infer static types, add runtime HTTP validations, and generate documentation in the swagger format.
The library can do all of that from just one reusable single-source-of-truth (SSOT) in your codebase. It's just an Express handler HOF (higher order function aka js decorator for functions). Let's check the example below.

github repository

npm package

Package usage preview

Inferred typescript types from API definition

static type helper preview

Generated Swagger API documentation from API definition

static type helper preview

HTTP 400 bad request runtime validation error from API definition

runtime error previewr

Code definition

import { 
  apiDoc, tNonNullable, tString, tUnion, tList, tObject
} from 'swagger-typed-express-docs'

app.get(
  '/user/:userId',
  /**
   * adding single-source-of-truth metadata for the Express handler and a library to do the
   * - runtime validations checks
   * - compile-time checks
   * - generate swagger documentation
   */
  apiDoc({
    params: {
      userId: tNonNullable(tString),
    },
    query: {
      name: tNonNullable(tString),
      header: tList(tNonNullable(tUnion(['a', 'b', 'c'] as const))),
    },
    body: {
      header: tList(tNonNullable(tUnion(['a', 'b', 'c'] as const))),
      message: tNonNullable(tString),
      footer: tString,
    },
    returns: tObject({
      enhancedBody: tObject({
        data: tUnion(['a', 'b', 'c'] as const),
      }),
    }),
  })((req, res) => {
    const body = req.body
    const query = req.query

    res.send({
      body,
      query,
    })
  })
)
Enter fullscreen mode Exit fullscreen mode

Motivation

The problem with Express is that it does not solve HTTP REST-API runtime-validation, static types, and API documentation out of the box. So many programmers who use Express create their own "wrapper" around the Express and the project starts to be complex because there is not a standard Express backend approach. It's cool that you can bend Express in a way your business logic wants it to, but it makes the complexity of the whole project higher for other programmers. So I think that there should be a small standard of documenting API directly in your codebase.

I tried to look for another library in the current npm ecosystem that could satisfy our needs.

  • 1. To define one SSOT (single source of truth) which would do
    • 1. runtime schema validations
    • 2. Typescript static time validations
    • 3. Generating of documentation
  • 2. Simplified minimalist API
  • 3. Nice runtime-error messages
  • 5. Support of REST-API

I tried hapi.js, nest.js, and other libraries but unfortunately, I found nothing, so I decided to create my solution which would fully satisfy our needs.

This article is intended to open a discussion if the API and the whole view on the problem is appropriate and to check if other people like the same approach as I do.

Thanks to simple APIs you can easily integrate Express endpoints one after another without breaking existing functionality with raw Express API.

Patterns used in the package API

SSOT / DRY

Many programmers try hard to keep the code DRY (don't repeat yourself). I think that a much more important pattern for clean code is the SSOT (single source of truth). It's very important to have a definition of your business state in only one place in your codebase. If you duplicate some parts of the state in more places you will end up with bugs in your programs because you
will forget to update all the relevant parts of the codebase.

Decorators vs Higher order functions

I decided to use a simple pure Javascript higher order function to decorate handlers instead of fancy pants class-based decorators API to simply keep the good old Express API of endpoints and not to be dependent on fancy experimental compilers options like decorators and so on.
If you want to know more about it, check this article.

Package future?

In the future, I would like to add few features which could add more flexibility to define a schema for Express handler endpoint

Cyclic dependencies

Cyclic dependencies of data types, similar to
graphql have already been defined.

Better support of creating custom data type

In the current library there is no official way how to create a custom data type with custom validators and custom type inference and you have to use one of the pre-defined types tBoolean tNumber tUnion tNonNullable tAny tObject tCustomScalar tString.

What do you think?

In my humble opinion, the npm ecosystem is missing some strong Typescript-supported solutions for handling that basic stuff like HTTP REST-API schema defining.

I would like to know your opinion about this kind of package API.

If you have any topics to discuss don't be shy to say them out loud in the comment section and open discussion.

If you enjoyed reading the article don’t forget to like it to tell me if it makes sense to continue.

Top comments (0)

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.