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 CHANGED
@@ -1,19 +1,29 @@
1
- # transit-kit
1
+ # Transit-Kit
2
2
 
3
3
  [![CI](https://github.com/D4rkr34lm/transit-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/D4rkr34lm/transit-kit/actions/workflows/ci.yml)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/D4rkr34lm/transit-kit/badge.svg?branch=main)](https://coveralls.io/github/D4rkr34lm/transit-kit?branch=main)
5
+ [![npm version](https://badge.fury.io/js/transit-kit.svg)](https://www.npmjs.com/package/transit-kit)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Node.js](https://img.shields.io/badge/Node.js-22+-green.svg)](https://nodejs.org/)
5
9
 
6
- A declarative TypeScript framework for building type-safe Express.js APIs with automatic OpenAPI generation.
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
- ## Features
12
+ ## Why Transit-Kit?
9
13
 
10
- - 🔒 **Type-Safe**: End-to-end type safety with TypeScript and Zod validation
11
- - 📝 **Declarative API Definition**: Define your endpoints with clear, declarative syntax
12
- - 🔄 **Automatic Validation**: Request body and query parameter validation using Zod schemas
13
- - 📚 **OpenAPI Generation**: Automatically generate OpenAPI documentation from your endpoint definitions
14
- - ⚡ **Express.js Powered**: Built on top of the battle-tested Express.js framework
15
- - ðŸŠĩ **Built-in Logging**: Request and response logging out of the box
16
- - ðŸŽŊ **Path Parameters**: Full support for path parameters with type safety
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
- ## Quick Start
34
+ ```bash
35
+ yarn add transit-kit
36
+ ```
37
+
38
+ ```bash
39
+ pnpm add transit-kit
40
+ ```
25
41
 
26
- ### 1. Create a Server
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
- ### 2. Define an API Endpoint
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
- import { createApiEndpointHandler } from "transit-kit/server";
42
- import z from "zod";
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
- // Define a simple GET endpoint
45
- const getUserEndpoint = createApiEndpointHandler(
185
+ // CREATE
186
+ const createTodo = createApiEndpointHandler(
46
187
  {
47
188
  meta: {
48
- name: "Get User",
49
- description: "Retrieves a user by ID",
50
- group: "Users",
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
- dataType: "application/json",
57
- dataSchema: z.object({
58
- id: z.string(),
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 (request) => {
67
- const { userId } = request.params;
68
-
69
- // Simulate fetching user
70
- const user = await fetchUser(userId);
240
+ async () => {
241
+ return {
242
+ code: 200,
243
+ data: {
244
+ todos: Array.from(todos.values()),
245
+ },
246
+ };
247
+ }
248
+ );
71
249
 
72
- if (!user) {
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
- dataType: "application/json",
81
- json: user,
284
+ data: todo,
82
285
  };
83
- },
286
+ }
84
287
  );
85
- ```
86
288
 
87
- ### 3. Define an Endpoint with Request Body and Query Parameters
88
-
89
- ```typescript
90
- const createUserEndpoint = createApiEndpointHandler(
289
+ // UPDATE
290
+ const updateTodo = createApiEndpointHandler(
91
291
  {
92
292
  meta: {
93
- name: "Create User",
94
- description: "Creates a new user",
95
- group: "Users",
293
+ name: "updateTodo",
294
+ group: "Todos",
295
+ description: "Update a todo item",
96
296
  },
97
- method: "post",
98
- path: "/users",
297
+ path: "/todos/:id",
298
+ method: "put",
99
299
  requestBodySchema: z.object({
100
- name: z.string().min(1),
101
- email: z.string().email(),
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
- 201: {
109
- dataType: "application/json",
110
- dataSchema: z.object({
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
- 400: {
117
- dataType: "application/json",
118
- dataSchema: z.object({
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 (request) => {
125
- // Request body is automatically validated and typed
126
- const { name, email, age } = request.body;
127
- const { notify } = request.query;
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
- try {
130
- const newUser = await createUser({ name, email, age });
325
+ const updated = {
326
+ ...todo,
327
+ ...body,
328
+ };
329
+ todos.set(parameters.id, updated);
131
330
 
132
- if (notify) {
133
- await sendNotification(email);
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: 201,
138
- dataType: "application/json",
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
- ### 4. Register Endpoints and Start Server
370
+ todos.delete(parameters.id);
153
371
 
154
- ```typescript
155
- server.registerApiEndpoint(getUserEndpoint);
156
- server.registerApiEndpoint(createUserEndpoint);
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-kit includes a simple, composable authentication mechanism based on `Authorization` headers. You declare one or more `SecurityScheme`s on an endpoint, and the framework will:
392
+ Transit-Kit supports **Basic** and **Bearer** authentication schemes out of the box.
164
393
 
165
- - Apply an authentication middleware for that endpoint
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
- ### Basic Auth
396
+ ```typescript
397
+ import { createBasicAuthSchema } from "transit-kit/server";
171
398
 
172
- Supports `Authorization: Basic <base64(username:password)>`.
399
+ // Define your user type
400
+ interface User {
401
+ id: string;
402
+ username: string;
403
+ role: string;
404
+ }
173
405
 
174
- ```typescript
175
- import {
176
- createApiEndpointHandler,
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
- const user = await findUserByUsername(username);
190
- if (!user) return null;
191
- const ok = await verifyPassword(password, user.passwordHash);
192
- return ok ? { id: user.id, role: user.role } : null;
193
- },
194
- );
195
-
196
- const getProfile = createApiEndpointHandler(
197
- {
198
- meta: { name: "Get Profile", description: "Returns caller profile", group: "Auth" },
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 Token Auth
223
-
224
- Supports `Authorization: Bearer <token>`.
423
+ ### Bearer Authentication
225
424
 
226
425
  ```typescript
227
- import { createBearerAuthSchema } from "transit-kit/server/security/bearerAuth";
228
-
229
- type Caller = { id: string; scopes: string[] };
426
+ import { createBearerAuthSchema } from "transit-kit/server";
230
427
 
231
- const bearerAuth = createBearerAuthSchema<Caller>(
232
- "bearer",
428
+ const bearerAuth = createBearerAuthSchema<User>(
429
+ "bearerAuth",
233
430
  async (token) => {
234
- const payload = await verifyJwt(token); // or call your token introspection service
235
- return payload ? { id: payload.sub, scopes: payload.scopes } : null;
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
- const getSecret = createApiEndpointHandler(
438
+ ### Protected Endpoints
439
+
440
+ ```typescript
441
+ const protectedEndpoint = createApiEndpointHandler(
240
442
  {
241
- meta: { name: "Get Secret", description: "Protected resource", group: "Auth" },
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: { dataType: "application/json", dataSchema: z.object({ secret: z.string() }) },
246
- 401: {},
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: [bearerAuth],
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 per Endpoint
485
+ ### Multiple Auth Schemes
258
486
 
259
- You can allow multiple authentication methods on the same endpoint. The framework tries them in the order you specify and picks the first successful scheme.
487
+ You can support multiple authentication methods:
260
488
 
261
489
  ```typescript
262
490
  const endpoint = createApiEndpointHandler(
263
491
  {
264
- meta: { name: "Hybrid Auth", description: "Basic or Bearer", group: "Auth" },
265
- method: "get",
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
- ### How It Works
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
- ## Response Types
504
+ Transit-Kit automatically generates OpenAPI 3.0 documentation from your endpoint definitions.
287
505
 
288
- ### JSON Response
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
- For endpoints that return JSON data:
533
+ // Serve the OpenAPI spec
534
+ server.expressApp.get("/openapi.json", (req, res) => {
535
+ res.json(openApiDoc);
536
+ });
291
537
 
292
- ```typescript
293
- return {
294
- code: 200,
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
- ### Empty Response
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
- For endpoints that return no content (e.g., 204 No Content):
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
- ```typescript
305
- return {
306
- code: 204,
307
- };
308
- ```
555
+ ## API Reference
309
556
 
310
- ## OpenAPI Generation
557
+ ### Server
311
558
 
312
- Transit-kit can automatically generate OpenAPI documentation from your endpoint definitions.
559
+ #### `createServer(config: ServerConfig): Server`
313
560
 
314
- ### Using the CLI
561
+ Creates a new Transit-Kit server instance.
315
562
 
316
- ```bash
317
- npx transit-kit generate-openapi --output openapi.json --target ./src
318
- ```
563
+ **Config Options:**
564
+ - `port: number` — Port to listen on
319
565
 
320
- Options:
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
- - `-o, --output <path>`: Output path for the generated OpenAPI document (default: `openapi.json`)
323
- - `-t, --target <path>`: Target path to search for endpoint definitions (default: `.`)
572
+ ### Endpoint Creation
324
573
 
325
- ### Programmatic Usage
574
+ #### `createApiEndpointHandler(definition, handler)`
326
575
 
327
- ```typescript
328
- import { generateOpenApiDoc } from "transit-kit/cli";
576
+ Creates a type-safe API endpoint.
329
577
 
330
- const openApiDoc = await generateOpenApiDoc("./src");
331
- ```
578
+ **Parameters:**
579
+ - `definition` — Endpoint definition object
580
+ - `handler` — Async function handling the request
332
581
 
333
- The generated OpenAPI document will include:
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
- - All registered endpoints
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
- ## Configuration
592
+ #### `createBasicAuthSchema<Caller>(name, validateCaller)`
343
593
 
344
- ### Server Configuration
594
+ Creates a Basic authentication scheme.
345
595
 
346
- ```typescript
347
- interface ServerConfig {
348
- inDevMode: boolean; // Enable development mode features
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
- ### Custom Logger
600
+ #### `createBearerAuthSchema<Caller>(name, validateCaller)`
355
601
 
356
- You can provide a custom logger with a console-like interface:
602
+ Creates a Bearer token authentication scheme.
357
603
 
358
- ```typescript
359
- const customLogger = {
360
- log: (message: string) => {
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
- const server = createServer({
370
- port: 3000,
371
- inDevMode: false,
372
- logger: customLogger,
373
- });
374
- ```
608
+ ### OpenAPI Generation
375
609
 
376
- ## API Reference
610
+ #### `generateOpenApiDoc(server, options): Promise<OpenAPIV3.Document>`
377
611
 
378
- ### `createServer(config: ServerConfig): Server`
612
+ Generates OpenAPI 3.0 documentation.
379
613
 
380
- Creates a new server instance with the specified configuration.
614
+ **Parameters:**
615
+ - `server: Server` — Your Transit-Kit server instance
616
+ - `options: GeneratorOptions` — OpenAPI metadata
381
617
 
382
- ### `createApiEndpointHandler(definition, handler)`
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
- Creates an API endpoint handler with type-safe request/response handling.
626
+ ## Response Types
385
627
 
386
- **Definition properties:**
628
+ Transit-Kit currently supports JSON responses:
387
629
 
388
- - `meta`: Metadata about the endpoint (name, description, group)
389
- - `method`: HTTP method (`get`, `post`, `put`, `patch`, `delete`)
390
- - `path`: Express-style path with optional parameters (e.g., `/users/:userId`)
391
- - `requestBodySchema`: (Optional) Zod schema for request body validation
392
- - `querySchema`: (Optional) Zod schema for query parameter validation
393
- - `responseSchemas`: Map of status codes to response schemas
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
- ### `server.registerApiEndpoint(endpoint)`
638
+ When using custom headers in your response schema, you must include them in the handler response:
396
639
 
397
- Registers an endpoint with the server.
640
+ ```typescript
641
+ return {
642
+ code: 200,
643
+ data: { /* ... */ },
644
+ headers: {
645
+ "X-Custom-Header": "value",
646
+ },
647
+ };
648
+ ```
398
649
 
399
- ### `server.start()`
650
+ ## Accessing Express Features
400
651
 
401
- Starts the Express server on the configured port.
652
+ Since Transit-Kit is built on Express, you can access the underlying Express app:
402
653
 
403
- ## Examples
654
+ ```typescript
655
+ const server = createServer({ /* ... */ });
404
656
 
405
- ### Complete Example
657
+ // Add custom middleware
658
+ server.expressApp.use(cors());
659
+ server.expressApp.use(helmet());
406
660
 
407
- ```typescript
408
- import { createServer, createApiEndpointHandler } from "transit-kit/server";
409
- import z from "zod";
661
+ // Static files
662
+ server.expressApp.use(express.static("public"));
410
663
 
411
- // Create server
412
- const server = createServer({
413
- port: 3000,
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
- // Define endpoints
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
- return {
449
- code: 200,
450
- dataType: "application/json",
451
- json: users,
452
- };
453
- },
454
- );
672
+ Transit-Kit exports two main modules:
455
673
 
456
- server.registerApiEndpoint(listUsersEndpoint);
457
- server.start();
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
- ## Author
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)
@@ -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;
@@ -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 { NoOpLogger } from "./utils/logging.js";
9
- import { hasNoValue, hasValue } from "./utils/typeGuards.js";
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, inDevMode } = config;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "transit-kit",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "A declarative TypeScript framework for building type-safe Express.js APIs with automatic OpenAPI generation",
5
5
  "keywords": [
6
6
  "express",
@@ -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
- }