npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

swagger-typed-express-docs

v0.8.14

Published

Simple express runtime parser and documentation swagger generator with 100% support of Typescript static types

Downloads

393

Readme

swagger-typed-express-docs

swagger-typed-express-docs keep you simple document your endpoints with just one single source of truth which

this project generates OpenAPI 3.0.0, not swagger!

  • Generate OpenAPI API documentation
  • Compile time validations - Infer Typescript static types out of the box
  • Runtime validate each of your HTTP request with user-friendly error messages

To do that there is just a simple high-order-function API. So you can just simply wrap your endpoint with the apiDoc(...) and initialize project via initApiDocs()

Example usage

You can see full app example in the repository:

import express from 'express'
import { apiDoc, initApiDocs, T } from 'swagger-typed-express-docs'
import swaggerUi from 'swagger-ui-express'

const app = express()
const port = 3000

app.get(
  '/user/:userId',
  /**
   * adding metadata for handlers where we want to have
   *  - runtime checks
   * - compile-time checks
   * - generate swagger documentation
   */
  apiDoc({
    params: {
      userId: T.string,
    },
    query: {
      name: T.string,
      header: T.list(T.enum(['a', 'b', 'c'] as const))),
    },
    body: {
      header: T.list(T.enum(['a', 'b', 'c'] as const))),
      message: T.string,
      footer: T.string,
    },
    returns: T.object({
      enhancedBody: T.object({
        data: T.enum(['a', 'b', 'c'] as const),
      }),
    }),
  })((req, res) => {
    const body = req.body
    const query = req.query

    // res.send is typed by typescript, but it do not transform values by tSchema, so
    // you may use tSend instead
    res.tSend({
      body,
      query,
    })
  })
)
/**
 * before you start the server you have to setup library
 */
const swaggerJSON = initApiDocs(app, { info: { title: 'my application' } })

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerJSON))

Package API

The whole library expose 2 main functions: initApiDocs(...) and & apiDoc(...)

initApiDocs

This method takes a swagger metadata which will be deeply merged into generated documentation.

initApiDocs() returns generated Swagger JSON which you can use to document your API.

example usage

const swaggerJSON = initApiDocs(app, { info: { title: 'my application' } })

to make the application work you have to call initApiDocs() at the end of routes definition and before you start app.listen(...)

apiDoc

apiDoc(...) is high-order-function which you use to wrap express endpoint handler and define a meta-information about inputs & outputs of each API handler.

example usage:

import { T } from 'swagger-typed-express-docs'

app.get(
  '/',
  apiDoc({
    query: {
      name: T.string
      header: T.list(T.enum(['a', 'b', 'c'] as const))),
    },
    body: {
      header: T.list(T.enum(['a', 'b', 'c'] as const))),
      message: T.null_list,
      footer: T.string,
    },
    returns: T.null_object({
      data: T.null_object({
        nestedData: T.enum(['a', 'b', 'c'] as const),
      }),
    }),
  })((req, res) => {
    const body = req.body
    const query = req.query

    res.send({
      body,
      query,
    })
  })
)

The library exposes many functions and objects which help you to create schema as you want.

  • T.string(...)
  • T.null_string(...)
  • T.boolean(...)
  • T.null_boolean(...)
  • T.number(...)
  • T.null_number(...)
  • T.enum(...)
  • T.null_enum(...)
  • T.oneOf(...)
  • T.null_oneOf(...)
  • T.any(...)
  • T.null_any(...)
  • T.object(...)
  • T.null_object(...)
  • T.list(...)
  • T.null_list(...)
  • T.nonNullable(...)

if you want to see more examples on how to build schema structure by function compositions you can check the tests

Setup environments

Express body parsing

if you want to parser body, you have to setup body parser express middleware.

app.use(express.json())

Typescripts null checks

to make fully work tNonNullable you have to setup tsconfig.json properly.

{
  ...
  "compilerOptions": {
    ...
    "strictNullChecks": true,
  }
}

Example library preview

static type helper preview

static type helper preview

Swagger preview

Decisions

if some field in the object is nullable null_ key may not be required, but in TS types, only value is of type | undefined

so the non existed keys are nullable as well, thanks to this, the schema is simplier for the writter, because there is less edge cases to think about

All defined schema attribute stripping

if you define one of apiDoc objects like query, body, params or headers it'll strip all unknown object attributes so omit potential security data injections

By default, if you do not define some of the tSchema, nothing is validate or parsed for current object

Express query parsing

You can parse query thanks to express-query-parser library.

We parser to keep parsing only undefined and null values and the rest may be done by transform types. Many transform types is predefined in the T.cast. object.

import { queryParser } from 'express-query-parser'

app.use(
  queryParser({
    parseNumber: false,
    parseBoolean: false,
    // turn on only null & undefined values, to use T.cast. utils
    parseNull: true,
    parseUndefined: true,
  })
)

app.get(
  '/',
  apiDoc({
    query: {
      name: T.cast.number,
      ids: T.extra.null_toListIfNot(T.cast.number),
    },
  })((req, res) => {
    const body = req.body
    const query = req.query

    res.send({
      body,
      query,
    })
  })
)

if you want to parse string 'null' by yourself, you need to create a custom T.transform data type which will handle this edge case

Validating output via res.tSend

The library automatically injects the tSend function into res.tSend. This function takes data and sends a 200 success status response. However, before sending, it verifies if the schema matches the apiDoc({ returns: ... }) schema definition and sanitizes the data. Therefore, if you send more data than what is defined (for example, an object with additional attributes), the surplus data will be stripped. This mechanism enhances the function's reliability.

After defining T.transform types, encoders are applied, and the data is transformed accordingly.

Custom transformation of incoming data (Encoders / decoders)

Data Transformation Flow: User -> HTTP -> Encoded -> Decoded -> Express Handler Express Handler -> Decoded -> Encoded -> HTTP -> User

  • Users interact exclusively with encoded types.
  • Express handlers interact solely with decoded types.

Null Handling:

  • If a data type is nullable, null and undefined values are automatically handled, and the encoder/decoder functions will not be invoked.
  • If null is not explicitly defined, encoder and decoder functions may still be called with null or undefined values. In such cases, handling must be implemented manually within the parser/serializer functions.

Data utils:

- T.deepNullable

Vocabularies

decoder = parser encoder = serializer transform = encoder + decoder