apigenie-generatorOpenAPI serversserver express

server express

Generate an Express server stub in TypeScript from an OpenAPI document.

Usage

apigenie-generator server express [...OPTIONS] INPUT_FILE

Description

The input must be an OpenAPI 3.0, 3.1 or 3.2 document; bundle documents referencing other files with apigenie bundle first. The generated code runs on Node.js 20 or later with Express 5.

The generator writes typed handler interfaces and a router serving them. Your code implements the handlers in files of its own, so regenerating the code never overwrites it:

import express from "express";
import { BASE_PATH, createRouter, HttpError, type PetsHandlers } from "pet-store-server";

const pets: Partial<PetsHandlers> = {
  async listPets(request) {
    const pets = await db.pets.list({ limit: request.query.limit });
    return { status: 200, body: pets, headers: { "X-Total-Count": pets.length } };
  },
  async showPetById(request) {
    const pet = await db.pets.find(request.params.petId);
    if (pet === undefined) {
      throw new HttpError(404, "No such pet");
    }
    return { status: 200, body: pet };
  },
};

const app = express();
app.use(
  BASE_PATH,
  createRouter(
    { pets },
    {
      handleNotFound: true,
      security: { api_key: ({ credential }) => users.byApiKey(credential) },
    },
  ),
);
app.listen(8080);

Operations are grouped into handler interfaces by their first tag (PetsHandlers); operations without tags belong to the default group. A handler is named after the operationId of its operation, or after its method and path. It receives:

  • the request: the decoded and validated path, query, header and cookie parameters (params, query, headers, cookies, by name) and the body with its declared media type (contentType), all typed from the document
  • the context: the Express request and response (req, res), the description of the operation and the principal returned by the security handlers

It returns the response: a status, a body, headers and optionally a content type, typed per status from the document. A handler which answers on its own through context.res returns nothing.

For every request, the router:

  1. checks the security requirements of the operation: any of them, with all the schemes of a requirement, must be satisfied. A security handler receives the credential (the API key, the bearer token or the decoded user:password of basic authentication) and the required scopes, and returns the principal, or undefined to reject the request with 401 Unauthorized. It may throw an HttpError, e.g. 403 Forbidden for missing scopes. Schemes without handlers reject every request.
  2. decodes the parameters in all OpenAPI styles (simple, label, matrix, form, spaceDelimited, pipeDelimited and deepObject, exploded or not) and converts them to the types of their schemas; parameters with content are parsed as JSON. Absent parameters get their default values.
  3. reads the body: JSON, application/x-www-form-urlencoded (converted to the types of its schema), text and binary bodies are parsed; multipart/form-data bodies are left to middleware such as multer, whose req.body is used. A media type that isn’t declared is answered with 415 Unsupported Media Type, a missing required body with 400.
  4. validates the parameters and the body against their schemas (see validation) and answers invalid requests with 400 Bad Request.
  5. calls the handler, or answers with 501 Not Implemented when there’s none.
  6. writes the response, optionally validating it first (validateResponses).

Errors are answered with problem details (application/problem+json). Validation errors list the failed checks:

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "The request is invalid",
  "errors": [
    { "in": "query", "name": "limit", "path": "", "keyword": "maximum", "message": "must be <= 50" },
    { "in": "body", "path": "/name", "keyword": "required", "message": "must have required property 'name'" }
  ]
}

Routes are registered so that literal path segments win over parameters (/pets/mine before /pets/{petId}). Deprecated operations are marked @deprecated, and operations disabled with the x-apigenie extension (codegen.disabled) are left out. Callbacks and webhooks are not generated, and operation and path servers are ignored: every route is served under BASE_PATH.

The generated package contains:

  • src/types.ts: the types of the component schemas, and the request and response types of every operation
  • src/handlers.ts: the handler interfaces, Handlers, SecurityHandlers and createRouter(handlers, options)
  • src/metadata.ts: the operations (OPERATIONS) and security schemes described for the runtime, and the port (DEFAULT_PORT) and path (BASE_PATH) of the first server
  • src/validation.ts and, depending on validation, src/schemas.ts and src/ajv_engine.ts, or src/validators.ts
  • src/runtime.ts: the request handling code, which doesn’t depend on the document
  • src/document.ts: the OpenAPI document, with copy_document
  • src/index.ts, package.json, tsconfig.json and README.md
  • src/server.ts: an example server with stub handlers, with scaffold

