M. Mirra's notebook[?]

created: 2019/05/27, tags: node

validate incoming API data and get editor completion and type safety for free

assume an API to save bookmarks:

// bookmarkapi/index.ts

import express, { json } from 'express'
import * as foreign from './foreign'

const app = express()
app.post('/items', json(), async (req, res) => {
  const itemSubmission = foreign.itemSubmission(req.body)
  // do something with itemSubmission

this code has the following benefits:

  • the point where foreign, unsafe data becomes valid is clearly marked
  • invalid data immediately causes a validation error, handled via the usual express mechanism [1]
  • itemSubmission is fully typed, providing both safety and convenience (intellisense)
  • foreign data is validated through more granular checks than those allowed by typescript: not just "string" but e.g. "email" or "url"; not just "number" but "integer between 10 and 1000"
  • no typescript definition needs to be written and kept in sync with the foreign data schema
  • all foreign data is described in one place only; details of how it is validated are confined to said place

how it works

the heavy lifting is courtesy of typesafe-joi [1]:

// foreign.ts

import joi from 'typesafe-joi'

const schemas = {
  itemSubmission: joi.object().keys({
    url: joi.string().uri({
      scheme: ['http', 'https']
    title: joi.string().allow('').optional(),

export const itemSubmission = (data: any) =>
  joi.attempt(data, schemas.itemSubmission)

and if for any reason an explicit type for itemSubmission is needed:

// bookmarkapi/foreign.ts
// ...

export type ItemSubmission = ReturnType<typeof itemSubmission>