{"id":46517208,"url":"https://github.com/jasonblanchard/openapi-typescript-server","last_synced_at":"2026-03-06T18:04:03.711Z","repository":{"id":280346668,"uuid":"941680089","full_name":"jasonblanchard/openapi-typescript-server","owner":"jasonblanchard","description":"Codegen TypeScript servers from OpenAPI","archived":false,"fork":false,"pushed_at":"2026-01-01T14:27:11.000Z","size":581,"stargazers_count":26,"open_issues_count":5,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-06T17:18:20.645Z","etag":null,"topics":["api","app","codegen","expressjs","openapi","openapi-generator","openapi3","openapi3-1","swagger","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jasonblanchard.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-02T21:03:06.000Z","updated_at":"2026-01-01T14:25:58.000Z","dependencies_parsed_at":"2025-03-17T00:25:57.397Z","dependency_job_id":"66961ee1-9852-4226-8aa0-932d49485882","html_url":"https://github.com/jasonblanchard/openapi-typescript-server","commit_stats":null,"previous_names":["jasonblanchard/openapi-typescript-server"],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/jasonblanchard/openapi-typescript-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonblanchard%2Fopenapi-typescript-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonblanchard%2Fopenapi-typescript-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonblanchard%2Fopenapi-typescript-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonblanchard%2Fopenapi-typescript-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jasonblanchard","download_url":"https://codeload.github.com/jasonblanchard/openapi-typescript-server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonblanchard%2Fopenapi-typescript-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30189485,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T17:33:53.563Z","status":"ssl_error","status_checked_at":"2026-03-06T17:33:51.678Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","app","codegen","expressjs","openapi","openapi-generator","openapi3","openapi3-1","swagger","typescript"],"created_at":"2026-03-06T18:04:03.177Z","updated_at":"2026-03-06T18:04:03.701Z","avatar_url":"https://github.com/jasonblanchard.png","language":"TypeScript","readme":"# openapi-typescript-server\n\n`openapi-typescript-server` is a CLI and minimal runtime library that helps you implement type-safe APIs documented by OpenAPI.\n\n[![Test Build](https://github.com/jasonblanchard/openapi-typescript-server/actions/workflows/test.yaml/badge.svg)](https://github.com/jasonblanchard/openapi-typescript-server/actions/workflows/test.yaml)\n[![npm version](https://badge.fury.io/js/openapi-typescript-server.svg)](https://badge.fury.io/js/openapi-typescript-server)\n\n## Key Features\n\n- ✅ Codegen server implementations from OpenAPI 3.0 and 3.1\n- ✅ Framework-agnostic core with Express adapter available\n- ✅ Minimal runtime footprint\n- ✅ Built on top of [openapi-typescript](https://github.com/openapi-ts/openapi-typescript)\n\n## Overview\n\nThis library works by generating a TypeScript server interface based on your OpenAPI spec using types from [openapi-typescript](https://github.com/openapi-ts/openapi-typescript).\n\nYou provide a concrete implementation that satisfies the interface for each path operation.\n\nAt runtime, your implementation is converted into HTTP handlers for various frameworks like [Express](https://github.com/jasonblanchard/openapi-typescript-server/tree/main/packages/openapi-typescript-server-express).\n\n## Stability\n\n⚠️ This package is in very early development. Breaking changes may be introduced as the design and implementation takes shape towards a stable release. For now, proceed with caution! ⚠️\n\n## Usage\n\n### Installation\n\nInstall build-time packages as dev dependencies:\n\n```bash\nnpm install -D openapi-typescript openapi-typescript-server\n```\n\nInstall runtime adapter (Express example):\n\n```bash\nnpm install openapi-typescript-server-express openapi-typescript-server-runtime\n```\n\n**Recommended**: Install middleware for runtime validation:\n\n```bash\nnpm install express-openapi-validator\n```\n\n### Quickstart\n\nGiven an OpenAPI spec like this:\n\n`openapi.yaml`\n\n```yaml\nopenapi: 3.0.2\ninfo:\n  title: Simple Petstore\n  version: 0.0.1\nservers:\n  - url: /api/v3\npaths:\n  /speak/{petId}:\n    post:\n      operationId: makePetSpeak\n      parameters:\n        - name: petId\n          in: path\n          description: ID of pet that will speak\n          required: true\n          schema:\n            type: integer\n      requestBody:\n        required: true\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                sound:\n                  type: string\n                  description: The sound the pet will make\n              required:\n                - sound\n      responses:\n        \"200\":\n          description: successful operation\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  greeting:\n                    type: string\n                    description: The greeting from the pet\n                required:\n                  - greeting\n```\n\nFirst, follow [recommended setup from openapi-typescript](https://openapi-ts.dev/introduction).\n\nGenerate types from your OpenAPI spec:\n\n```bash\nnpx openapi-typescript ./openapi.yaml --output ./gen/schema.d.ts\n```\n\nGenerate the server interface:\n\n```bash\nnpx openapi-typescript-server ./openapi.yaml --types ./schema.d.ts --output ./gen/server.ts\n```\n\n\u003e **Note**: The `--types` path is relative to the output directory so the generated code can import it correctly.\n\nImplement your API handlers:\n\n`api.ts`\n\n```typescript\nimport type * as ServerTypes from \"./gen/server.ts\";\nimport type { Request, Response } from \"express\";\n\n// Explicitly specifying the type (rather than relying on structural typing)\n// gives you type inference for handler arguments and faster feedback in the\n// definition vs at the call site.\nconst API: ServerTypes.Server\u003cRequest, Response\u003e = {\n  makePetSpeak: async ({ parameters, requestBody }) =\u003e {\n    const petId = parameters.path.petId;\n    const sound = requestBody.content.sound;\n\n    return {\n      content: {\n        200: {\n          \"application/json\": {\n            greeting: `Pet ${petId} says \"${sound}\"`,\n          },\n        },\n      },\n    };\n  },\n};\n\nexport default API;\n```\n\nSet up your Express server:\n\n`app.ts`\n\n```typescript\nimport express from \"express\";\nimport type { Request, Response, NextFunction } from \"express\";\nimport { registerRouteHandlers } from \"./gen/server.ts\";\nimport registerRoutes from \"openapi-typescript-server-express\";\nimport API from \"./api.ts\";\nimport OpenApiValidator from \"express-openapi-validator\";\n\nexport default function makeApp() {\n  const app = express();\n\n  app.use(express.json());\n\n  const apiRouter = express();\n\n  // Runtime validation (recommended)\n  apiRouter.use(\n    OpenApiValidator.middleware({\n      apiSpec: \"./openapi.yaml\",\n      validateResponses: false,\n    }),\n  );\n\n  // Register your typed route handlers\n  const routeHandlers = registerRouteHandlers(API);\n\n  // Make Express routes from your route handlers\n  registerRoutes(routeHandlers, apiRouter);\n\n  // Mount to the base path\n  app.use(\"/api/v3\", apiRouter);\n\n  // Global error handler\n  app.use((err: unknown, req: Request, res: Response, next: NextFunction) =\u003e {\n    console.error(err);\n\n    res.status(500).json({\n      message: \"Internal Server Error\",\n    });\n    return;\n  });\n\n  return app;\n}\n```\n\nRun this server and test your API:\n\n```bash\ncurl localhost:8080/api/v3/speak/123 \\\n  -d '{\"sound\":\"grrrr\"}' \\\n  -H \"Content-Type: application/json\"\n```\n\nResponse:\n\n```json\n{ \"greeting\": \"Pet 123 says \\\"grrrr\\\"\" }\n```\n\nSee `examples/docs` for a fully working example.\n\n### Making a change\n\nNow, make a change to your OpenAPI spec:\n\n```diff\n paths:\n   /speak/{petId}:\n     post:\n       operationId: makePetSpeak\n       parameters:\n         - name: petId\n           in: path\n           description: ID of pet that will speak\n           required: true\n           schema:\n             type: integer\n       requestBody:\n         required: true\n         content:\n           application/json:\n             schema:\n               type: object\n               properties:\n                 sound:\n                   type: string\n                   description: The sound the pet will make\n               required:\n                 - sound\n       responses:\n         \"200\":\n           description: successful operation\n           content:\n             application/json:\n               schema:\n                 type: object\n                 properties:\n                   greeting:\n                     type: string\n                     description: The greeting from the pet\n+                  vibe:\n+                    type: string\n+                    enum: [\"friendly\", \"fierce\", \"playful\", \"sleepy\"]\n+                    description: The pet's current mood/vibe\n                 required:\n                   - greeting\n+                  - vibe\n```\n\nAnd re-generate the types and server interface:\n\n```bash\nopenapi-typescript ./openapi.yaml --output ./gen/schema.d.ts \u0026\u0026 openapi-typescript-server ./openapi.yaml --types ./schema.d.ts --output ./gen/server.ts\n```\n\nYou'll see that TypeScript will tell you exactly what needs to change in your code to conform to the new OpenAPI spec:\n\n```\nProperty 'vibe' is missing in type '{ greeting: string; }' but required in type '{ greeting: string; vibe: \"friendly\" | \"fierce\" | \"playful\" | \"sleepy\"; }'.ts(2322)\n```\n\n\u003e **Note**: You may need to restart the TS Server in VS Code to see the TypeScript error after re-generating the types and server interface.\n\nSatisfy the compiler to get it working again:\n\n```diff\n const API: ServerTypes.Server\u003cRequest, Response\u003e = {\n   makePetSpeak: async ({ parameters, requestBody }) =\u003e {\n     const petId = parameters.path.petId;\n     const sound = requestBody.content.sound;\n\n     return {\n       content: {\n         200: {\n           \"application/json\": {\n             greeting: `Pet ${petId} says \"${sound}\"`,\n+            vibe: \"fierce\",\n           },\n         },\n       },\n     };\n   },\n };\n```\n\nThis pattern also guides you when adding or removing routes.\n\n## Deeper dive\n\n### Design goals\n\nThe main goal of this package is to ensure that your server implementation stays in sync with the API documentation by generating a typed interface _from_ the OpenAPI spec.\n\nThis schema-first approach documents your system for humans or LLM coding agents, and helps you achieve type safety across the system.\n\n### In scope/goals\n\n- **Trade off build time complexity for runtime simplicity**. By using codegen as a build step, the surface area of what's executed on the server is reduced.\n- **Keep package footprint small**. Keep the solution space narrow by integrating with existing solutions.\n\n### Out of scope/non-goals\n\n- **Schema validation \u0026 type coercion**. This package assumes that all data inputs have been validated and coerced to the expected types by the time they get to your route handlers. Use other middleware like [express-openapi-validator](https://github.com/cdimascio/express-openapi-validator) to handle validation and coercion.\n- **Security \u0026 auth scopes**. This package does not handle any authentication nor authorization and ignores all security aspects of your OpenAPI schema.\n- **Webhooks**. For now, this project focuses on HTTP ingress. Webhook support is an opportunity for future enhancement.\n\n### Inspiration \u0026 prior art\n\nThis package was heavily influenced by the [Go oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) package and gRPC ergonomics.\n\n### Comparison to other solutions\n\n- [openapi-typescript](https://github.com/openapi-ts/openapi-typescript). This library only generates types from an OpenAPI spec, not operation interfaces. This is foundational to this library, but only part of the solution.\n- [openapi-ts-router](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router). This library works similarly, but requires that you bring your own path validators alongside the route handlers. It also doesn't ensure that you've implemented the entire spec.\n- [tRPC](https://trpc.io/). This library tightly couples frontend and backend code and only works for systems that are TypeScript on the frontend and backend. It also requires a decent amount of runtime complexity to tie clients and servers together.\n\n### How does it work?\n\n#### Route handling\n\nThe return value of your route handlers closely matches the structure of the OpenAPI V3 spec path response content:\n\n```\n- content:\n  - response variant (status code int or \"default\"):\n    - content type (usually \"application/json\"):\n      - \u003cyour schema starts here\u003e\n```\n\nThe route handler name uses the `operationId` if present. Otherwise it's generated based on path and method.\n\nThe return value is enveloped in `content` so that you can provide `headers` and `status` at the same level. `status` is required when the response variant is \"default\".\n\n\u003e **Note**: These return values are a bit verbose, but, since the HTTP handling side effects are delegated to the adapters, they have the added benefit of being easily testable functions with data in and out.\n\nYour route handlers are packaged up in the generated `registerRouteHandlers` function which returns a list of objects containing route method, path, and handler function.\n\nAdapters supply a `registerRoutes` function. It iterates through this data structure and uses the underlying framework (i.e. Express) to add each route, set headers, serialize response bodies, and terminate the response.\n\n#### Content type serialization\n\nContent type serialization and deserialization happens in or near the adapter layer.\n\nFor Express, request bodies are deserialized into JavaScript objects prior to the Express adapter by Express middleware. The Express adapter assumes this has already happened, and passes the deserialized (and presumably validated and coerced) JavaScript objects to your route handlers. The content type is passed as an argument to your handler in case you need to return a different response shape per.\n\nFor response body serialization, `application/json` is built in and happens automatically. Other content types can be handled by supplying a `serializers` option to the `registerRoutes` function with custom serialization functions keyed by content type.\n\n```typescript\nimport { json2xml } from \"xml-js\";\n\n...\n\nregisterRoutes(routeHandlers, apiRouter, {\n  serializers: {\n    \"application/xml\": (content) =\u003e {\n      const serialized = json2xml(JSON.stringify(content), {\n        compact: true,\n      });\n      return serialized;\n    },\n  },\n});\n```\n\n#### Accessing the underlying request and response objects\n\nIn some cases, you may need to access the `request` or `response` objects from the underlying HTTP framework.\n\nThese are generic arguments you can access in your handler implementation:\n\n```typescript\nimport type * as ServerTypes from \"./gen/server.ts\";\nimport type { Request, Response } from \"express\";\n\ntype DecoratedRequest = Request \u0026 {\n  userId: string;\n};\n\nconst API: ServerTypes.Server\u003cDecoratedRequest, Response\u003e = {\n  makePetSpeak: async ({ parameters, requestBody, req }) =\u003e {\n    const petId = parameters.path.petId;\n    const sound = requestBody.content.sound;\n\n    console.log(`The userId is: ${req.userId}`);\n\n    return {\n      content: {\n        200: {\n          \"application/json\": {\n            greeting: `Pet ${petId} says \"${sound}\"`,\n          },\n        },\n      },\n    };\n  },\n};\n```\n\n#### Route paths\n\nThe Express middleware will mount paths based on the exact paths in your OpenAPI spec ignoring any base paths in `servers`. You'll need to mount the routes returned from `registerRoutes` to the right base path in your server setup.\n\n#### Error handling\n\nErrors can be handled directly in your route handlers by defining a \"default\" schema for all errors. In the route handler, return the \"default\" response variant and include a `status` code.\n\n```yaml\nopenapi: 3.0.2\ninfo:\n  title: Simple Petstore\n  version: 0.0.1\nservers:\n  - url: /api/v3\npaths:\n  /uhoh:\n    get:\n      operationId: uhoh\n      responses:\n        \"default\":\n          description: unexpected error\n          content:\n            application/json:\n              schema:\n                $ref: \"#/components/schemas/ErrorResponse\"\ncomponents:\n  schemas:\n    ErrorResponse:\n      type: object\n      properties:\n        message:\n          type: string\n```\n\n```typescript\nimport type * as ServerTypes from \"./gen/server.ts\";\nimport type { Request, Response } from \"express\";\n\nconst API: ServerTypes.Server\u003cDecoratedRequest, Response\u003e = {\n  uhoh: async () =\u003e {\n    return {\n      status: 418,\n      content: {\n        default: {\n          \"application/json\": {\n            message: \"This is what it looks like when something goes wrong\",\n          },\n        },\n      },\n    };\n  },\n};\n\nexport default API;\n```\n\nOtherwise, thrown errors will propagate up the call stack to your global error handler. You can check for `err instanceof NotImplementedError` if you want to handle those with a `501 Not Implemented` status code.\n\n## Contributing\n\nPlease see [CONTRIBUTING.md](https://github.com/jasonblanchard/openapi-typescript-server/blob/main/CONTRIBUTING.md) for details on how to contribute to this project.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonblanchard%2Fopenapi-typescript-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjasonblanchard%2Fopenapi-typescript-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonblanchard%2Fopenapi-typescript-server/lists"}