Skip to main content

ExpressJS

The @basketry/express generator automates the creation of Express-based APIs, streamlining the process of defining routes, handling requests, and enforcing API contracts. It generates a router factory that creates an Express router for structured error handling, middleware support, and type-safe DTO mappings between internal business objects and API responses. This ensures consistency, maintainability, and adherence to the API schema while allowing developers to focus on business logic rather than boilerplate code.

Installโ€‹

npm install @basketry/express

Basic Usageโ€‹

basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": [
"@basketry/typescript",
"@basketry/typescript-dtos",
"@basketry/express",
"@basketry/zod"
],
"output": "src",
"options": {
"dtos": { "role": "server" },
"express": { "validation": "zod" }
}
}

File Structureโ€‹

This generator will create an express directory that contains all the necessary files for an Express-based API. Details about each of these files can be found in Advanced Usage section below.

my-project/
โ”œโ”€โ”€ node_modules/
โ”œโ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ v1/
โ”‚ โ”‚ โ”œโ”€โ”€ dtos
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ mappers.ts
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ README.md
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ types.ts
โ”‚ โ”‚ โ”œโ”€โ”€ express/ <-- generated
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ errors.ts <-- generated
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ handlers.ts <-- generated
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ index.ts <-- generated
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ README.ts <-- generated
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ router-factory.ts <-- generated
โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ types.ts <-- generated
โ”‚ โ”‚ โ”œโ”€โ”€ schemas.ts
โ”‚ โ”‚ โ””โ”€โ”€ types.ts
โ”‚ โ”œโ”€โ”€ app.ts
โ”‚ โ””โ”€โ”€ index.ts
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ basketry.config.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ petstore.json
โ””โ”€โ”€ README.md

Optionsโ€‹

This generator depends on the @basketry/typescript, @basketry/typescript-dtos, and @basketry/zod generators and all of their applied options will also apply to files emitted by this generator.

See:

This generator depends on files generated by the @basketry/typescript, @basketry/typescript-dtos, and @basketry/zod generators. In most cases, those files will exist within the same directory as the generated express directory.

By default, the generated code will import its dependencies as follows:

src/v1/express/handlers.ts
import * as schemas from "../schemas";
import * as types from "../types";

The @basketry/express generator accepts the following additional options to customize the generated code:

validationโ€‹

  • Type: "zod" | "native" - The validation library to use for request validation.
  • Default: "native"
basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": [
"@basketry/typescript",
"@basketry/typescript-dtos",
"@basketry/express",
"@basketry/zod"
],
"output": "src",
"options": {
"dtos": { "role": "server" },
"express": { "validation": "zod" }
}
}

responseValidationโ€‹

  • Type: "none" | "warn" | "strict" - The response validation strategy to use.

    • none: Disables response validation.
    • warn (default): Logs warnings for invalid responses but does not block execution.
    • strict: Enforces strict validation, throwing errors for invalid responses.
basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": [
"@basketry/typescript",
"@basketry/typescript-dtos",
"@basketry/express",
"@basketry/zod"
],
"output": "src",
"options": {
"dtos": { "role": "server" },
"express": {
"validation": "zod",
"responseValidation": "strict"
}
}
}

schemasImportPathโ€‹

  • Type: string - The location of the Zod schemas module. (This option is only applicable when validation is set to "zod".)
basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": [
"@basketry/typescript",
"@basketry/typescript-dtos",
"@basketry/express",
"@basketry/zod"
],
"output": "src",
"options": {
"dtos": { "role": "server" },
"express": {
"validation": "zod",
"schemasImportPath": "@custom/package/schemas"
}
}
}

This option as configured will now emit the following code at the top of the generated file:

src/v1/express/handlers.ts
import * as schemas from "@custom/package/schemas";

typesImportPathโ€‹

  • Type: string - specifies the path for the types import.
basketry.config.json
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": [
"@basketry/typescript",
"@basketry/typescript-dtos",
"@basketry/express",
"@basketry/zod"
],
"output": "src",
"options": {
"dtos": { "role": "server" },
"express": {
"validation": "zod",
"typesImportPath": "@custom/package/types"
}
}
}

This option as configured will now emit the following code at the top of the generated file:

src/v1/express/handlers.ts
import * as types from "@custom/package/types";

Advanced Usageโ€‹

A project-specific set of docs will be generated in the express/README.md file. Here is a generalized overview of the files generated by this generator.

