mtrac — open-source tooling by Michael Tracy

open-source tooling by Michael Tracy

scroll ↓

// express-openapi-joi

Define it once.

One route definition. Validation, types and API docs come out of it, so none of them can drift from the others.

widgets.ts
defineRoute(router, {
  method: 'post',
  path: '/:id',
  prefix: '/widgets',
  request: {
    params: Joi.object({ id: Joi.string().uuid().required() }),
    body: Joi.object<{ name: string }>({ name: Joi.string().required() }),
  },
  responses: { 201: { description: 'Created' } },
  handler: (req, res) => {
    // req.params.id and req.body.name are both typed
    res.status(201).json({ id: req.params.id, name: req.body.name });
  },
});

Validated runtime

POST /widgets/not-a-uuid
400 { "message": "\"id\" must be a valid GUID",
      "details": [{ "type": "string.guid", … }] }

Typed compile time

req.params.id   // string
req.body.name   // string
req.body.nmae   // ✗ Property 'nmae' does not exist

Documented GET /openapi.json

"/widgets/{id}": {
  "post": {
    "operationId": "postWidgetsId",
    "parameters": [{
      "name": "id", "in": "path", "required": true,
      "schema": { "type": "string", "format": "uuid" }
    }],
    "requestBody": { "content": { "application/json": {
      "schema": {
        "type": "object",
        "properties": { "name": { "type": "string" } },
        "required": ["name"],
        "additionalProperties": false
      } } } },
    "responses": { "201": { "description": "Created" } }
  }
}