ts-openapi-express 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +264 -1
- package/dist/errors.d.ts +2 -7
- package/dist/errors.js +2 -14
- package/dist/openapiExpress.js +2 -1
- package/package.json +21 -8
package/README.md
CHANGED
|
@@ -1,3 +1,266 @@
|
|
|
1
|
+
**[Documentation](https://jacobshirley.github.io/ts-openapi-express/v1)**
|
|
2
|
+
|
|
1
3
|
# ts-openapi-express
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
An opinionated, type-safe Express.js integration library for OpenAPI specifications. This package enables you to build type-safe REST APIs with full request/response validation using OpenAPI schemas.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/ts-openapi-express)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Type-Safe Routes**: Leverage TypeScript to ensure your route handlers match your OpenAPI spec
|
|
13
|
+
- **Request/Response Validation**: Built-in middleware to validate incoming requests and outgoing responses against your OpenAPI schema
|
|
14
|
+
- **OpenAPI Schema Support**: Load OpenAPI specifications (YAML/JSON) and use them to drive your API implementation
|
|
15
|
+
- **Zero Configuration**: Minimal setup required - just provide your spec and route handlers
|
|
16
|
+
- **Middleware Support**: Integrate custom Express middleware alongside OpenAPI validation
|
|
17
|
+
- **Streaming Support**: Handle streaming responses with native Node.js streams
|
|
18
|
+
|
|
19
|
+
## Getting Started
|
|
20
|
+
|
|
21
|
+
See [examples/](./examples/blog-api) for a complete working example.
|
|
22
|
+
|
|
23
|
+
### 1. Generate TypeScript Types from Your OpenAPI Spec
|
|
24
|
+
|
|
25
|
+
The first step is to generate TypeScript type definitions from your OpenAPI specification using [openapi-typescript](https://openapi-ts.pages.dev/).
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install --save-dev openapi-typescript
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Add this script to your `package.json`:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"scripts": {
|
|
36
|
+
"generate:types": "openapi-typescript path/to/your/openapi.yaml --output path/to/generated/types.ts"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then run it to generate the types:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run generate:types
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It's also recommended to add it to your `compile/build` script to ensure types are always up to date.
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "npm run generate:types && tsc"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This command generates a `types.ts` file with path types that look like:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
export interface paths {
|
|
61
|
+
'/users': {
|
|
62
|
+
get: {
|
|
63
|
+
responses: {
|
|
64
|
+
'200': {
|
|
65
|
+
content: {
|
|
66
|
+
'application/json': {
|
|
67
|
+
id: string
|
|
68
|
+
name: string
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ... more paths
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. Install ts-openapi-express
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install ts-openapi-express
|
|
83
|
+
yarn add ts-openapi-express
|
|
84
|
+
pnpm add ts-openapi-express
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Define Your Routes with Type Safety
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { openapiExpress } from 'ts-openapi-express'
|
|
91
|
+
import { paths } from './generated/types' // Generated from step 1
|
|
92
|
+
|
|
93
|
+
const app = openapiExpress<paths>({
|
|
94
|
+
specPath: './openapi.yaml',
|
|
95
|
+
routes: {
|
|
96
|
+
'/users': {
|
|
97
|
+
get: {
|
|
98
|
+
handler: async (req) => ({
|
|
99
|
+
200: {
|
|
100
|
+
headers: {},
|
|
101
|
+
body: [
|
|
102
|
+
{ id: '1', name: 'John' },
|
|
103
|
+
{ id: '2', name: 'Jane' },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
},
|
|
108
|
+
post: {
|
|
109
|
+
handler: async (req) => ({
|
|
110
|
+
201: {
|
|
111
|
+
headers: { 'content-type': 'application/json' },
|
|
112
|
+
body: { id: '3', name: req.body.name },
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
'/users/{id}': {
|
|
118
|
+
get: {
|
|
119
|
+
handler: async (req) => ({
|
|
120
|
+
200: {
|
|
121
|
+
headers: {},
|
|
122
|
+
body: { id: req.params.id, name: 'John' },
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
app.listen(3000, () => {
|
|
131
|
+
console.log('API running on http://localhost:3000')
|
|
132
|
+
console.log('OpenAPI spec available at http://localhost:3000/openapi.json')
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Examples
|
|
137
|
+
|
|
138
|
+
See the [examples/](./examples/) directory for complete working examples, including a simple blog API.
|
|
139
|
+
|
|
140
|
+
## API Options
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
interface OpenapiExpressOptions<Spec> {
|
|
144
|
+
/**
|
|
145
|
+
* Path to your OpenAPI specification file (YAML or JSON)
|
|
146
|
+
*/
|
|
147
|
+
specPath: string
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Route handlers object mapping paths to HTTP method handlers
|
|
151
|
+
*/
|
|
152
|
+
routes: Routes<Spec>
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Optional Express middleware to use
|
|
156
|
+
*/
|
|
157
|
+
middleware?: ExpressMiddleware[]
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Enable request validation against OpenAPI spec (default: true)
|
|
161
|
+
*/
|
|
162
|
+
validateRequest?: boolean
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Enable response validation against OpenAPI spec (default: true)
|
|
166
|
+
*/
|
|
167
|
+
validateResponse?: boolean
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Automatically parse JSON request bodies (default: true)
|
|
171
|
+
*/
|
|
172
|
+
decodeJsonBody?: boolean
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Provide an existing Express app instance (optional)
|
|
176
|
+
*/
|
|
177
|
+
app?: Application
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Disable the 'X-Powered-By' header (default: true)
|
|
181
|
+
*/
|
|
182
|
+
disableXPoweredBy?: boolean
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Handler Signature
|
|
187
|
+
|
|
188
|
+
Route handlers receive type-safe request objects with:
|
|
189
|
+
|
|
190
|
+
- `req.params`: Path parameters (fully typed)
|
|
191
|
+
- `req.query`: Query parameters (fully typed)
|
|
192
|
+
- `req.body`: Request body (fully typed)
|
|
193
|
+
- `req.headers`: HTTP headers
|
|
194
|
+
|
|
195
|
+
Handlers must return a response object with a single status code:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
{
|
|
199
|
+
200: {
|
|
200
|
+
headers: { 'content-type': 'application/json' },
|
|
201
|
+
body: { /* response data */ }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Or, if no body is required:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
{
|
|
210
|
+
204: {
|
|
211
|
+
headers: {}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Streaming responses are supported by passing a Node.js `Readable`:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
{
|
|
220
|
+
200: {
|
|
221
|
+
headers: { 'content-type': 'text/csv' },
|
|
222
|
+
body: fs.createReadStream('data.csv')
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Examples
|
|
228
|
+
|
|
229
|
+
### Running the Test
|
|
230
|
+
|
|
231
|
+
This repository includes a comprehensive test suite that demonstrates usage. To run it:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# This will:
|
|
235
|
+
# 1. Generate types from the OpenAPI spec
|
|
236
|
+
# 2. Run the test suite with validation
|
|
237
|
+
npm test
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The test file at `packages/ts-openapi-express/test/unit/openapi.test.ts` shows:
|
|
241
|
+
|
|
242
|
+
- Type-safe route definition
|
|
243
|
+
- Request/response validation
|
|
244
|
+
- Error handling
|
|
245
|
+
- Path parameter usage
|
|
246
|
+
- Query parameter handling
|
|
247
|
+
|
|
248
|
+
### Regenerating Test Types
|
|
249
|
+
|
|
250
|
+
If you modify the test OpenAPI spec, regenerate the types:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
npm run test:compile:spec
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Architecture
|
|
257
|
+
|
|
258
|
+
The library works in three main steps:
|
|
259
|
+
|
|
260
|
+
1. **Type Generation** (via openapi-typescript): Convert your OpenAPI spec into TypeScript types
|
|
261
|
+
2. **Route Definition**: Define your Express handlers using the generated types
|
|
262
|
+
3. **Validation**: Automatic request/response validation ensures your implementation matches the spec
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import { error as
|
|
2
|
-
export type OpenapiValidatorError = (typeof openapiValidatorErrors)[keyof typeof openapiValidatorErrors];
|
|
3
|
-
declare const isExpressOpenAPIValidatorError: (err: any) => err is Error & {
|
|
4
|
-
status: number;
|
|
5
|
-
};
|
|
1
|
+
import { error as OpenapiValidationErrors } from 'express-openapi-validator';
|
|
6
2
|
declare class HttpError extends Error {
|
|
7
3
|
readonly status: number;
|
|
8
4
|
constructor(message: string, status: number);
|
|
9
|
-
type(): string;
|
|
10
5
|
}
|
|
11
6
|
declare class UnauthorizedError extends HttpError {
|
|
12
7
|
constructor();
|
|
@@ -18,4 +13,4 @@ declare class InvalidJsonError extends HttpError {
|
|
|
18
13
|
readonly jsonString: string;
|
|
19
14
|
constructor(jsonString: string);
|
|
20
15
|
}
|
|
21
|
-
export {
|
|
16
|
+
export { OpenapiValidationErrors, HttpError, RequestBodyTooLargeError, InvalidJsonError, UnauthorizedError, };
|
package/dist/errors.js
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import { error as
|
|
2
|
-
const isExpressOpenAPIValidatorError = (err) => {
|
|
3
|
-
for (const key in openapiValidatorErrors) {
|
|
4
|
-
if (err instanceof
|
|
5
|
-
openapiValidatorErrors[key]) {
|
|
6
|
-
return true;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
return false;
|
|
10
|
-
};
|
|
1
|
+
import { error as OpenapiValidationErrors } from 'express-openapi-validator';
|
|
11
2
|
class HttpError extends Error {
|
|
12
3
|
status;
|
|
13
4
|
constructor(message, status) {
|
|
14
5
|
super(message);
|
|
15
6
|
this.status = status;
|
|
16
7
|
}
|
|
17
|
-
type() {
|
|
18
|
-
return this.constructor.name;
|
|
19
|
-
}
|
|
20
8
|
}
|
|
21
9
|
class UnauthorizedError extends HttpError {
|
|
22
10
|
constructor() {
|
|
@@ -35,4 +23,4 @@ class InvalidJsonError extends HttpError {
|
|
|
35
23
|
this.jsonString = jsonString;
|
|
36
24
|
}
|
|
37
25
|
}
|
|
38
|
-
export {
|
|
26
|
+
export { OpenapiValidationErrors, HttpError, RequestBodyTooLargeError, InvalidJsonError, UnauthorizedError, };
|
package/dist/openapiExpress.js
CHANGED
|
@@ -15,7 +15,7 @@ function openapiExpress(options) {
|
|
|
15
15
|
if (decodeJsonBody) {
|
|
16
16
|
app.use(json());
|
|
17
17
|
}
|
|
18
|
-
if (validateRequest || validateResponse)
|
|
18
|
+
if (validateRequest || validateResponse) {
|
|
19
19
|
app.use(...requestResponseValidatorMiddleware({
|
|
20
20
|
spec,
|
|
21
21
|
validation: {
|
|
@@ -23,6 +23,7 @@ function openapiExpress(options) {
|
|
|
23
23
|
responses: validateResponse,
|
|
24
24
|
},
|
|
25
25
|
}));
|
|
26
|
+
}
|
|
26
27
|
for (const m of middleware) {
|
|
27
28
|
app.use(m);
|
|
28
29
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-openapi-express",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"directories": {
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
"repository": {
|
|
11
11
|
"url": "https://github.com/jacobshirley/ts-openapi-express"
|
|
12
12
|
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/jacobshirley/ts-openapi-express/issues"
|
|
15
|
+
},
|
|
13
16
|
"homepage": "https://jacobshirley.github.io/ts-openapi-express/v1",
|
|
14
17
|
"keywords": [
|
|
15
18
|
"openapi",
|
|
@@ -28,13 +31,23 @@
|
|
|
28
31
|
"author": "",
|
|
29
32
|
"license": "ISC",
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"express": "^5.0.0",
|
|
32
|
-
"express-openapi-validator": "^5.3.9",
|
|
33
34
|
"lodash-es": "^4.17.23",
|
|
34
|
-
"yaml": "^2.8.1"
|
|
35
|
+
"yaml": "^2.8.1",
|
|
36
|
+
"express-openapi-validator": "^5.0.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"express": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"express": {
|
|
43
|
+
"optional": false
|
|
44
|
+
},
|
|
45
|
+
"express-openapi-validator": {
|
|
46
|
+
"optional": false
|
|
47
|
+
}
|
|
35
48
|
},
|
|
36
49
|
"devDependencies": {
|
|
37
|
-
"@types/express": "^5.0.
|
|
50
|
+
"@types/express": "^5.0.6",
|
|
38
51
|
"@types/lodash-es": "^4.17.12",
|
|
39
52
|
"@types/node": "^22.0.0",
|
|
40
53
|
"@types/supertest": "^6.0.2",
|
|
@@ -50,9 +63,9 @@
|
|
|
50
63
|
"README.md"
|
|
51
64
|
],
|
|
52
65
|
"scripts": {
|
|
53
|
-
"test": "
|
|
54
|
-
"test:unit": "pnpm
|
|
55
|
-
"
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:unit": "pnpm pretest && vitest run",
|
|
68
|
+
"pretest": "openapi-typescript test/unit/openapi.test.yaml --output test/unit/test-schema.gen.ts",
|
|
56
69
|
"compile": "tsc -p tsconfig.prod.json",
|
|
57
70
|
"watch": "tsc -p tsconfig.prod.json --watch"
|
|
58
71
|
}
|