Router Factoryโ€‹

The router factory provides the getRouter function, which generates an Express router for the API. The router factory is responsible for creating the Express router, registering the routes, and attaching the appropriate middleware to each route.

Basic Usageโ€‹

Mount the router on an Express app:

src/app.ts
import express from "express";
import { getRouter } from "./v1/express/router-factory";

const app = express();

app.use("/v1", [
getRouter({
// Update with your OpenAPI schema
schema: require("./petstore.json"),

// Update with your service initializers
getUserService: (req) => new MyUserService(req),
getWidgetService: (req) => new MyWidgetService(req),
}),
]);

Custom Middlewareโ€‹

You can provide custom middleware to the router factory by passing a middleware object to the middleware property of the input object. The middleware object should contain middleware functions keyed by the name of the service method they are associated with.

src/app.ts
import express from "express";
import { getRouter } from "./v1/express/router-factory";
import { authenticationMiddleware } from "../auth";

const app = express();

app.use("/v1", [
getRouter({
// TODO: add schema and service initializers

// Update with your middleware as needed
middleware: {
// Added to all routes except the one that serves the Swagger UI
_exceptSwaggerUI: authenticationMiddleware,

getWidget: (req, res, next) => {
// TODO: Implement your custom middleware here
next();
},

deleteWidget: (req, res, next) => {
// TODO: Implement your custom middleware here
next();
},
},
}),
]);

Custom Handlersโ€‹

The handlers module exports the default generated handlers. These handlers are added to the Express router and generally don't need to be manually imported anywhere to make the API work. Occasionally, the generated handlers won't be sufficient, so the router factory allows for supplying custom handler overrides.

src/app.ts
import express from "express";
import { getRouter } from "./v1/express/router-factory";

const app = express();

app.use("/v1", [
getRouter({
// TODO: add schema and service initializers

// Update with your handlers as needed
handlerOverrides: {
getUser: (req, res, next) => {
// TODO: Implement your custom handler here
// Note that the params, body, and query values are statically typed
// per the API contract
},
},
}),
]);

Errorsโ€‹

This module provides utility functions and types for creating and identifying errors in an ExpressJS API. It defines custom error types for different error conditions such as validation errors, method not allowed, handled exceptions, and unhandled exceptions. Each error type is accompanied by helper functions to generate and identify errors, which can be used in your ExpressJS API error-handling middleware.

Method Not Allowedโ€‹

This error type is used to indicate that the HTTP method is not defined on the route in the API contract.

import { RequestHandler } from "express";
import { isMethodNotAllowed } from "./v1/express/errors";

export const handler: RequestHandler = (err, req, res, next) => {
// Checks to see if the error is a MethodNotAllowedError
if (isMethodNotAllowed(err)) {
// TODO: log/instrument occurence of the error

if (!res.headersSent) {
// TODO: return an error response
}
}
next(err);
};

Validation Errorโ€‹

This error type is used to indicate that the request data failed validation against the API contract. This error will contain an array of validation errors provided by the validation library (eg. zod, native, etc).

import { RequestHandler } from "express";
import { isValidationErrors } from "./v1/express/errors";

export const handler: RequestHandler = (err, req, res, next) => {
// Checks to see if the error is a ValidationErrorsError
if (isValidationErrors(err)) {
// TODO: log/instrument occurence of the error

if (!res.headersSent) {
// TODO: return an error response
}
}
next(err);
};

Handled Exceptionโ€‹

This error type is used to indicate that an error occured in a service method as was returned in a well-structured format. (This is not supported if the service does not return response envelopes.)

import { RequestHandler } from "express";
import { isHandledException } from "./express/errors";

export const handler: RequestHandler = (err, req, res, next) => {
// Checks to see if the error is a HandledExceptionError
if (isHandledException(err)) {
// TODO: log/instrument occurence of the error

if (!res.headersSent) {
// TODO: return an error response
}
}
next(err);
};

Unhandled Exceptionโ€‹

This error type is used to indicate that an unexpected error occured in the API.

import { RequestHandler } from "express";
import { isUnhandledException } from "./v1/express/errors";

export const handler: RequestHandler = (err, req, res, next) => {
// Checks to see if the error is a UnhandledExceptionError
if (isUnhandledException(err)) {
// TODO: log/instrument occurence of the error

if (!res.headersSent) {
// TODO: return an error response
}
}
next(err);
};