README.md and server.ts are written only if they don’t exist; the other files are overwritten.

Extensions

Extension Effect
x-apigenie.codegen.name name of the type of a component schema
x-apigenie.codegen.controller-name on an operation or its path item: the handler interface of the operation, when grouping lists extension
x-apigenie.codegen.action-name name of the handler of an operation, when action_naming lists extension
x-apigenie.codegen.disabled true on an operation or its path item leaves the operation out, unless skip_disabled is false
x-defaultMapping defaultMapping of a discriminator in OpenAPI 3.0 and 3.1 documents: the schema of the values whose discriminator value isn’t mapped

The extensions can live in an overlay for this target instead of in the document, e.g. express.overlay.yaml:

overlay: 1.0.0
info:
  title: TypeScript names
  version: 1.0.0
actions:
  - target: $.components.schemas.Pet
    update:
      x-apigenie:
        codegen:
          name: Animal
  - target: $.paths['/admin']
    update:
      x-apigenie:
        codegen:
          disabled: true
apigenie-generator server express openapi.yaml --overlay express.overlay.yaml -o out

Options

-c, –config

Path to the configuration file to use. See the “Configuration” section for more information.

–overlay OVERLAY

Path to an OpenAPI Overlay (1.0 to 1.2) applied to the input before generating, e.g. one setting the extensions of the “Extensions” section. Can be repeated: the overlays apply in order, after those listed under commands.generate.server_express.overlay in the configuration file (with paths relative to it). Warnings and errors about a value an overlay set point to the overlay.

–strict

Makes an overlay action whose target selects nothing an error (exit status 2, nothing is generated) instead of a warning (OVERLAY:LINE:COL: action #N (TARGET) selects no nodes.).

-o, –output

Directory where the package is generated.

–package-name

Name of the generated package. Defaults to the title of the API followed by -server.

–package-version

Version of the generated package. Defaults to the version of the API.

Configuration

version: '1.0'
commands:
  generate:
    server_express:
      validation: ajv
      validate_responses: true
      scaffold: true
Option Description
layout package (default): an npm package with sources in src/; sources: the sources only
import_extension Extension of relative imports: .js (default, Node.js) or an empty string (bundlers)
validation ajv (default): JSON Schemas validated with Ajv; generated: generated TypeScript validators, without dependencies; none
validate_responses Generate what’s needed to validate responses, enabled by the validateResponses router option (default: false)
grouping How operations are grouped into handler interfaces: tag (default), extension (x-apigenie codegen.controller-name) or url, or a list of them, in order of preference
action_naming Sources of handler names: id, extension (x-apigenie codegen.action-name) or url, or a list of them (default: [id, url])
skip_deprecated Leave deprecated operations out (default: false)
skip_disabled Leave out operations disabled with x-apigenie (default: true)
copy_document Include the document in the package, so that the router can serve it (default: false)
scaffold Write an example server, src/server.ts, unless it exists (default: false)
security Check the security requirements of operations (default: true)

Both validation modes report the same issues with the same messages, and both convert OAS 3.0 schemas (nullable, boolean exclusiveMinimum and exclusiveMaximum) and leave readOnly properties out of the required properties of requests and writeOnly ones out of responses. The generated validators don’t check if/then/else, dependentRequired, dependentSchemas, unevaluatedProperties, unevaluatedItems, contains, propertyNames, contentSchema and $dynamicRef; the generator warns about them. Formats are checked as JSON Schema defines them, the generated validators knowing the common ones (date-time, date, time, duration, email, hostname, ipv4, ipv6, uri, uri-reference, uuid, byte, int32 and int64).

Router options

Option Description
security Security handlers, by scheme name
validation A validation engine replacing the generated one, e.g. noValidation
validateResponses Validate the bodies returned by handlers; invalid ones are answered with 500
handleNotFound Answer requests to unknown paths with 404 and unknown methods with 405
notImplemented "501" (default) or "next": pass requests to operations without handlers to the next middleware
exposeErrors Put the messages of unexpected errors into 500 responses
onError Called with unexpected errors, which are logged with console.error otherwise
bodyLimit Maximum size of request bodies, e.g. "1mb"
serveDocument Serve the document as JSON at this path, e.g. "/openapi.json" (needs copy_document)
caseSensitive, strict Options of the Express router