transit-kit 0.8.1 â 0.9.0
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 +548 -313
- package/dist/server/server.d.ts +0 -4
- package/dist/server/server.js +15 -13
- package/package.json +1 -1
- package/dist/server/middleware/logging.d.ts +0 -4
- package/dist/server/middleware/logging.js +0 -25
package/README.md
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Transit-Kit
|
|
2
2
|
|
|
3
3
|
[](https://github.com/D4rkr34lm/transit-kit/actions/workflows/ci.yml)
|
|
4
4
|
[](https://coveralls.io/github/D4rkr34lm/transit-kit?branch=main)
|
|
5
|
+
[](https://www.npmjs.com/package/transit-kit)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://nodejs.org/)
|
|
5
9
|
|
|
6
|
-
A declarative TypeScript framework for building type-safe Express.js
|
|
10
|
+
A **declarative TypeScript framework** for building end-to-end type-safe REST APIs with Express.js and automatic OpenAPI documentation generation.
|
|
7
11
|
|
|
8
|
-
##
|
|
12
|
+
## Why Transit-Kit?
|
|
9
13
|
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
14
|
+
Transit-Kit brings modern type-safety and developer experience to Express.js without abandoning the familiar, battle-tested foundation you already know. It's built on minimalism:
|
|
15
|
+
|
|
16
|
+
- ðŊ **Express at its core** â Use the familiar Express API you already know
|
|
17
|
+
- ð **End-to-end type safety** â Full TypeScript inference from request to response
|
|
18
|
+
- ð **Auto-generated OpenAPI** â Documentation that stays in sync with your code
|
|
19
|
+
- â
**Zod validation** â Runtime type checking with zero boilerplate
|
|
20
|
+
- ðŠķ **Minimal overhead** â Thin layer over Express, not a complete rewrite
|
|
21
|
+
- **Declarative definitions** â Define endpoints declaratively with full type info
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- **Node.js 22+**
|
|
26
|
+
- **TypeScript 5.9+**
|
|
17
27
|
|
|
18
28
|
## Installation
|
|
19
29
|
|
|
@@ -21,450 +31,675 @@ A declarative TypeScript framework for building type-safe Express.js APIs with a
|
|
|
21
31
|
npm install transit-kit
|
|
22
32
|
```
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
```bash
|
|
35
|
+
yarn add transit-kit
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm add transit-kit
|
|
40
|
+
```
|
|
25
41
|
|
|
26
|
-
|
|
42
|
+
## Quick Start
|
|
27
43
|
|
|
28
44
|
```typescript
|
|
29
45
|
import { createServer } from "transit-kit/server";
|
|
46
|
+
import { createApiEndpointHandler } from "transit-kit/server";
|
|
47
|
+
import { z } from "zod";
|
|
30
48
|
|
|
49
|
+
// Create a server
|
|
31
50
|
const server = createServer({
|
|
32
51
|
port: 3000,
|
|
33
|
-
inDevMode: true,
|
|
34
|
-
logger: true, // or pass a custom logger (console-like interface)
|
|
35
52
|
});
|
|
53
|
+
|
|
54
|
+
// Define a simple endpoint
|
|
55
|
+
const helloEndpoint = createApiEndpointHandler(
|
|
56
|
+
{
|
|
57
|
+
meta: {
|
|
58
|
+
name: "sayHello",
|
|
59
|
+
group: "Greetings",
|
|
60
|
+
description: "Returns a greeting message",
|
|
61
|
+
},
|
|
62
|
+
path: "/hello/:name",
|
|
63
|
+
method: "get",
|
|
64
|
+
responseSchemas: {
|
|
65
|
+
200: {
|
|
66
|
+
type: "json",
|
|
67
|
+
schema: z.object({
|
|
68
|
+
message: z.string(),
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
securitySchemes: [],
|
|
73
|
+
},
|
|
74
|
+
async ({ parameters }) => {
|
|
75
|
+
return {
|
|
76
|
+
code: 200,
|
|
77
|
+
data: {
|
|
78
|
+
message: `Hello, ${parameters.name}!`,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Register and start
|
|
85
|
+
server.registerApiEndpoint(helloEndpoint);
|
|
86
|
+
server.start();
|
|
36
87
|
```
|
|
37
88
|
|
|
38
|
-
|
|
89
|
+
## Core Concepts
|
|
90
|
+
|
|
91
|
+
### 1. Endpoint Definitions
|
|
92
|
+
|
|
93
|
+
Every API endpoint in Transit-Kit is defined declaratively with full type information:
|
|
39
94
|
|
|
40
95
|
```typescript
|
|
41
|
-
|
|
42
|
-
|
|
96
|
+
const definition = {
|
|
97
|
+
meta: {
|
|
98
|
+
name: "createUser", // Unique operation ID
|
|
99
|
+
group: "Users", // OpenAPI tag
|
|
100
|
+
description: "Create a user" // Endpoint description
|
|
101
|
+
},
|
|
102
|
+
path: "/users", // Express-style path
|
|
103
|
+
method: "post", // HTTP method
|
|
104
|
+
requestBodySchema: z.object({ // Request validation (optional)
|
|
105
|
+
name: z.string(),
|
|
106
|
+
email: z.string().email(),
|
|
107
|
+
}),
|
|
108
|
+
querySchema: z.object({ // Query params validation (optional)
|
|
109
|
+
sendEmail: z.boolean().optional(),
|
|
110
|
+
}),
|
|
111
|
+
responseSchemas: { // All possible responses
|
|
112
|
+
201: {
|
|
113
|
+
type: "json",
|
|
114
|
+
schema: z.object({
|
|
115
|
+
id: z.string(),
|
|
116
|
+
name: z.string(),
|
|
117
|
+
email: z.string(),
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
400: {
|
|
121
|
+
type: "json",
|
|
122
|
+
schema: z.object({
|
|
123
|
+
error: z.string(),
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
securitySchemes: [], // Auth schemes (if any)
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 2. Type-Safe Handlers
|
|
132
|
+
|
|
133
|
+
Handlers automatically infer types from your endpoint definition:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
const handler = async ({ body, query, parameters }) => {
|
|
137
|
+
// body is typed as { name: string, email: string }
|
|
138
|
+
// query is typed as { sendEmail?: boolean }
|
|
139
|
+
// parameters are inferred from the path pattern
|
|
140
|
+
|
|
141
|
+
const user = await createUserInDatabase(body);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
code: 201,
|
|
145
|
+
data: user, // Must match the response schema for code 201
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 3. Server Registration
|
|
151
|
+
|
|
152
|
+
Combine definitions and handlers, then register with your server:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const endpoint = createApiEndpointHandler(definition, handler);
|
|
156
|
+
server.registerApiEndpoint(endpoint);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Example: CRUD API
|
|
160
|
+
|
|
161
|
+
Here's a complete example showing a REST API for managing todos:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { createServer, createApiEndpointHandler } from "transit-kit/server";
|
|
165
|
+
import { z } from "zod";
|
|
166
|
+
|
|
167
|
+
const server = createServer({
|
|
168
|
+
port: 3000,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// In-memory storage (use a real database in production)
|
|
172
|
+
const todos: Map<string, { id: string; title: string; completed: boolean }> = new Map();
|
|
173
|
+
|
|
174
|
+
// Schemas
|
|
175
|
+
const TodoSchema = z.object({
|
|
176
|
+
id: z.string(),
|
|
177
|
+
title: z.string(),
|
|
178
|
+
completed: z.boolean(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const CreateTodoSchema = z.object({
|
|
182
|
+
title: z.string().min(1),
|
|
183
|
+
});
|
|
43
184
|
|
|
44
|
-
//
|
|
45
|
-
const
|
|
185
|
+
// CREATE
|
|
186
|
+
const createTodo = createApiEndpointHandler(
|
|
46
187
|
{
|
|
47
188
|
meta: {
|
|
48
|
-
name: "
|
|
49
|
-
|
|
50
|
-
|
|
189
|
+
name: "createTodo",
|
|
190
|
+
group: "Todos",
|
|
191
|
+
description: "Create a new todo item",
|
|
51
192
|
},
|
|
193
|
+
path: "/todos",
|
|
194
|
+
method: "post",
|
|
195
|
+
requestBodySchema: CreateTodoSchema,
|
|
196
|
+
responseSchemas: {
|
|
197
|
+
201: {
|
|
198
|
+
type: "json",
|
|
199
|
+
schema: TodoSchema,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
securitySchemes: [],
|
|
203
|
+
},
|
|
204
|
+
async ({ body }) => {
|
|
205
|
+
const id = crypto.randomUUID();
|
|
206
|
+
const todo = {
|
|
207
|
+
id,
|
|
208
|
+
title: body.title,
|
|
209
|
+
completed: false,
|
|
210
|
+
};
|
|
211
|
+
todos.set(id, todo);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
code: 201,
|
|
215
|
+
data: todo,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// READ (List)
|
|
221
|
+
const listTodos = createApiEndpointHandler(
|
|
222
|
+
{
|
|
223
|
+
meta: {
|
|
224
|
+
name: "listTodos",
|
|
225
|
+
group: "Todos",
|
|
226
|
+
description: "Get all todo items",
|
|
227
|
+
},
|
|
228
|
+
path: "/todos",
|
|
52
229
|
method: "get",
|
|
53
|
-
path: "/users/:userId",
|
|
54
230
|
responseSchemas: {
|
|
55
231
|
200: {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
name: z.string(),
|
|
60
|
-
email: z.string().email(),
|
|
232
|
+
type: "json",
|
|
233
|
+
schema: z.object({
|
|
234
|
+
todos: z.array(TodoSchema),
|
|
61
235
|
}),
|
|
62
236
|
},
|
|
63
|
-
404: {}, // Empty response
|
|
64
237
|
},
|
|
238
|
+
securitySchemes: [],
|
|
65
239
|
},
|
|
66
|
-
async (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
240
|
+
async () => {
|
|
241
|
+
return {
|
|
242
|
+
code: 200,
|
|
243
|
+
data: {
|
|
244
|
+
todos: Array.from(todos.values()),
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
);
|
|
71
249
|
|
|
72
|
-
|
|
250
|
+
// READ (Single)
|
|
251
|
+
const getTodo = createApiEndpointHandler(
|
|
252
|
+
{
|
|
253
|
+
meta: {
|
|
254
|
+
name: "getTodo",
|
|
255
|
+
group: "Todos",
|
|
256
|
+
description: "Get a specific todo item",
|
|
257
|
+
},
|
|
258
|
+
path: "/todos/:id",
|
|
259
|
+
method: "get",
|
|
260
|
+
responseSchemas: {
|
|
261
|
+
200: {
|
|
262
|
+
type: "json",
|
|
263
|
+
schema: TodoSchema,
|
|
264
|
+
},
|
|
265
|
+
404: {
|
|
266
|
+
type: "json",
|
|
267
|
+
schema: z.object({ error: z.string() }),
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
securitySchemes: [],
|
|
271
|
+
},
|
|
272
|
+
async ({ parameters }) => {
|
|
273
|
+
const todo = todos.get(parameters.id);
|
|
274
|
+
|
|
275
|
+
if (!todo) {
|
|
73
276
|
return {
|
|
74
277
|
code: 404,
|
|
278
|
+
data: { error: "Todo not found" },
|
|
75
279
|
};
|
|
76
280
|
}
|
|
77
281
|
|
|
78
282
|
return {
|
|
79
283
|
code: 200,
|
|
80
|
-
|
|
81
|
-
json: user,
|
|
284
|
+
data: todo,
|
|
82
285
|
};
|
|
83
|
-
}
|
|
286
|
+
}
|
|
84
287
|
);
|
|
85
|
-
```
|
|
86
288
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
const createUserEndpoint = createApiEndpointHandler(
|
|
289
|
+
// UPDATE
|
|
290
|
+
const updateTodo = createApiEndpointHandler(
|
|
91
291
|
{
|
|
92
292
|
meta: {
|
|
93
|
-
name: "
|
|
94
|
-
|
|
95
|
-
|
|
293
|
+
name: "updateTodo",
|
|
294
|
+
group: "Todos",
|
|
295
|
+
description: "Update a todo item",
|
|
96
296
|
},
|
|
97
|
-
|
|
98
|
-
|
|
297
|
+
path: "/todos/:id",
|
|
298
|
+
method: "put",
|
|
99
299
|
requestBodySchema: z.object({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
age: z.number().min(18),
|
|
103
|
-
}),
|
|
104
|
-
querySchema: z.object({
|
|
105
|
-
notify: z.boolean().optional(),
|
|
300
|
+
title: z.string().optional(),
|
|
301
|
+
completed: z.boolean().optional(),
|
|
106
302
|
}),
|
|
107
303
|
responseSchemas: {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
id: z.string(),
|
|
112
|
-
name: z.string(),
|
|
113
|
-
email: z.string().email(),
|
|
114
|
-
}),
|
|
304
|
+
200: {
|
|
305
|
+
type: "json",
|
|
306
|
+
schema: TodoSchema,
|
|
115
307
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
error: z.string(),
|
|
120
|
-
}),
|
|
308
|
+
404: {
|
|
309
|
+
type: "json",
|
|
310
|
+
schema: z.object({ error: z.string() }),
|
|
121
311
|
},
|
|
122
312
|
},
|
|
313
|
+
securitySchemes: [],
|
|
123
314
|
},
|
|
124
|
-
async (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
315
|
+
async ({ parameters, body }) => {
|
|
316
|
+
const todo = todos.get(parameters.id);
|
|
317
|
+
|
|
318
|
+
if (!todo) {
|
|
319
|
+
return {
|
|
320
|
+
code: 404,
|
|
321
|
+
data: { error: "Todo not found" },
|
|
322
|
+
};
|
|
323
|
+
}
|
|
128
324
|
|
|
129
|
-
|
|
130
|
-
|
|
325
|
+
const updated = {
|
|
326
|
+
...todo,
|
|
327
|
+
...body,
|
|
328
|
+
};
|
|
329
|
+
todos.set(parameters.id, updated);
|
|
131
330
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
331
|
+
return {
|
|
332
|
+
code: 200,
|
|
333
|
+
data: updated,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
);
|
|
135
337
|
|
|
338
|
+
// DELETE
|
|
339
|
+
const deleteTodo = createApiEndpointHandler(
|
|
340
|
+
{
|
|
341
|
+
meta: {
|
|
342
|
+
name: "deleteTodo",
|
|
343
|
+
group: "Todos",
|
|
344
|
+
description: "Delete a todo item",
|
|
345
|
+
},
|
|
346
|
+
path: "/todos/:id",
|
|
347
|
+
method: "delete",
|
|
348
|
+
responseSchemas: {
|
|
349
|
+
204: {
|
|
350
|
+
type: "json",
|
|
351
|
+
schema: z.object({}),
|
|
352
|
+
},
|
|
353
|
+
404: {
|
|
354
|
+
type: "json",
|
|
355
|
+
schema: z.object({ error: z.string() }),
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
securitySchemes: [],
|
|
359
|
+
},
|
|
360
|
+
async ({ parameters }) => {
|
|
361
|
+
const exists = todos.has(parameters.id);
|
|
362
|
+
|
|
363
|
+
if (!exists) {
|
|
136
364
|
return {
|
|
137
|
-
code:
|
|
138
|
-
|
|
139
|
-
json: newUser,
|
|
140
|
-
};
|
|
141
|
-
} catch (error) {
|
|
142
|
-
return {
|
|
143
|
-
code: 400,
|
|
144
|
-
dataType: "application/json",
|
|
145
|
-
json: { error: error.message },
|
|
365
|
+
code: 404,
|
|
366
|
+
data: { error: "Todo not found" },
|
|
146
367
|
};
|
|
147
368
|
}
|
|
148
|
-
},
|
|
149
|
-
);
|
|
150
|
-
```
|
|
151
369
|
|
|
152
|
-
|
|
370
|
+
todos.delete(parameters.id);
|
|
153
371
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
372
|
+
return {
|
|
373
|
+
code: 204,
|
|
374
|
+
data: {},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Register all endpoints
|
|
380
|
+
server.registerApiEndpoint(createTodo);
|
|
381
|
+
server.registerApiEndpoint(listTodos);
|
|
382
|
+
server.registerApiEndpoint(getTodo);
|
|
383
|
+
server.registerApiEndpoint(updateTodo);
|
|
384
|
+
server.registerApiEndpoint(deleteTodo);
|
|
157
385
|
|
|
158
386
|
server.start();
|
|
387
|
+
console.log("Server running on http://localhost:3000");
|
|
159
388
|
```
|
|
160
389
|
|
|
161
390
|
## Authentication
|
|
162
391
|
|
|
163
|
-
Transit-
|
|
392
|
+
Transit-Kit supports **Basic** and **Bearer** authentication schemes out of the box.
|
|
164
393
|
|
|
165
|
-
|
|
166
|
-
- Try all declared schemes in order and pick the first successful one
|
|
167
|
-
- Return `401 Unauthorized` automatically when authentication fails
|
|
168
|
-
- Inject the authenticated `caller` into your endpoint handler
|
|
394
|
+
### Basic Authentication
|
|
169
395
|
|
|
170
|
-
|
|
396
|
+
```typescript
|
|
397
|
+
import { createBasicAuthSchema } from "transit-kit/server";
|
|
171
398
|
|
|
172
|
-
|
|
399
|
+
// Define your user type
|
|
400
|
+
interface User {
|
|
401
|
+
id: string;
|
|
402
|
+
username: string;
|
|
403
|
+
role: string;
|
|
404
|
+
}
|
|
173
405
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
createServer,
|
|
178
|
-
} from "transit-kit/server";
|
|
179
|
-
import { createBasicAuthSchema } from "transit-kit/server/security/basicAuth";
|
|
180
|
-
import z from "zod";
|
|
181
|
-
|
|
182
|
-
// Define the shape of the authenticated caller
|
|
183
|
-
type Caller = { id: string; role: "admin" | "user" };
|
|
184
|
-
|
|
185
|
-
// Your validation logic (DB lookup, etc.)
|
|
186
|
-
const basicAuth = createBasicAuthSchema<Caller>(
|
|
187
|
-
"basic",
|
|
406
|
+
// Create an auth scheme
|
|
407
|
+
const basicAuth = createBasicAuthSchema<User>(
|
|
408
|
+
"basicAuth",
|
|
188
409
|
async (username, password) => {
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
method: "get",
|
|
200
|
-
path: "/me",
|
|
201
|
-
responseSchemas: {
|
|
202
|
-
200: {
|
|
203
|
-
dataType: "application/json",
|
|
204
|
-
dataSchema: z.object({ id: z.string(), role: z.enum(["admin", "user"]) }),
|
|
205
|
-
},
|
|
206
|
-
401: {},
|
|
207
|
-
},
|
|
208
|
-
// Enable authentication for this endpoint
|
|
209
|
-
securitySchemes: [basicAuth],
|
|
210
|
-
},
|
|
211
|
-
async (_req, { caller }) => {
|
|
212
|
-
// `caller` is typed from your security scheme
|
|
213
|
-
return { code: 200, dataType: "application/json", json: caller };
|
|
214
|
-
},
|
|
410
|
+
// Validate credentials (use your database)
|
|
411
|
+
if (username === "admin" && password === "secret") {
|
|
412
|
+
return {
|
|
413
|
+
id: "1",
|
|
414
|
+
username: "admin",
|
|
415
|
+
role: "admin",
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
return null; // Invalid credentials
|
|
419
|
+
}
|
|
215
420
|
);
|
|
216
|
-
|
|
217
|
-
const server = createServer({ port: 3000, inDevMode: true, logger: true });
|
|
218
|
-
server.registerApiEndpoint(getProfile);
|
|
219
|
-
server.start();
|
|
220
421
|
```
|
|
221
422
|
|
|
222
|
-
### Bearer
|
|
223
|
-
|
|
224
|
-
Supports `Authorization: Bearer <token>`.
|
|
423
|
+
### Bearer Authentication
|
|
225
424
|
|
|
226
425
|
```typescript
|
|
227
|
-
import { createBearerAuthSchema } from "transit-kit/server
|
|
228
|
-
|
|
229
|
-
type Caller = { id: string; scopes: string[] };
|
|
426
|
+
import { createBearerAuthSchema } from "transit-kit/server";
|
|
230
427
|
|
|
231
|
-
const bearerAuth = createBearerAuthSchema<
|
|
232
|
-
"
|
|
428
|
+
const bearerAuth = createBearerAuthSchema<User>(
|
|
429
|
+
"bearerAuth",
|
|
233
430
|
async (token) => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
431
|
+
// Validate token (e.g., JWT verification)
|
|
432
|
+
const user = await verifyJWT(token);
|
|
433
|
+
return user; // or null if invalid
|
|
434
|
+
}
|
|
237
435
|
);
|
|
436
|
+
```
|
|
238
437
|
|
|
239
|
-
|
|
438
|
+
### Protected Endpoints
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
const protectedEndpoint = createApiEndpointHandler(
|
|
240
442
|
{
|
|
241
|
-
meta: {
|
|
443
|
+
meta: {
|
|
444
|
+
name: "getProfile",
|
|
445
|
+
group: "Users",
|
|
446
|
+
description: "Get current user profile",
|
|
447
|
+
},
|
|
448
|
+
path: "/profile",
|
|
242
449
|
method: "get",
|
|
243
|
-
path: "/secret",
|
|
244
450
|
responseSchemas: {
|
|
245
|
-
200: {
|
|
246
|
-
|
|
451
|
+
200: {
|
|
452
|
+
type: "json",
|
|
453
|
+
schema: z.object({
|
|
454
|
+
id: z.string(),
|
|
455
|
+
username: z.string(),
|
|
456
|
+
role: z.string(),
|
|
457
|
+
}),
|
|
458
|
+
},
|
|
459
|
+
401: {
|
|
460
|
+
type: "json",
|
|
461
|
+
schema: z.object({ error: z.string() }),
|
|
462
|
+
},
|
|
247
463
|
},
|
|
248
|
-
securitySchemes: [
|
|
249
|
-
},
|
|
250
|
-
async (_req, { caller }) => {
|
|
251
|
-
// Use `caller.scopes` for authorization decisions
|
|
252
|
-
return { code: 200, dataType: "application/json", json: { secret: "shh" } };
|
|
464
|
+
securitySchemes: [basicAuth], // Require authentication
|
|
253
465
|
},
|
|
466
|
+
async ({ caller }) => {
|
|
467
|
+
// caller is typed as User | null
|
|
468
|
+
if (!caller) {
|
|
469
|
+
return {
|
|
470
|
+
code: 401,
|
|
471
|
+
data: { error: "Unauthorized" },
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
code: 200,
|
|
477
|
+
data: caller, // Fully typed user object
|
|
478
|
+
};
|
|
479
|
+
}
|
|
254
480
|
);
|
|
481
|
+
|
|
482
|
+
server.registerApiEndpoint(protectedEndpoint);
|
|
255
483
|
```
|
|
256
484
|
|
|
257
|
-
### Multiple Schemes
|
|
485
|
+
### Multiple Auth Schemes
|
|
258
486
|
|
|
259
|
-
You can
|
|
487
|
+
You can support multiple authentication methods:
|
|
260
488
|
|
|
261
489
|
```typescript
|
|
262
490
|
const endpoint = createApiEndpointHandler(
|
|
263
491
|
{
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
path: "/hybrid",
|
|
267
|
-
responseSchemas: { 200: { dataType: "application/json", dataSchema: z.object({ ok: z.boolean() }) }, 401: {} },
|
|
268
|
-
securitySchemes: [basicAuth, bearerAuth],
|
|
269
|
-
},
|
|
270
|
-
async (_req, { caller }) => {
|
|
271
|
-
// `caller` resolves from whichever scheme authenticated successfully
|
|
272
|
-
return { code: 200, dataType: "application/json", json: { ok: true } };
|
|
492
|
+
// ... other config
|
|
493
|
+
securitySchemes: [basicAuth, bearerAuth], // Accept either
|
|
273
494
|
},
|
|
495
|
+
async ({ caller }) => {
|
|
496
|
+
// caller will be set if any scheme succeeds
|
|
497
|
+
// ...
|
|
498
|
+
}
|
|
274
499
|
);
|
|
275
500
|
```
|
|
276
501
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
- Authentication middleware is built automatically per-endpoint when `securitySchemes` are present.
|
|
280
|
-
- Internals use `authenticate()` from [src/server/security/SecuritySchema.ts](src/server/security/SecuritySchema.ts) to try each scheme.
|
|
281
|
-
- Basic auth reads and decodes the header in [src/server/security/basicAuth.ts](src/server/security/basicAuth.ts).
|
|
282
|
-
- Bearer auth reads the header in [src/server/security/bearerAuth.ts](src/server/security/bearerAuth.ts).
|
|
283
|
-
- On success, the authenticated `caller` is stored and passed to your handler as `extractedRequestData.caller`.
|
|
284
|
-
- On failure, the framework responds with `401` and a JSON `{ message: "Unauthorized" }` from [src/server/middleware/auth.ts](src/server/middleware/auth.ts).
|
|
502
|
+
## OpenAPI Documentation
|
|
285
503
|
|
|
286
|
-
|
|
504
|
+
Transit-Kit automatically generates OpenAPI 3.0 documentation from your endpoint definitions.
|
|
287
505
|
|
|
288
|
-
|
|
506
|
+
```typescript
|
|
507
|
+
import { generateOpenApiDoc } from "transit-kit/generator";
|
|
508
|
+
|
|
509
|
+
const openApiDoc = await generateOpenApiDoc(server, {
|
|
510
|
+
title: "My API",
|
|
511
|
+
version: "1.0.0",
|
|
512
|
+
description: "A type-safe REST API built with Transit-Kit",
|
|
513
|
+
servers: [
|
|
514
|
+
{
|
|
515
|
+
url: "http://localhost:3000",
|
|
516
|
+
description: "Development server",
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
url: "https://api.example.com",
|
|
520
|
+
description: "Production server",
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
contact: {
|
|
524
|
+
name: "API Support",
|
|
525
|
+
email: "support@example.com",
|
|
526
|
+
},
|
|
527
|
+
license: {
|
|
528
|
+
name: "MIT",
|
|
529
|
+
url: "https://opensource.org/licenses/MIT",
|
|
530
|
+
},
|
|
531
|
+
});
|
|
289
532
|
|
|
290
|
-
|
|
533
|
+
// Serve the OpenAPI spec
|
|
534
|
+
server.expressApp.get("/openapi.json", (req, res) => {
|
|
535
|
+
res.json(openApiDoc);
|
|
536
|
+
});
|
|
291
537
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
dataType: "application/json",
|
|
296
|
-
json: { message: "Success", data: myData },
|
|
297
|
-
};
|
|
538
|
+
// Or write to file
|
|
539
|
+
import { writeFileSync } from "fs";
|
|
540
|
+
writeFileSync("./openapi.json", JSON.stringify(openApiDoc, null, 2));
|
|
298
541
|
```
|
|
299
542
|
|
|
300
|
-
|
|
543
|
+
The generated OpenAPI document includes:
|
|
544
|
+
- All endpoints with request/response schemas
|
|
545
|
+
- Path and query parameters
|
|
546
|
+
- Request body validation schemas
|
|
547
|
+
- Security requirements
|
|
548
|
+
- Response schemas for all status codes
|
|
301
549
|
|
|
302
|
-
|
|
550
|
+
You can use this with tools like:
|
|
551
|
+
- **Swagger UI** for interactive documentation
|
|
552
|
+
- **Postman** for API testing
|
|
553
|
+
- **OpenAPI Generator** for client SDK generation
|
|
303
554
|
|
|
304
|
-
|
|
305
|
-
return {
|
|
306
|
-
code: 204,
|
|
307
|
-
};
|
|
308
|
-
```
|
|
555
|
+
## API Reference
|
|
309
556
|
|
|
310
|
-
|
|
557
|
+
### Server
|
|
311
558
|
|
|
312
|
-
|
|
559
|
+
#### `createServer(config: ServerConfig): Server`
|
|
313
560
|
|
|
314
|
-
|
|
561
|
+
Creates a new Transit-Kit server instance.
|
|
315
562
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
```
|
|
563
|
+
**Config Options:**
|
|
564
|
+
- `port: number` â Port to listen on
|
|
319
565
|
|
|
320
|
-
|
|
566
|
+
**Returns:** Server instance with:
|
|
567
|
+
- `expressApp: Application` â Underlying Express app
|
|
568
|
+
- `registerApiEndpoint(endpoint)` â Register an API endpoint
|
|
569
|
+
- `start()` â Start the server
|
|
570
|
+
- `endpointDefinitions` â Array of registered endpoint definitions
|
|
321
571
|
|
|
322
|
-
|
|
323
|
-
- `-t, --target <path>`: Target path to search for endpoint definitions (default: `.`)
|
|
572
|
+
### Endpoint Creation
|
|
324
573
|
|
|
325
|
-
|
|
574
|
+
#### `createApiEndpointHandler(definition, handler)`
|
|
326
575
|
|
|
327
|
-
|
|
328
|
-
import { generateOpenApiDoc } from "transit-kit/cli";
|
|
576
|
+
Creates a type-safe API endpoint.
|
|
329
577
|
|
|
330
|
-
|
|
331
|
-
|
|
578
|
+
**Parameters:**
|
|
579
|
+
- `definition` â Endpoint definition object
|
|
580
|
+
- `handler` â Async function handling the request
|
|
332
581
|
|
|
333
|
-
|
|
582
|
+
**Handler receives:**
|
|
583
|
+
- `request` â Express Request object
|
|
584
|
+
- `response` â Express Response object
|
|
585
|
+
- `parameters` â Type-safe path parameters
|
|
586
|
+
- `query` â Type-safe query parameters
|
|
587
|
+
- `body` â Type-safe request body
|
|
588
|
+
- `caller` â Authenticated user (if auth is enabled)
|
|
334
589
|
|
|
335
|
-
|
|
336
|
-
- Request/response schemas
|
|
337
|
-
- Path parameters
|
|
338
|
-
- Query parameters
|
|
339
|
-
- Request body schemas
|
|
340
|
-
- Response schemas for all status codes
|
|
590
|
+
### Authentication
|
|
341
591
|
|
|
342
|
-
|
|
592
|
+
#### `createBasicAuthSchema<Caller>(name, validateCaller)`
|
|
343
593
|
|
|
344
|
-
|
|
594
|
+
Creates a Basic authentication scheme.
|
|
345
595
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
port: number; // Port to listen on
|
|
350
|
-
logger: Logger | boolean; // Logger instance or boolean to enable/disable
|
|
351
|
-
}
|
|
352
|
-
```
|
|
596
|
+
**Parameters:**
|
|
597
|
+
- `name: string` â Unique name for the scheme
|
|
598
|
+
- `validateCaller: (username, password) => Promise<Caller | null>` â Validation function
|
|
353
599
|
|
|
354
|
-
|
|
600
|
+
#### `createBearerAuthSchema<Caller>(name, validateCaller)`
|
|
355
601
|
|
|
356
|
-
|
|
602
|
+
Creates a Bearer token authentication scheme.
|
|
357
603
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
/* custom logging */
|
|
362
|
-
},
|
|
363
|
-
error: (message: string) => {
|
|
364
|
-
/* custom error logging */
|
|
365
|
-
},
|
|
366
|
-
// ... other console methods
|
|
367
|
-
};
|
|
604
|
+
**Parameters:**
|
|
605
|
+
- `name: string` â Unique name for the scheme
|
|
606
|
+
- `validateCaller: (token) => Promise<Caller | null>` â Validation function
|
|
368
607
|
|
|
369
|
-
|
|
370
|
-
port: 3000,
|
|
371
|
-
inDevMode: false,
|
|
372
|
-
logger: customLogger,
|
|
373
|
-
});
|
|
374
|
-
```
|
|
608
|
+
### OpenAPI Generation
|
|
375
609
|
|
|
376
|
-
|
|
610
|
+
#### `generateOpenApiDoc(server, options): Promise<OpenAPIV3.Document>`
|
|
377
611
|
|
|
378
|
-
|
|
612
|
+
Generates OpenAPI 3.0 documentation.
|
|
379
613
|
|
|
380
|
-
|
|
614
|
+
**Parameters:**
|
|
615
|
+
- `server: Server` â Your Transit-Kit server instance
|
|
616
|
+
- `options: GeneratorOptions` â OpenAPI metadata
|
|
381
617
|
|
|
382
|
-
|
|
618
|
+
**Options:**
|
|
619
|
+
- `title: string` â API title
|
|
620
|
+
- `version: string` â API version
|
|
621
|
+
- `description?: string` â API description
|
|
622
|
+
- `servers?: ServerObject[]` â Server URLs
|
|
623
|
+
- `contact?: ContactObject` â Contact information
|
|
624
|
+
- `license?: LicenseObject` â License information
|
|
383
625
|
|
|
384
|
-
|
|
626
|
+
## Response Types
|
|
385
627
|
|
|
386
|
-
|
|
628
|
+
Transit-Kit currently supports JSON responses:
|
|
387
629
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
-
|
|
393
|
-
|
|
630
|
+
```typescript
|
|
631
|
+
{
|
|
632
|
+
type: "json",
|
|
633
|
+
schema: z.object({ /* your schema */ }),
|
|
634
|
+
headers?: ["X-Custom-Header"], // Optional custom headers
|
|
635
|
+
}
|
|
636
|
+
```
|
|
394
637
|
|
|
395
|
-
|
|
638
|
+
When using custom headers in your response schema, you must include them in the handler response:
|
|
396
639
|
|
|
397
|
-
|
|
640
|
+
```typescript
|
|
641
|
+
return {
|
|
642
|
+
code: 200,
|
|
643
|
+
data: { /* ... */ },
|
|
644
|
+
headers: {
|
|
645
|
+
"X-Custom-Header": "value",
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
```
|
|
398
649
|
|
|
399
|
-
|
|
650
|
+
## Accessing Express Features
|
|
400
651
|
|
|
401
|
-
|
|
652
|
+
Since Transit-Kit is built on Express, you can access the underlying Express app:
|
|
402
653
|
|
|
403
|
-
|
|
654
|
+
```typescript
|
|
655
|
+
const server = createServer({ /* ... */ });
|
|
404
656
|
|
|
405
|
-
|
|
657
|
+
// Add custom middleware
|
|
658
|
+
server.expressApp.use(cors());
|
|
659
|
+
server.expressApp.use(helmet());
|
|
406
660
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
import z from "zod";
|
|
661
|
+
// Static files
|
|
662
|
+
server.expressApp.use(express.static("public"));
|
|
410
663
|
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
inDevMode: true,
|
|
415
|
-
logger: true,
|
|
664
|
+
// Custom routes
|
|
665
|
+
server.expressApp.get("/health", (req, res) => {
|
|
666
|
+
res.json({ status: "ok" });
|
|
416
667
|
});
|
|
668
|
+
```
|
|
417
669
|
|
|
418
|
-
|
|
419
|
-
const listUsersEndpoint = createApiEndpointHandler(
|
|
420
|
-
{
|
|
421
|
-
meta: {
|
|
422
|
-
name: "List Users",
|
|
423
|
-
description: "Get a list of all users",
|
|
424
|
-
group: "Users",
|
|
425
|
-
},
|
|
426
|
-
method: "get",
|
|
427
|
-
path: "/users",
|
|
428
|
-
querySchema: z.object({
|
|
429
|
-
page: z.number().optional(),
|
|
430
|
-
limit: z.number().optional(),
|
|
431
|
-
}),
|
|
432
|
-
responseSchemas: {
|
|
433
|
-
200: {
|
|
434
|
-
dataType: "application/json",
|
|
435
|
-
dataSchema: z.array(
|
|
436
|
-
z.object({
|
|
437
|
-
id: z.string(),
|
|
438
|
-
name: z.string(),
|
|
439
|
-
}),
|
|
440
|
-
),
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
async (request) => {
|
|
445
|
-
const { page = 1, limit = 10 } = request.query;
|
|
446
|
-
const users = await fetchUsers(page, limit);
|
|
670
|
+
## Project Structure
|
|
447
671
|
|
|
448
|
-
|
|
449
|
-
code: 200,
|
|
450
|
-
dataType: "application/json",
|
|
451
|
-
json: users,
|
|
452
|
-
};
|
|
453
|
-
},
|
|
454
|
-
);
|
|
672
|
+
Transit-Kit exports two main modules:
|
|
455
673
|
|
|
456
|
-
server
|
|
457
|
-
|
|
674
|
+
- **`transit-kit/server`** â Server creation, endpoint handlers, and authentication
|
|
675
|
+
- **`transit-kit/generator`** â OpenAPI documentation generation
|
|
676
|
+
|
|
677
|
+
## TypeScript Configuration
|
|
678
|
+
|
|
679
|
+
For the best experience, ensure your `tsconfig.json` includes:
|
|
680
|
+
|
|
681
|
+
```json
|
|
682
|
+
{
|
|
683
|
+
"compilerOptions": {
|
|
684
|
+
"strict": true,
|
|
685
|
+
"esModuleInterop": true,
|
|
686
|
+
"moduleResolution": "node",
|
|
687
|
+
"target": "ES2022",
|
|
688
|
+
"module": "ES2022"
|
|
689
|
+
}
|
|
690
|
+
}
|
|
458
691
|
```
|
|
459
692
|
|
|
460
693
|
## License
|
|
461
694
|
|
|
462
|
-
MIT
|
|
695
|
+
MIT ÂĐ [D4rkr34lm](https://github.com/D4rkr34lm)
|
|
463
696
|
|
|
464
697
|
## Contributing
|
|
465
698
|
|
|
466
699
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
467
700
|
|
|
468
|
-
##
|
|
701
|
+
## Links
|
|
469
702
|
|
|
470
|
-
D4rkr34lm
|
|
703
|
+
- [GitHub Repository](https://github.com/D4rkr34lm/transit-kit)
|
|
704
|
+
- [npm Package](https://www.npmjs.com/package/transit-kit)
|
|
705
|
+
- [Report Issues](https://github.com/D4rkr34lm/transit-kit/issues)
|
package/dist/server/server.d.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { Application } from "express";
|
|
2
2
|
import { ApiEndpointDefinition } from "./handlers/api/EndpointDefinition.js";
|
|
3
3
|
import { HandlerForDefinition } from "./handlers/api/HandlerFromDefinition.js";
|
|
4
|
-
import { Logger } from "./utils/logging.js";
|
|
5
4
|
export interface ServerConfig {
|
|
6
|
-
inDevMode: boolean;
|
|
7
5
|
port: number;
|
|
8
|
-
logger: Logger | boolean;
|
|
9
6
|
}
|
|
10
7
|
export interface Server {
|
|
11
8
|
expressApp: Application;
|
|
12
|
-
logger: Logger | boolean;
|
|
13
9
|
endpointDefinitions: ApiEndpointDefinition[];
|
|
14
10
|
registerApiEndpoint<Definition extends ApiEndpointDefinition>({ definition, handler, }: {
|
|
15
11
|
definition: Definition;
|
package/dist/server/server.js
CHANGED
|
@@ -2,11 +2,10 @@ import cookieParser from "cookie-parser";
|
|
|
2
2
|
import express from "express";
|
|
3
3
|
import { buildApiEndpointHandler } from "./handlers/api/createApiHandler.js";
|
|
4
4
|
import { buildAuthenticationMiddleware } from "./middleware/auth.js";
|
|
5
|
-
import { buildRequestLogger, buildResponseLogger, } from "./middleware/logging.js";
|
|
6
5
|
import { buildBodyValidatorMiddleware, buildQueryValidatorMiddleware, } from "./middleware/validation.js";
|
|
7
6
|
import { isEmpty } from "./utils/funcs.js";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
7
|
+
import { hasValue } from "./utils/typeGuards.js";
|
|
8
|
+
import colors from "colors";
|
|
10
9
|
function registerApiEndpoint(expressApp, endpoint) {
|
|
11
10
|
const { definition, handler } = endpoint;
|
|
12
11
|
const handlerStack = [
|
|
@@ -24,28 +23,31 @@ function registerApiEndpoint(expressApp, endpoint) {
|
|
|
24
23
|
expressApp[definition.method](definition.path, handlerStack);
|
|
25
24
|
}
|
|
26
25
|
export function createServer(config) {
|
|
27
|
-
const { port
|
|
28
|
-
const logger = config.logger === true
|
|
29
|
-
? console
|
|
30
|
-
: config.logger === false || hasNoValue(config.logger)
|
|
31
|
-
? NoOpLogger
|
|
32
|
-
: config.logger;
|
|
26
|
+
const { port } = config;
|
|
33
27
|
const app = express();
|
|
34
28
|
app.use(express.json());
|
|
35
29
|
app.use(express.urlencoded({ extended: true }));
|
|
36
30
|
app.use(cookieParser());
|
|
37
|
-
app.use(buildRequestLogger(logger, inDevMode));
|
|
38
|
-
app.use(buildResponseLogger(logger, inDevMode));
|
|
39
31
|
return {
|
|
40
32
|
expressApp: app,
|
|
41
|
-
logger: logger,
|
|
42
33
|
endpointDefinitions: [],
|
|
43
34
|
registerApiEndpoint(endpoint) {
|
|
44
35
|
registerApiEndpoint(app, endpoint);
|
|
45
36
|
this.endpointDefinitions.push(endpoint.definition);
|
|
46
37
|
},
|
|
47
38
|
start() {
|
|
48
|
-
app.listen(port)
|
|
39
|
+
app.listen(port, (err) => {
|
|
40
|
+
if (hasValue(err)) {
|
|
41
|
+
console.error(`${colors.red("Failed")} to start server on ${colors.cyan(port.toString())}`, err);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(`${colors.green("Successfully")} started server on ${colors.cyan(port.toString())}`);
|
|
45
|
+
this.endpointDefinitions.forEach((definition) => {
|
|
46
|
+
console.log(`Registering endpoint handler on ${colors.cyan(definition.path)} - ${colors.magenta(definition.method)}`);
|
|
47
|
+
});
|
|
48
|
+
console.log();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
49
51
|
},
|
|
50
52
|
};
|
|
51
53
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { NextFunction, Request, Response } from "express";
|
|
2
|
-
import { Logger } from "../utils/logging.js";
|
|
3
|
-
export declare function buildRequestLogger(logger: Logger, isInDevMode: boolean): (req: Request, res: Response, next: NextFunction) => void;
|
|
4
|
-
export declare function buildResponseLogger(logger: Logger, isInDevMode: boolean): (req: Request, res: Response, next: NextFunction) => void;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import colors from "colors/safe.js";
|
|
2
|
-
export function buildRequestLogger(logger, isInDevMode) {
|
|
3
|
-
return (req, res, next) => {
|
|
4
|
-
logger.info(`[Request] ${colors.cyan(req.method)} - ${colors.cyan(req.path)}`);
|
|
5
|
-
if (isInDevMode) {
|
|
6
|
-
logger.info(`[Request - Dev] Headers: ${JSON.stringify(req.headers)}`);
|
|
7
|
-
logger.info(`[Request - Dev] Query: ${JSON.stringify(req.query)}`);
|
|
8
|
-
logger.info(`[Request - Dev] Body: ${JSON.stringify(req.body)}`);
|
|
9
|
-
}
|
|
10
|
-
next();
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
export function buildResponseLogger(logger, isInDevMode) {
|
|
14
|
-
return (req, res, next) => {
|
|
15
|
-
const originalSend = res.send;
|
|
16
|
-
res.send = function (body) {
|
|
17
|
-
logger.info(`[Response] ${colors.cyan(req.method)} - ${colors.cyan(req.path)} - Status: ${res.statusCode > 299 && res.statusCode < 599 ? colors.red(res.statusCode.toString()) : colors.green(res.statusCode.toString())}`);
|
|
18
|
-
if (isInDevMode) {
|
|
19
|
-
logger.info(`[Response - Dev] Body: ${JSON.stringify(body)}`);
|
|
20
|
-
}
|
|
21
|
-
return originalSend.call(this, body);
|
|
22
|
-
};
|
|
23
|
-
next();
|
|
24
|
-
};
|
|
25
|
-
}
|