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
- yarn
- pnpm
npm install @basketry/express
yarn add @basketry/express
pnpm add @basketry/express
Basic Usageโ
{
"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:
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"
- zod
- native
{
"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" }
}
}
Native validation is deprecated and will not receive updates. Please use the Zod Generator instead.
{
"source": "petstore.json",
"parser": "@basketry/openapi-3",
"generators": [
"@basketry/typescript",
"@basketry/typescript-dtos",
"@basketry/typescript-validators",
"@basketry/express"
],
"output": "src",
"options": {
"dtos": { "role": "server" },
"express": { "validation": "native" }
}
}
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.
- zod
- native
{
"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"
}
}
}
This option is not applicable when validation
is set to "native"
. Please use the Zod Generator instead.
schemasImportPath
โ
- Type:
string
- The location of the Zod schemas module. (This option is only applicable whenvalidation
is set to"zod"
.)
- zod
- native
{
"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:
import * as schemas from "@custom/package/schemas";
This option is not applicable when validation
is set to "native"
. Please use the Zod Generator instead.
typesImportPath
โ
- Type:
string
- specifies the path for the types import.
{
"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:
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:
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.
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.
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);
};