# openapi-backend routes, validates and mocks from one OpenAPI document

> openapi-backend is a framework agnostic middleware for Node.js that registers handlers by operationId, validates requests and responses with AJV against your schemas, and auto-mocks responses from your examples, with no code generation step. It suits teams that already keep an OpenAPI document and want it enforced at runtime, and it is not a code generator.

**openapistack/openapi-backend** — Build, Validate, Route, Authenticate and Mock using OpenAPI

- Repository: https://github.com/openapistack/openapi-backend
- Website: https://openapistack.co
- Stars: 687 · Forks: 99
- Language: TypeScript
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/openapistack-openapi-backend

## The operationId is the whole binding between your spec and your code

The core mechanism is unusual and worth understanding before you adopt it. openapi-backend does not generate a server, and it does not generate a client. It reads your OpenAPI definition at runtime and dispatches an incoming request to a handler you registered under a name you chose. That name is the operationId from the specification. So the definition file is not documentation that a tool reads to produce something else, it is the routing table. A route is matched by method and path against the document, and the operationId found there selects which registered function runs. The practical consequence is a strict requirement on the document itself, because a missing or duplicated operationId means a route that dispatches to the wrong handler or to none. It also means the same specification serves as the contract for consumers, and the error responses you return are validated against the same document, which is a tighter loop than a spec that only lives in a repository.

## The quick start is a constructor, a register call and an init

Installing is a single dependency, and the documentation points to a petstore definition for the example:

```bash
npm install --save openapi-backend
```

The setup then follows three steps. You create the API object with your definition file or object, you register the handlers, and you initialize the backend:

```javascript
import OpenAPIBackend from 'openapi-backend';

// create api with your definition file or object
const api = new OpenAPIBackend({ definition: './petstore.yml' });

// register your framework specific request handlers here
api.register({
  getPets: (c, req, res) => res.status(200).json({ result: 'ok' }),
  getPetById: (c, req, res) => res.status(200).json({ result: 'ok' }),
  validationFail: (c, req, res) => res.status(400).json({ err: c.validation.errors }),
  notFound: (c, req, res) => res.status(404).json({ err: 'not found' }),
});

// initalize the backend
api.init();
```

The two error handlers are not incidental. validationFail and notFound are reserved names that the library calls when a request fails schema validation or when no route matches, so a caller does not have to guess the shape of a failure response, and the validation errors themselves are handed to you in the context object. The handler signature is framework specific, which is the point of the framework agnostic claim.

## Six runtimes are documented, and the request shape is the variable

The examples in the README cover Express, AWS Lambda, Azure Functions, Fastify, Hapi and Koa, with separate example projects for AWS SAM, AWS CDK, the Serverless Framework and SST. The pattern is the same everywhere, and it is worth understanding because it explains the design. The library does not know about your framework's request or response objects, so you adapt them into a plain object with method, path, query, body and headers, and you pass that object as the first argument to handleRequest, followed by the framework's own request and response for the handler to use. In Express the adapter is trivial:

```javascript
import express from 'express';

const app = express();
app.use(express.json());
app.use((req, res) => api.handleRequest(req, req, res));
app.listen(9000);
```

Express and Koa pass the request object straight through, so the call is nearly the same. The AWS Lambda example builds the plain object explicitly from the event, taking method from event.httpMethod, path from event.path, query from event.queryStringParameters, body from event.body and headers from event.headers, and the Fastify and Hapi examples read the body from request.body and req.payload respectively. One consequence worth noting is that a serverless example uses the legacy httpMethod field rather than the versioned request context, so the shape of the adapter is something to check against your own event format.

## Validation runs on AJV against your JSON Schema

The validation feature is where the runtime cost and the runtime benefit both live. The library uses JSON Schema to validate API requests and responses, and it does so with the AJV library under the hood for performant validation. There are two sides to that. On the way in, a request body or parameter that does not match the schema is rejected before your handler runs, which is the part most teams want, because a malformed payload becomes a 400 with the validation errors rather than a strange state in your database. On the way out, a response that does not match the declared schema is caught as well, which is the part that catches a drift between the spec and the implementation, and which is easier to forget you have turned on. The dependency list in the package metadata backs the description, with ajv at version 8, openapi-schema-validator for the OpenAPI level, dereference-json-schema and the json-schema-ref-parser for resolving references, and qs for query string parsing. The reference resolution matters because most real documents use $ref to share schemas, and a validator that does not dereference would treat those as unresolvable.

## Mock responses come from your examples, which is what makes the mock honest

The auto-mock feature derives responses from the OpenAPI examples objects or from the JSON Schema definitions in your document. That is the interesting design decision. A mock server usually means a fixture file that somebody maintains alongside the spec, and the two drift. Here the mock is a function of the same document that describes the real API, so a schema change moves the mock with it. The dependency list shows mock-json-schema in the package, which is the piece that synthesises a value from a schema when there is no explicit example to use, and the qs and lodash entries cover query handling and object work. The limitation follows from the design. A mock generated from a schema is only as good as the schema, and a schema with no example and a loose type will produce a value that satisfies the contract while being useless as a fixture for a front end developer. The examples branch of the repository carries full example projects for the frameworks above, and those are the place to see the mock behaviour in a realistic document.

