oapi-express-gen
Version:
Code generator to create the interfaces and route handler for an express server from OpenAPI types
354 lines (262 loc) • 10 kB
Markdown
# OpenAPI Express Generator
A code generator that creates fully-typed Express.js handlers from OpenAPI specifications.
I built this package to make code generation resilient to iterative changes in OpenAPI specs. Tools like [`oapi-codegen`](https://github.com/oapi-codegen/oapi-codegen) work well for generating initial server stubs, but once you implement those stubs, re-running codegen overwrites your work. The guiding principle here is that code generation should only produce *interfaces*—which can be safely regenerated—while leaving user implementations intact. This way, developers can evolve their API specs, regenerate code, and then rely on their IDE to surface unimplemented methods or type errors, without losing their existing logic.
## Features
- **Fully Typed**: Generates TypeScript interfaces for request bodies, query parameters, path parameters, and responses
- **Express Integration**: Creates handlers that integrate seamlessly with Express.js
- **OpenAPI 3.0 Support**: Parses OpenAPI 3.0.x specifications
- **Automatic Route Registration**: Includes helper functions to register all generated handlers
- **Test Workspaces**: Multiple test workspaces with snapshot testing
- **Command Line Interface**: Easy-to-use CLI with Commander.js
## Installation
```bash
npm install
```
## Usage
### CLI Interface (Recommended)
The generator now provides a modern CLI interface:
```bash
# Basic usage
oapi-express-gen <source-file>
# With custom output path
oapi-express-gen ./openapi.json -o ./src/handlers.ts
# With verbose logging
oapi-express-gen ./openapi.json -o ./src/handlers.ts -v
# Validate OpenAPI spec without generating code
oapi-express-gen validate ./openapi.json
# Use custom template
oapi-express-gen ./openapi.json -t ./custom-template.hbs
```
#### CLI Options
- `-o, --output <path>`: Output path for generated handlers (default: `./generated/handlers.ts`)
- `-t, --template <path>`: Custom Handlebars template path
- `-v, --verbose`: Enable verbose logging
- `-h, --help`: Show help information
#### CLI Commands
- `generate <source>`: Generate handlers from OpenAPI spec (default command)
- `validate <source>`: Validate OpenAPI specification without generating code
### Legacy Usage
For backward compatibility, you can still use the old method:
```bash
npm run generate <openapi-spec.json> <output-path>
```
Example:
```bash
npm run generate ./openapi.json ./src/generated/handlers.ts
```
### 2. Use Generated Handlers
The generator creates a `Handlers` type that maps operation IDs to fully-typed handler functions:
### 3. Request Parsing with OpenAPI Parser
The generator includes a powerful request parser middleware that automatically parses and validates incoming requests according to your OpenAPI specification:
```typescript
import { openAPIParser } from "oapi-express-gen/parser";
const app = express();
app.use(express.json()); // For parsing JSON bodies
// Apply the OpenAPI parser middleware to all routes
app.use(openAPIParser('./your-openapi-spec.json'));
// Register your handlers - parsing is automatically enabled
import { registerHandlers } from './generated/handlers';
registerHandlers(app, handlers);
```
#### What the Parser Does
- **Path Parameters**: Automatically parses and validates path parameters (e.g., `/users/:userId`)
- **Query Parameters**: Parses query strings with proper type conversion (numbers, booleans, arrays)
- **Request Bodies**: Validates request body structure against OpenAPI schemas
- **Type Safety**: Ensures all parsed values match the expected types from your spec
#### Parser Features
- **Automatic Type Conversion**: Converts string inputs to appropriate types (numbers, booleans, dates)
- **Array Support**: Handles comma-separated arrays and array parameters
- **Validation**: Throws errors for invalid data types or formats
- **Express Middleware**: Drop-in middleware that works with any Express app
#### Example Usage
```typescript
// Your handler will receive parsed and validated data
const handlers: Handlers = {
getUser: (req, res) => {
// req.params contains validated path parameters (automatically parsed)
const userId = req.params.userId; // Already parsed and validated
// req.query contains validated query parameters (automatically converted)
const page = req.query.page; // Already converted to number if valid
res.json({ userId, page });
}
};
```
#### Parser Configuration
You can customize the parser behavior with options:
```typescript
import { openAPIParser, ParseOptions } from "oapi-express-gen/parser";
const options: ParseOptions = {
coerceTypes: true, // Convert types when possible (default: true)
strictNumbers: false, // Strict number validation (default: false)
strictBooleans: false // Strict boolean validation (default: false)
};
// Apply with custom options
app.use(openAPIParser('./your-openapi-spec.json', options));
```
#### Supported Data Types
The parser automatically handles:
- **Strings**: Including email, date-time, date, and UUID formats
- **Numbers**: Integers and floating-point numbers with validation
- **Booleans**: Flexible boolean parsing (true/false, 1/0, yes/no)
- **Arrays**: Comma-separated arrays and array parameters
- **Objects**: Nested object structures from request bodies
### 4. Handler Implementation
```typescript
import { Handlers } from './generated/handlers';
// Implement your handlers
const handlers: Handlers = {
getUser: (req, res) => {
// req.params.userId is fully typed and automatically parsed
// req.query.page is fully typed and automatically converted (if defined)
// req.body is fully typed (if defined)
const userId = req.params.userId; // string (parsed according to OpenAPI spec)
const page = req.query.page; // number | undefined (converted from string)
res.json({ userId, page });
},
createUser: (req, res) => {
// req.body is fully typed with user properties
const { name, email } = req.body;
res.json({ id: '123', name, email });
}
};
// Register handlers with Express
import { registerHandlers } from './generated/handlers';
import express from 'express';
const app = express();
app.use(express.json());
registerHandlers(app, handlers);
```
## Testing
The project includes comprehensive testing to ensure generated code quality and consistency.
### Snapshot Testing
```bash
# Test all workspaces
npm run test:workspaces
# Test specific workspace
cd test/workspace-1
npm run test:snapshot
```
### Type Safety Testing
The generator produces robust TypeScript types that catch common programming errors at compile time. We test this by intentionally introducing type violations and ensuring they fail compilation consistently.
```bash
# Test type safety across all workspaces
npm run test:typesafety
# Test specific workspace type safety
cd test/workspace-1
npm run test:typesafety
npm run test:typesafety-snapshot
```
#### Type Safety Test Coverage
- **Type Mismatches**: String vs number, wrong property types
- **Required Properties**: Missing required fields
- **Additional Properties**: Extra properties when `additionalProperties: false`
- **Path Parameters**: Type safety for URL parameters
- **Query Parameters**: Type safety for query strings
- **Request Bodies**: Type safety for POST/PUT data
- **Response Types**: Type safety for API responses
- **Express Integration**: Proper Request/Response generic typing
#### Snapshot Files
Type safety errors are captured as snapshots to ensure consistency:
- `test/workspace-1/snapshots/typesafety-errors.snapshot.txt`
- `test/workspace-2/snapshots/typesafety-errors.snapshot.txt`
### Demo Script
Run the interactive demo to see type safety testing in action:
```bash
./test/type-safety-demo.sh
```
### All Tests
```bash
# Run all tests (generation + type safety)
npm run test:all
```
## Generated Types
The generator creates several TypeScript interfaces:
### Request Types
- `{operationId}Request` - Extends Express Request with typed params, query, and body
- `{operationId}PathParams` - Path parameter types
- `{operationId}QueryParams` - Query parameter types
- `{operationId}Body` - Request body types
### Response Types
- `{operationId}Response` - Response body types
### Handler Type
- `Handlers` - Dictionary mapping operation IDs to typed handler functions
## Example OpenAPI Specs
### User Management API (Workspace 1)
```yaml
openapi: 3.0.0
info:
title: User Management API
version: 1.0.0
paths:
/users/{userId}:
get:
operationId: getUser
parameters:
- name: userId
in: path
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
userId:
type: string
```
### PetStore API (Workspace 2)
```yaml
openapi: 3.0.0
info:
title: PetStore API
version: 1.0.0
paths:
/pets:
get:
operationId: listPets
responses:
'200':
content:
application/json:
schema:
type: object
properties:
pets:
type: array
items:
type: object
properties:
id:
type: integer
name:
type: string
```
## Development
### Build
```bash
npm run build
```
### Test
```bash
npm test
```
### Generate Test Code
```bash
# Generate for workspace 1
npm run test:generate --workspace=test/workspace-1
# Generate for workspace 2
npm run test:generate --workspace=test/workspace-2
```
### CLI Development
```bash
# Build the CLI
npm run build
# Test the CLI
node dist/cli.js --help
node dist/cli.js validate ./test/workspace-1/example-openapi.json
```
## License
ISC