## Security schemes become auth handlers you register yourself

Authorization is handled in the same declarative way. You register Auth or Security Handlers for the OpenAPI Security Schemes to authorize API requests, which means the specification declares the scheme, a bearer token, basic auth, an API key, or an OAuth flow, and you supply the function that checks it. The library then applies that check to the routes that reference the scheme, so the mapping between declaration and enforcement is derived from the document rather than maintained in a list. That is the same philosophy as the routing and the validation, and it is the reason the package describes itself as a middleware tool for building APIs with the OpenAPI Specification. The trade is that the enforcement logic is yours, and a scheme declared in the document with no handler registered is a gap rather than an error, so the document and the code have to be reviewed together. The topics list express, nodejs, openapi, swagger, router, mock, serverless and hacktoberfest, which is a fair summary of the scope.

## No code generation is the feature, and it is worth weighing deliberately

The feature list makes a point of optimising runtime routing and validation with no generated code, and the comparison with code generators is the real design decision. A generator produces a client and a server stub from your document, which gives you type safety at compile time and a tree you can read, but the generated artefacts go stale when the document changes, and a large API produces a large generated tree. This package instead reads the document at runtime, so there is nothing to regenerate and the document is always the live contract. The cost is the mirror image of that. There is no compile time check that your handler exists for every operation, no generated types for your clients, and the routing and validation happen on every request rather than being folded into build output. For a service whose document changes weekly and whose clients are written by hand or in another language, that trade is usually right. For a team that wants a typed client and a typed server, a generator is the better tool, and the two approaches are not really comparable.

## Where openapi-backend is the wrong choice

Three cases argue against it. The first is a project without an OpenAPI document, or with one that is out of date. Since the document is the routing table, the validator and the mock source, an inaccurate document is not a documentation bug but a functional one, and a team that treats the spec as an afterthought will spend its time debugging mismatches between the two. The second is a team that wants a strongly typed server with a compile time guarantee that every route is implemented. Nothing here provides that, because the handlers are registered at runtime and a missing one surfaces as a 404 at request time rather than as a build error. The third is an API with a large number of operations and a heavy payload, because validation on every request has a cost that a generated server would have folded into a schema check at build time. There is also a governance point. The package is MIT licensed with the licence file at the top of the repository, and it ships a CycloneDX SBOM file under sbom/, along with THIRD_PARTY_NOTICES.md and a SECURITY.md. For an organisation that requires a software bill of materials for its dependencies, that is a practical advantage over a smaller package, and it is one of the reasons this library shows up in enterprise API gateways rather than only in side projects.

## Conclusion

Adopt openapi-backend when your team already maintains an OpenAPI document and wants request validation, response validation, security schemes and mock responses driven by it at runtime, and write the handlers by hand if you want a generated client instead. Verify first that every operation you serve has an operationId, since that string is the only thing binding a route to a handler, and read the docs on openapistack.co because the README now points there rather than carrying the full reference.

## FAQ

### What does openapi-backend do?

It is a framework agnostic middleware tool for building APIs with the OpenAPI Specification. You describe the API in an OpenAPI document, register handlers for its operationIds, and the library routes requests, validates them and responses with AJV against your schemas, applies your security handlers and can auto-mock responses from your examples.

### How do I install and start openapi-backend?

Install it with npm install --save openapi-backend, then create the API object with your definition file, register your handlers with api.register, and call api.init(). The README points at the examples branch of the repository for full example projects.

### Does openapi-backend generate code?

No. The feature list states that runtime routing and validation are optimised with no generated code, which means the OpenAPI document is read at runtime rather than compiled into a client or a server stub.

### Which frameworks does openapi-backend support?

The README documents examples for Express, AWS Lambda, Azure Functions, Fastify, Hapi and Koa, with full example projects for AWS SAM, AWS CDK, the Serverless Framework and SST. In each case you adapt the framework request into a plain object with method, path, query, body and headers.

### Is openapi-backend still maintained?

The repository is not archived and the last push was on 2026-10-19, with releases 5.22.0 on 2026-10-16, 5.21.3 on 2026-10-09 and 5.21.2 on 2026-09-29. The package version in package.json is 5.22.0, the licence is MIT, and the documentation has moved to openapistack.co.

## Sources

- [License: MIT](https://github.com/openapistack/openapi-backend/blob/main/LICENSE)
- [openapistack/openapi-backend on GitHub](https://github.com/openapistack/openapi-backend)
- [Project website](https://openapistack.co)
- [README](https://github.com/openapistack/openapi-backend/blob/main/README.md)
- [Releases](https://github.com/openapistack/openapi-backend/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/openapistack-openapi-backend
