alado 2.2.1 → 2.2.3

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,185 +1,338 @@
1
- Alado is an extremely fast, simple, robust, and lightweight (no-dependency)
2
- for creating server applications written in Node.js and running over the HTTP protocol.
1
+ # Alado
3
2
 
4
- Its main difference from most similar well-known frameworks is that with a minimum of external dependencies
5
- it provides out-of-the-box such fully-functioning features as routing, CORS, automatic API documentation (Open API 3.0),
6
- parsing query and path parameters, and the request body, file uploading, request authentication, etc.
3
+ > A fast, lightweight, zero-dependency Node.js HTTP framework with built-in routing, validation, authentication, file uploads, and automatic OpenAPI 3.0 documentation.
7
4
 
8
- # Documentation for version 2.0.0 and above:
5
+ [![npm](https://img.shields.io/npm/v/alado)](https://www.npmjs.com/package/alado)
6
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
9
7
 
10
- Version 2.0.0 comes with decorators that simplify the project structure and minimize the amount of code you need to write.
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ - [Why Alado](#why-alado)
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [Project Structure](#project-structure)
16
+ - [tsconfig Setup](#tsconfig-setup)
17
+ - [Application Setup](#application-setup)
18
+ - [Controllers](#controllers)
19
+ - [HTTP Method Decorators](#http-method-decorators)
20
+ - [defineRequest](#definerequest)
21
+ - [defineResponse](#defineresponse)
22
+ - [withAuth](#withauth)
23
+ - [DTOs](#dtos)
24
+ - [validateProperty](#validateproperty)
25
+ - [documentProperty](#documentproperty)
26
+ - [transformProperty](#transformproperty)
27
+ - [fileUploadProperty](#fileuploadproperty)
28
+ - [Authentication](#authentication)
29
+ - [File Uploads](#file-uploads)
30
+ - [OpenAPI / Swagger](#openapi--swagger)
31
+ - [API Reference](#api-reference)
11
32
 
12
- There is an example of the API creating with Alado (version >= 2.0.0):
13
- [https://github.com/databikers/alado-decorators-api-example](https://github.com/databikers/alado-decorators-api-example)
33
+ ---
14
34
 
15
- ## Use inside controllers the next decorators:
35
+ ## Why Alado
16
36
 
17
- ### HTTP Method Decorators
37
+ Alado sits in a unique position — it has the **simplicity of Express**, the **performance focus of Fastify**, and the **decorator-based structure of NestJS**, but with **zero external dependencies** and a much smaller surface area.
38
+
39
+ ---
40
+
41
+ ### Feature Comparison
42
+
43
+ | Feature | Alado | Express | Fastify | NestJS |
44
+ | --------------------------- | ---------- | --------------------- | --------------------- | ----------- |
45
+ | Zero dependencies | ✅ | ❌ | ❌ | ❌ |
46
+ | Decorator-based routing | ✅ | ❌ | ❌ | ✅ |
47
+ | Built-in request validation | ✅ | ❌ | ✅ (schemas) | ✅ (pipes) |
48
+ | Built-in auth pipeline | ✅ | ❌ | ❌ | ✅ (guards) |
49
+ | Auto OpenAPI 3.0 docs | ✅ | ❌ | ✅ (plugin) | ✅ (plugin) |
50
+ | Built-in file upload | ✅ | ❌ | ❌ | ❌ |
51
+ | Built-in body parsing | ✅ | ❌ (body-parser) | ✅ | ✅ |
52
+ | Built-in CORS | ✅ | ❌ (cors) | ❌ (plugin) | ✅ |
53
+ | TypeScript-first | ✅ | ⚠️ types via `@types` | ⚠️ types via `@types` | ✅ |
54
+ | Learning curve | 🟢 Low | 🟢 Low | 🟡 Medium | 🔴 High |
55
+ | Boilerplate | 🟢 Minimal | 🟡 Medium | 🟡 Medium | 🔴 Heavy |
56
+ | Ecosystem / plugins | 🟡 Small | 🟢 Massive | 🟢 Large | 🟢 Large |
57
+
58
+ ---
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ npm install alado
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Quick Start
69
+
70
+ A minimal API in under 30 lines:
18
71
 
19
72
  ```ts
20
- get(path: string, options: HttpDecoratorOptions = {})
21
- post(path: string, options: HttpDecoratorOptions = {})
22
- put(path: string, options: HttpDecoratorOptions = {})
23
- patch(path: string, options: HttpDecoratorOptions = {})
24
- del(path: string, options: HttpDecoratorOptions = {})
25
- head(path: string, options: HttpDecoratorOptions = {})
73
+ // src/modules/user/user.controller.ts
74
+ import { post, defineRequest, defineResponse } from 'alado';
75
+ import { CredentialsDto } from './dto/credentials.dto';
76
+
77
+ export class UserController {
78
+ @post('/user', { tags: ['User'] })
79
+ @defineResponse({
80
+ statusCode: 201,
81
+ title: 'Created',
82
+ headers: { 'Content-Type': 'application/json' },
83
+ body: { id: '123', email: 'user@example.com' },
84
+ })
85
+ @defineRequest({ body: CredentialsDto })
86
+ async create(req) {
87
+ const user = { id: '123', ...req.body };
88
+ return {
89
+ statusCode: 201,
90
+ headers: { 'Content-Type': 'application/json' },
91
+ body: user,
92
+ };
93
+ }
94
+ }
95
+ ```
96
+
97
+ ```ts
98
+ // src/main.ts
99
+ import { initializeApplication } from 'alado';
100
+ import { UserController } from './user/user.controller';
101
+
102
+ const app = initializeApplication({
103
+ serverOptions: { port: 3000 },
104
+ controllers: [UserController],
105
+ });
106
+
107
+ app.start(() => console.log('Running on http://localhost:3000'));
26
108
  ```
27
109
 
28
- #### Arguments
110
+ ---
111
+
112
+ ## Project Structure
113
+
114
+ Possible layout for a decorator-based Alado project:
29
115
 
30
- - **path**: Route string (e.g., `/users/:id`).
31
- - **options**: `HttpDecoratorOptions` object, may include:
32
- - `appId`: Identifier of the application instance.
33
- - `title`: A descriptive name of the endpoint.
34
- - `description`: Documentation text for the endpoint.
35
- - `tags`: Array of tags for grouping in OpenAPI docs.
36
- - `isHidden`: Boolean flag to hide the endpoint from OpenAPI output
116
+ ```
117
+ src/
118
+ ├── main.ts # App entry point
119
+ ├── config/
120
+ │ └── server.options.ts # AladoServerOptions
121
+ ├── auth/
122
+ │ └── bearer.auth.ts # RequestAuthentication config
123
+ └── modules/
124
+ └── user/
125
+ ├── index.ts
126
+ ├── user.controller.ts
127
+ └── dto/
128
+ ├── credentials.dto.ts
129
+ ├── user.dto.ts
130
+ ├── user-files.dto.ts
131
+ └── id.dto.ts
132
+ ```
37
133
 
38
134
  ---
39
135
 
40
- ### `defineRequest` decorator
136
+ ## Application Setup
137
+
138
+ ### `initializeApplication`
41
139
 
42
140
  ```ts
43
- defineRequest(request: Partial<Record<keyof [Request](#request), AnyClass>>)
141
+ import { initializeApplication } from 'alado';
142
+ import { UserController } from '@user';
143
+
144
+ const app = initializeApplication({
145
+ serverOptions: {
146
+ port: 3000,
147
+ appId: 'my-api', // optional — used to scope multi-app setups
148
+ logger: {
149
+ log: console.log,
150
+ error: console.error,
151
+ },
152
+ },
153
+ controllers: [UserController],
154
+ });
155
+
156
+ app.start(() => console.log('Started'));
157
+ app.stop(() => console.log('Stopped'));
44
158
  ```
45
159
 
46
- #### Arguments
160
+ ### Passing options to controller constructors
47
161
 
48
- - **request**: A partial mapping of keys from [Request](#request) to your DTO classes.
49
- - Keys can include:
50
- - `headers`, `query`, `path`, `body`: each a record of property definitions.
51
- - `files`: mapping of [FilePropertyDefinition](#filepropertydefinition).
52
- - `auth`: authentication data mapping.
53
- - Each value points to a class that uses specific DTO decorators to define how that part of the request should be validated and documented.
162
+ ```ts
163
+ initializeApplication({
164
+ serverOptions: { port: 3000 },
165
+ controllers: [
166
+ {
167
+ controller: UserController,
168
+ options: [{ service: UserService }], // passed to new UserController(...)
169
+ },
170
+ ],
171
+ });
172
+ ```
54
173
 
55
- #### `Request`
174
+ ### HTTPS
56
175
 
57
176
  ```ts
58
- interface Request {
59
- request: IncomingMessage;
60
- ip?: string;
61
- origin?: string;
62
- method?: HttpMethod;
63
- url?: string;
64
- auth?: Record<string, any>;
65
- path?: Record<string, string>;
66
- headers?: IncomingHttpHeaders;
67
- query?: Record<string, any>;
68
- body?: Record<string, any>;
69
- rawBody?: string;
70
- files?: Record<string, { stream: Readable; size: number; mimetype: string }>;
71
- }
177
+ import { readFileSync } from 'fs';
178
+
179
+ const app = initializeApplication({
180
+ serverOptions: {
181
+ port: 443,
182
+ ssl: {
183
+ cert: readFileSync('/path/to/cert.pem'),
184
+ key: readFileSync('/path/to/key.pem'),
185
+ },
186
+ },
187
+ controllers: [UserController],
188
+ });
72
189
  ```
73
190
 
74
191
  ---
75
192
 
76
- ### `defineResponse` decorator
193
+ ## Controllers
194
+
195
+ Controllers are plain TypeScript classes decorated with HTTP method decorators.
196
+
197
+ ### HTTP Method Decorators
77
198
 
78
199
  ```ts
79
- defineResponse(response: [Response](#response)<any>)
200
+ import { get, post, put, patch, del, head } from 'alado';
80
201
  ```
81
202
 
82
- #### Arguments
203
+ | Decorator | Method |
204
+ | ------------------------ | ------ |
205
+ | `@get(path, options?)` | GET |
206
+ | `@post(path, options?)` | POST |
207
+ | `@put(path, options?)` | PUT |
208
+ | `@patch(path, options?)` | PATCH |
209
+ | `@del(path, options?)` | DELETE |
210
+ | `@head(path, options?)` | HEAD |
211
+
212
+ **Options:**
83
213
 
84
- - **response**: Response schema definition, including:
85
- - `title`: Short descriptive title.
86
- - `description`: Explanation of the response.
87
- - `statusCode`: HTTP status code.
88
- - `headers`: Mapping of header names to values.
89
- - `body`: Response body object (may be an instance of some DTO or class).
214
+ ```ts
215
+ interface HttpDecoratorOptions {
216
+ appId?: string; // scope to a specific app instance
217
+ title?: string; // endpoint name in OpenAPI
218
+ description?: string; // endpoint description in OpenAPI
219
+ tags?: string[]; // grouping tags in OpenAPI
220
+ isHidden?: boolean; // exclude from OpenAPI output
221
+ }
222
+ ```
90
223
 
91
- #### `Response`
224
+ **Example:**
92
225
 
93
226
  ```ts
94
- interface Response<T> {
95
- title?: string;
96
- description?: string;
97
- statusCode: number;
98
- headers?: Record<string, string>;
99
- body?: T;
227
+ @get('/users', { tags: ['User'], title: 'List users' })
228
+ async list(req: Request) {
229
+ return {
230
+ statusCode: 200,
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: [],
233
+ };
100
234
  }
101
235
  ```
102
236
 
103
237
  ---
104
238
 
105
- ### `withAuth` decorator
239
+ ### `defineRequest`
240
+
241
+ Declares and validates the incoming request shape.
106
242
 
107
243
  ```ts
108
- withAuth(requestAuthentication: [RequestAuthentication](#requestauthentication))
244
+ @defineRequest({
245
+ headers: AnyClass,
246
+ query: AnyClass,
247
+ path: AnyClass,
248
+ body: AnyClass,
249
+ files: AnyClass,
250
+ auth: AnyClass,
251
+ })
109
252
  ```
110
253
 
111
- #### Arguments
254
+ Each value is a DTO class whose properties carry `@validateProperty`, `@documentProperty`, etc.
112
255
 
113
- - **requestAuthentication**: Defines authentication requirements, including:
114
- - `inputProperty`: Field name in request used for auth input.
115
- - `outputProperty`: Field name in request to store auth result.
116
- - `handler`: Function to transform/verify authentication value.
117
- - `handlerContext`: Optional context object passed to handler.
118
- - `required`: Boolean indicating if authentication is mandatory.
119
- - `error`: Error object to use when authentication fails.
120
-
121
- #### `RequestAuthentication`
256
+ **Example:**
122
257
 
123
258
  ```ts
124
- interface RequestAuthentication {
125
- inputProperty: string;
126
- outputProperty: string;
127
- handler: (value: any) => any | Promise<any>;
128
- handlerContext?: any;
129
- required: boolean;
130
- error: AladoServerError;
259
+ @post('/user', { tags: ['User'] })
260
+ @defineRequest({ body: CredentialsDto })
261
+ async create(req: Request) {
262
+ console.log(req.body); // typed, validated CredentialsDto fields
131
263
  }
132
264
  ```
133
265
 
134
- ```typescript
135
- import { RequestAuthentication } from 'alado';
136
- import { DataStore } from '@data-store';
266
+ ---
137
267
 
138
- export const bearerAuth: RequestAuthentication = {
139
- required: true,
140
- inputProperty: 'headers.x-api-key',
141
- outputProperty: 'auth.user',
142
- handler(value: string) {
143
- return DataStore.bearerAuth(value);
144
- },
145
- error: {
146
- statusCode: 401,
147
- message: 'Unauthorized',
148
- },
149
- };
268
+ ### `defineResponse`
269
+
270
+ Documents the expected response shape for OpenAPI output.
271
+
272
+ ```ts
273
+ @defineResponse({
274
+ statusCode: 201,
275
+ title: 'Created',
276
+ entity: 'User',
277
+ description: 'User was created successfully',
278
+ headers: { 'Content-Type': 'application/json' },
279
+ body: exampleUserDto, // an instance of your DTO class
280
+ })
150
281
  ```
151
282
 
152
- #### `AladoServerError`
283
+ ---
284
+
285
+ ### `withAuth`
286
+
287
+ Attaches an authentication pipeline to a route.
153
288
 
154
289
  ```ts
155
- type AladoServerError = {
156
- statusCode: number;
157
- message: string;
158
- };
290
+ @patch('/user/:id', { tags: ['User'] })
291
+ @withAuth(bearerAuth)
292
+ @defineRequest({ path: Id, body: UserDto })
293
+ async update(req: Request) {
294
+ console.log(req.auth.user); // injected by the auth handler
295
+ }
159
296
  ```
160
297
 
298
+ See [Authentication](#authentication) for full config details.
299
+
161
300
  ---
162
301
 
163
- Example of controller:
302
+ ### Full controller example
164
303
 
165
304
  ```ts
305
+ import { get, post, patch, del, defineRequest, defineResponse, withAuth } from 'alado';
306
+ import { bearerAuth } from './auth/bearer.auth';
307
+ import { CredentialsDto, UserDto, Id } from './dto';
308
+
166
309
  export class UserController {
310
+ @get('/users', { tags: ['User'] })
311
+ @defineResponse({
312
+ statusCode: 200,
313
+ headers: { 'Content-Type': 'application/json' },
314
+ body: [],
315
+ })
316
+ async list(req) {
317
+ return {
318
+ statusCode: 200,
319
+ headers: { 'Content-Type': 'application/json' },
320
+ body: [],
321
+ };
322
+ }
323
+
167
324
  @post('/user', { tags: ['User'] })
168
325
  @defineResponse({
169
326
  statusCode: 201,
170
- title: 'Created',
171
- entity: 'User',
172
327
  headers: { 'Content-Type': 'application/json' },
173
- body: exampleUserDto,
328
+ body: {},
174
329
  })
175
330
  @defineRequest({ body: CredentialsDto })
176
- public async create(req: Request) {
177
- const { body } = req;
178
- const user = DataStore.signUp(body as CredentialsDto);
331
+ async create(req) {
179
332
  return {
180
333
  statusCode: 201,
181
334
  headers: { 'Content-Type': 'application/json' },
182
- body: user,
335
+ body: req.body,
183
336
  };
184
337
  }
185
338
 
@@ -187,70 +340,29 @@ export class UserController {
187
340
  @withAuth(bearerAuth)
188
341
  @defineResponse({
189
342
  statusCode: 200,
190
- title: 'OK',
191
- enity: 'User',
192
343
  headers: { 'Content-Type': 'application/json' },
193
- body: exampleUserDto,
344
+ body: {},
345
+ })
346
+ @defineRequest({
347
+ path: Id,
348
+ body: UserDto,
194
349
  })
195
- @defineRequest({ body: UserDto, path: Id })
196
- public async update(req: Request) {
197
- const isMyId = this.isMyId(req);
198
- if (!isMyId) {
199
- return {
200
- statusCode: 403,
201
- headers: { 'Content-Type': 'application/json' },
202
- body: { message: 'Access denied' },
203
- };
204
- }
205
- const { path, body } = req;
206
- const user = DataStore.getUser(path.id);
207
- if (user) {
208
- DataStore.setUser(path.id, body as UserDto);
209
- }
350
+ async update(req) {
210
351
  return {
211
- statusCode: user ? 200 : 404,
352
+ statusCode: 200,
212
353
  headers: { 'Content-Type': 'application/json' },
213
- body: DataStore.getUser(path.id) || { message: 'Not Found' },
354
+ body: req.body,
214
355
  };
215
356
  }
216
357
 
217
- @post('/user/:id/avatar', { tags: ['User'] })
358
+ @del('/user/:id', { tags: ['User'] })
218
359
  @withAuth(bearerAuth)
219
- @defineResponse({
220
- statusCode: 200,
221
- title: 'OK',
222
- enity: 'User',
223
- headers: { 'Content-Type': 'application/json' },
224
- body: exampleUserDto,
225
- })
226
- @defineRequest({ path: Id, files: UserFilesDto })
227
- public setAvatar(req: Request) {
228
- const isMyId = this.isMyId(req);
229
- if (!isMyId) {
230
- return {
231
- statusCode: 403,
232
- headers: { 'Content-Type': 'application/json' },
233
- body: { message: 'Access denied' },
234
- };
235
- }
236
- const { path, files } = req;
237
- const user = DataStore.getUser(path.id);
238
- if (!user) {
239
- return {
240
- statusCode: 404,
241
- headers: { 'Content-Type': 'application/json' },
242
- body: { message: 'Not Found' },
243
- };
244
- }
245
- const { avatar } = files;
246
- const writeStream = createWriteStream(`${process.cwd()}/uploads/user-${path.id}-avatar.png`, {
247
- encoding: 'latin1',
248
- });
249
- avatar.stream.pipe(writeStream);
360
+ @defineRequest({ path: Id })
361
+ async remove(req) {
250
362
  return {
251
- statusCode: 202,
252
- headers: { 'Content-Type': 'application/json' },
253
- body: {},
363
+ statusCode: 204,
364
+ headers: {},
365
+ body: null,
254
366
  };
255
367
  }
256
368
  }
@@ -258,93 +370,77 @@ export class UserController {
258
370
 
259
371
  ---
260
372
 
261
- ## Use these decorators inside DTO classes:
373
+ ## DTOs
262
374
 
263
- ### `fileUploadProperty`
375
+ DTOs are plain TypeScript classes with decorated properties. They define how request data is validated, transformed, and documented.
264
376
 
265
- ```ts
266
- fileUploadProperty(filePropertyDefinition: [FilePropertyDefinition](#filepropertydefinition))
267
- ```
268
-
269
- #### Arguments
270
-
271
- - **filePropertyDefinition**: Object describing file upload rules, including:
272
- - `mimetypes`: Array of allowed MIME types.
273
- - `maxSize`: Maximum file size (bytes).
274
- - `required`: Whether the file must be provided.
275
- - `mimetypeError`, `maxSizeError`, `requiredError`: Error objects describing validation failures.
377
+ ### `validateProperty`
276
378
 
277
- #### `FilePropertyDefinition`
379
+ Validates an incoming property value. Runs before your handler.
278
380
 
279
381
  ```ts
280
- interface FilePropertyDefinition {
281
- mimetypes: string[];
282
- maxSize: number;
283
- required: boolean;
284
- mimetypeError: AladoServerError;
285
- maxSizeError: AladoServerError;
286
- requiredError: AladoServerError;
382
+ import { validateProperty } from 'alado';
383
+
384
+ class CredentialsDto {
385
+ @validateProperty({
386
+ required: true,
387
+ handler: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
388
+ error: {
389
+ statusCode: 400,
390
+ message: 'Invalid email address',
391
+ },
392
+ })
393
+ email: string = 'user@example.com';
287
394
  }
288
395
  ```
289
396
 
397
+ - `handler` receives the raw value and must return `boolean | Promise<boolean>`
398
+ - returning `false` → framework returns the configured `error` automatically
399
+ - `handler` is called with `this` bound to the full `Request` object — you can access `this.auth`, `this.query`, etc.
400
+ - **do not use arrow functions** if you need access to `this`
401
+
290
402
  ---
291
403
 
292
404
  ### `documentProperty`
293
405
 
294
- ```ts
295
- documentProperty(propertyDocumentation: [PropertyDocumentation](#propertydocumentation))
296
- ```
297
-
298
- #### Arguments
299
-
300
- - **propertyDocumentation**: Documentation metadata for a property, including:
301
- - `schema`: JSON Schema–like definition (`type`, `format`, validation structure).
302
- - `description`: Human-readable explanation.
303
- - `example`: Example value.
304
-
305
- #### `PropertyDocumentation`
406
+ Adds OpenAPI/Swagger metadata to a property.
306
407
 
307
408
  ```ts
308
- interface PropertyDocumentation {
309
- schema: PropertyDefinitionSchema;
310
- description?: string;
311
- example?: any;
409
+ import { documentProperty } from 'alado';
410
+
411
+ class UserDto {
412
+ @documentProperty({
413
+ schema: {
414
+ type: 'string',
415
+ minLength: 2,
416
+ maxLength: 50,
417
+ },
418
+ description: 'The user display name',
419
+ example: 'John Doe',
420
+ })
421
+ name: string = 'John Doe';
312
422
  }
313
423
  ```
314
424
 
315
- #### `PropertyDefinitionSchema`
425
+ Supports the full OpenAPI `PropertyDefinitionSchema`:
316
426
 
317
427
  ```ts
318
428
  interface PropertyDefinitionSchema {
319
- type?: string;
320
- format?: string;
429
+ type?: string; // 'string' | 'number' | 'boolean' | 'object' | 'array'
430
+ format?: string; // 'email' | 'uuid' | 'date-time' | ...
321
431
  default?: any;
322
- $ref?: string;
323
432
  nullable?: boolean;
324
- readOnly?: boolean;
325
- writeOnly?: boolean;
326
- deprecated?: boolean;
327
433
  enum?: any[];
328
- oneOf?: Array<PropertyDefinitionSchema>;
329
- anyOf?: Array<PropertyDefinitionSchema>;
330
- allOf?: Array<PropertyDefinitionSchema>;
331
- not?: Array<PropertyDefinitionSchema>;
332
- properties?: Record<string, PropertyDefinitionSchema>;
333
- additionalProperties?: PropertyDefinitionSchema;
334
- minProperties?: number;
335
- maxProperties?: number;
336
434
  minimum?: number;
337
435
  maximum?: number;
338
- exclusiveMinimum?: number;
339
- exclusiveMaximum?: number;
340
- multipleOf?: number;
341
436
  minLength?: number;
342
437
  maxLength?: number;
343
438
  pattern?: string;
344
- items?: PropertyDefinitionSchema;
345
- minItems?: number;
346
- maxItems?: number;
347
- uniqueItems?: boolean;
439
+ items?: PropertyDefinitionSchema; // for arrays
440
+ properties?: Record<string, PropertyDefinitionSchema>; // for objects
441
+ oneOf?: PropertyDefinitionSchema[];
442
+ anyOf?: PropertyDefinitionSchema[];
443
+ allOf?: PropertyDefinitionSchema[];
348
444
  }
349
445
  ```
350
446
 
@@ -352,350 +448,100 @@ interface PropertyDefinitionSchema {
352
448
 
353
449
  ### `transformProperty`
354
450
 
355
- ```ts
356
- transformProperty(propertyTransformer: (value:any) => any | Promise<any>)
357
-
358
- ```
359
-
360
- ### `validateProperty`
451
+ Transforms a property value before it reaches your handler.
361
452
 
362
453
  ```ts
363
- validateProperty(propertyValidation: [PropertyValidation](#propertyvalidation))
364
- ```
454
+ import { transformProperty } from 'alado';
365
455
 
366
- #### Arguments
456
+ class QueryDto {
457
+ @transformProperty((value) => parseInt(value.toString(), 10))
458
+ page: number = 1;
367
459
 
368
- - **propertyValidation**: Validation rules, including:
369
- - `required`: Whether the property is mandatory.
370
- - `handler`: Function to validate the property value (sync or async).
371
- - `error`: Error object returned when validation fails.
372
-
373
- #### `PropertyValidation`
374
-
375
- ```ts
376
- interface PropertyValidation {
377
- required?: boolean;
378
- handler: (value: any) => boolean | Promise<boolean>; // throws error when returns false
379
- error: AladoServerError;
460
+ @transformProperty((value) => value.trim().toLowerCase())
461
+ email: string = '';
380
462
  }
381
463
  ```
382
464
 
383
465
  ---
384
466
 
385
- DTOs examples:
386
-
387
- ```typescript
388
- import { validateProperty, documentProperty } from 'alado';
467
+ ### `fileUploadProperty`
389
468
 
390
- export class Id {
391
- @validateProperty({
392
- required: true,
393
- handler: async (value: any) => idValidator(value),
394
- error: {
395
- statusCode: 400,
396
- message: 'Invalid id',
397
- },
398
- })
399
- @documentProperty({
400
- schema: {
401
- type: 'string',
402
- },
403
- example: '7ef5ed25-53b1-432f-96ec-8e35d830eb9c',
404
- description: 'User Id',
405
- })
406
- id: string = '7ef5ed25-53b1-432f-96ec-8e35d830eb9c';
407
- }
408
- ```
469
+ Marks a DTO property as a file upload field with MIME type and size validation.
409
470
 
410
- ```typescript
471
+ ```ts
411
472
  import { fileUploadProperty } from 'alado';
412
473
 
413
474
  export class UserFilesDto {
414
475
  @fileUploadProperty({
415
- mimetypes: ['image/png'],
416
- maxSize: 1048576,
476
+ mimetypes: [
477
+ 'image/png',
478
+ 'image/jpeg',
479
+ ],
480
+ maxSize: 2 * 1024 * 1024, // 2MB in bytes
417
481
  required: true,
418
482
  maxSizeError: {
419
483
  statusCode: 413,
420
- message: 'The avatar should not be larger than 1MB',
484
+ message: 'File must not exceed 2MB',
421
485
  },
422
486
  mimetypeError: {
423
487
  statusCode: 415,
424
- message: 'The avatar should be a PNG image',
488
+ message: 'Only PNG and JPEG images are allowed',
425
489
  },
426
490
  requiredError: {
427
491
  statusCode: 400,
428
- message: 'The avatar file is required',
492
+ message: 'Avatar file is required',
429
493
  },
430
494
  })
431
- avatar: string = '/path/to/image.png';
495
+ avatar: string = '/path/to/avatar.png';
432
496
  }
433
497
  ```
434
498
 
435
- ```typescript
436
- import { validateProperty, documentProperty } from 'alado';
499
+ ---
437
500
 
438
- export class CredentialsDto {
439
- @validateProperty({
440
- required: true,
441
- handler: async (value: any) => emailValidator(value),
442
- error: {
443
- statusCode: 400,
444
- message: 'Invalid email',
445
- },
446
- })
447
- @documentProperty({
448
- schema: {
449
- type: 'string',
450
- },
451
- example: 'example@example.com',
452
- description: 'User email',
453
- })
454
- email: string = 'example@example.com';
501
+ ### Combining decorators
502
+
503
+ Decorators stack — use them together for full validation + documentation:
455
504
 
505
+ ```ts
506
+ export class Id {
456
507
  @validateProperty({
457
508
  required: true,
458
- handler: async (value: any) => passwordValidator(value),
509
+ handler: (value) => /^[0-9a-f-]{36}$/.test(value),
459
510
  error: {
460
511
  statusCode: 400,
461
- message: 'Invalid password',
512
+ message: 'Invalid UUID',
462
513
  },
463
514
  })
464
515
  @documentProperty({
465
516
  schema: {
466
517
  type: 'string',
518
+ format: 'uuid',
467
519
  },
468
- example: 'securePassword',
469
- description: 'User password',
520
+ example: '7ef5ed25-53b1-432f-96ec-8e35d830eb9c',
521
+ description: 'Resource identifier',
470
522
  })
471
- password: string = 'securePassword';
523
+ id: string = '7ef5ed25-53b1-432f-96ec-8e35d830eb9c';
472
524
  }
473
-
474
- export const credentialsDto = new CredentialsDto();
475
525
  ```
476
526
 
477
527
  ---
478
528
 
479
- ## Initialize the application
480
-
481
- Finally, use the `initializeApplication` function to get an instance you would like to run:
529
+ ## Authentication
482
530
 
483
- ```ts
484
- import { initializeApplication } from 'alado';
485
- import { aladoServerOptions } from '@config';
486
- import { UserController } from '@user';
487
-
488
- export const app = initializeApplication({
489
- serverOptions: aladoServerOptions,
490
- controllers: [UserController],
491
- });
492
-
493
- app.start(() => console.log('Application has been successfully started'));
494
- ```
495
-
496
- There's ability to pass args to controller`s constructor:
497
-
498
- ```ts
499
- import { initializeApplication } from 'alado';
500
- import { aladoServerOptions } from '@config';
501
- import { UserController } from '@user';
502
-
503
- export const app = initializeApplication({
504
- serverOptions: aladoServerOptions,
505
- controllers: [{ controller: UserController, options: [{ service: SomeService }] }],
506
- });
507
-
508
- app.start(() => console.log('Application has been successfully started'));
509
- ```
510
-
511
- #### Arguments
512
-
513
- - **initializeApplicationOptions**: Object containing:
514
- - **serverOptions** ([AladoServerOptions](#aladoserveroptions)): Configuration for starting the server (e.g., `appId`, port, logger).
515
- - **controllers**: Array of controller classes to initialize at startup.
516
-
517
- #### `InitializeApplicationOptions`
518
-
519
- ```ts
520
- interface InitializeApplicationOptions {
521
- serverOptions: AladoServerOptions;
522
- controllers: AnyClass[];
523
- }
524
- ```
525
-
526
- #### `AladoServerOptions`
531
+ Define a `RequestAuthentication` config object and pass it to `@withAuth`.
527
532
 
528
533
  ```ts
529
- interface AladoServerOptions {
530
- appId?: string;
531
- port?: number;
532
- logger?: AladoServerLogger;
533
- }
534
- ```
535
-
536
- #### `AladoServerLogger`
537
-
538
- ```ts
539
- interface AladoServerLogger {
540
- log: (...args: any[]) => void;
541
- error: (error: Error) => void;
542
- }
543
- ```
544
-
545
- There is an example of the API creating with Alado (version < 2.0.0):
546
-
547
- The basis of use is a combination of interface simplicity and separation of the “routine” part of writing API
548
- from business logic implementation - due to a declarative approach to describing all request parameters
549
- in the form of static reusable structures.
550
-
551
- [https://github.com/databikers/alado-api-example](https://github.com/databikers/alado-api-example)
552
-
553
- 1. Installation
554
-
555
- ```shell
556
- npm i -s alado
557
- ```
558
-
559
- 2. Instancing
560
-
561
- ```typescript
562
- import { AladoServer } from 'alado';
563
-
564
- const app: AladoServer = new AladoServer({
565
- port: 3000,
566
- cors: {
567
- enable: true,
568
- allowedOrigin: '*',
569
- allowedHeaders: ['Authorization'],
570
- exposeHeaders: ['x-total-count'],
571
- },
572
- documentProperty: {
573
- enable: true,
574
- route: '/open-api',
575
- info: {
576
- title: 'My API title',
577
- description: 'My API description',
578
- version: '1.0.0',
579
- },
580
- },
581
- verbose: true,
582
- maxBodySizeBytes: 1024,
583
- });
584
- ```
585
-
586
- In case you want to run an HTTPs server (even though normally a Node.js app runs behind nginx/ingress etc.)
587
- You can do it using the ssl parameter (look at SecureContextOptions in the "tls" module -
588
- [Node.js TLS module](https://nodejs.org/api/tls.html#tlscreateserveroptions-secureconnectionlistener):
589
-
590
- ```typescript
591
- import { readFileSync } from 'fs';
592
- import { AladoServer } from 'alado';
593
-
594
- const app = new AladoServer({
595
- port: 3000,
596
- ssl: {
597
- cert: readFileSync('/path/to/cert'),
598
- key: readFileSync('/path/to/key'),
599
- },
600
- // https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener
601
- serverOptions: {
602
- keepAliveTimeout: 5000,
603
- },
604
- cors: {
605
- enable: true,
606
- allowedOrigin: '*',
607
- allowedHeaders: ['Authorization'],
608
- exposeHeaders: ['x-total-count'],
609
- allowedCredentials: true,
610
- maxAge: 3600,
611
- },
612
- headers: {
613
- a: 'b', //any additional header
614
- },
615
- openApiDoc: {
616
- enable: true,
617
- route: '/',
618
- info: {
619
- title: 'My API title',
620
- description: 'My API description',
621
- version: '1.0.0',
622
- },
623
- },
624
- verbose: true,
625
- maxBodySizeBytes: 1024,
626
- });
627
- ```
628
-
629
- 3. Defining routes
630
-
631
- Here is how you define routes in Alado:
632
-
633
- ```typescript
634
- app.get('/user/:id', context, (request: Request): Response<UserDto> => {
635
- const user = {
636
- name: 'John Doe',
637
- id: request.path.id,
638
- };
639
- return {
640
- statusCode: 200,
641
- headers: {
642
- 'Content-Type': 'application/json',
643
- },
644
- body: user,
645
- };
646
- });
647
-
648
- app.start(() => console.log(`[API]: application has been launched successfully`));
649
- ```
650
-
651
- The application provides "get", "post", "delete", "patch", "put" and "head" methods for defining routes.
652
- These methods accept three required arguments:
653
-
654
- - route or path: A string that may contain variables to specify the route.
655
- - context: An aggregating object that fully describes the request.
656
- - handler: A function that handles the request.
657
-
658
- 3.1 Route or path.
659
-
660
- Path variables defining works in the same way as it works in other frameworks: You just define a part of your route (means part separated by slashes)
661
- as ":id" and receive the passed value as request.path.id).
662
-
663
- 3.2 Context
664
-
665
- Here's the contract of the request context:
666
-
667
- ```typescript
668
- interface Context<T> {
669
- title: string;
670
- auth?: RequestAuthentication;
671
- isHidden?: boolean; //Just exclude some route from Your API specification
672
- options: ContextOptions;
673
- request: ContextRequest;
674
- response: Response<T>;
675
- }
676
- ```
677
-
678
- Unlike context.title, which is simply a unique string, the other properties may not be immediately clear.
679
- However, once explained, they become quite obvious and extremely logical.
680
-
681
- **Note that all parts of the context and its nested properties can be reused**
682
-
683
- 3.2.1 Context.auth
684
-
685
- ```typescript
534
+ // src/modules/user/auth/bearer.auth.ts
686
535
  import { RequestAuthentication } from 'alado';
536
+ import { DataStore } from '@data-store';
687
537
 
688
- const bearerAuth: RequestAuthentication = {
689
- inputProperty: 'headers.authorization',
690
- outputProperty: 'auth.user',
691
- handler: async (bearer: string) => {
692
- // validateProperty header, return user
693
- return {
694
- name: 'John Doe',
695
- id: 1,
696
- };
538
+ export const bearerAuth: RequestAuthentication = {
539
+ required: true,
540
+ inputProperty: 'headers.x-api-key', // where to read the token from
541
+ outputProperty: 'auth.user', // where to write the result on req
542
+ handler(value: string) {
543
+ return DataStore.findUserByToken(value); // return user or null/undefined
697
544
  },
698
- required: true, // false You want "soft" checking
699
545
  error: {
700
546
  statusCode: 401,
701
547
  message: 'Unauthorized',
@@ -703,43 +549,26 @@ const bearerAuth: RequestAuthentication = {
703
549
  };
704
550
  ```
705
551
 
706
- This means that the application takes an "Authorization" header
707
- (or "api_token" query parameter if you provide "query.api_token" as an inputProperty,
708
- "body.someThing" and "path.token" work also)
709
- use the provided handler to get the user and set the received user to request.auth.user,
710
- (and the mentioned "request" here is the request argument in your route handler)
711
- Or just returns 401 Unauthorized.
712
- This means that the application can authenticate using an "Authorization" header, or alternatively,
713
- using an "api_token" query parameter (if specified as "query.api_token" in the inputProperty).
714
- It can also authenticate using values like "body.someThing" or "path.token".
715
- In the example the provided handler retrieves the user and assigns it to request.auth.user
716
- (where request is the argument in your route handler).
717
- If authentication fails, it returns a 401 Unauthorized response.
718
-
719
- This also works:
720
-
721
- ```typescript
722
- import { RequestAuthentication } from 'alado';
552
+ The `inputProperty` supports dot-notation and can point to any part of the request:
723
553
 
724
- const bearerAuth: RequestAuthentication = {
725
- inputProperty: 'header.x-api-token',
726
- outputProperty: 'auth',
727
- handler: async (xApiToken: string) => {
728
- // You will receive user and company as nested properties in request auth
729
- return {
730
- user: {
731
- name: 'John Doe',
732
- id: 1,
733
- },
734
- company: {
735
- name: 'Databikers Limited',
736
- id: 2,
737
- },
738
- };
739
- },
740
- //optional property if You want to specify execution context for the auth handler
741
- handlerContext: {},
742
- required: true, // false You want "soft" checking
554
+ | `inputProperty` | Reads from |
555
+ | ----------------------- | -------------------- |
556
+ | `headers.x-api-key` | Request header |
557
+ | `headers.authorization` | Authorization header |
558
+ | `query.api_token` | Query parameter |
559
+ | `body.token` | Request body field |
560
+ | `path.token` | Path parameter |
561
+
562
+ The resolved value from `handler` is set at `outputProperty` on the request object, making it available as `req.auth.user` in your handler.
563
+
564
+ **Optional auth** — set `required: false` for soft checking (handler runs but failure doesn't block the request):
565
+
566
+ ```ts
567
+ export const optionalAuth: RequestAuthentication = {
568
+ required: false, // won't return 401 if token is missing or invalid
569
+ inputProperty: 'headers.authorization',
570
+ outputProperty: 'auth.user',
571
+ handler: (token) => UserService.findByToken(token),
743
572
  error: {
744
573
  statusCode: 401,
745
574
  message: 'Unauthorized',
@@ -747,310 +576,145 @@ const bearerAuth: RequestAuthentication = {
747
576
  };
748
577
  ```
749
578
 
750
- 3.2.2 Context.options
751
-
752
- ```typescript
753
- interface ContextOptions {
754
- allowUnknownFields?: boolean;
755
- documentProperty?: {
756
- description?: string;
757
- operationId: string;
758
- tags?: string[];
759
- };
760
- }
761
- ```
762
-
763
- Setting "allowUnknownFields" to false protects your application from receiving unexpected properties in the request body, query, and files. It does not affect request.path, as path variables are defined at the routing level, nor does it affect request headers. Conversely, you can receive { name: 'John Doe', role: 'Admin' } when only expecting name.
764
-
765
- The "documentProperty" property is part of the Open API route definition and is optional.
579
+ ---
766
580
 
767
- 3.2.3 Context.request
581
+ ## File Uploads
768
582
 
769
- Context.request is aggregating object that describes all HTTP request properties You expect
583
+ 1. Define a files DTO:
770
584
 
771
- ```typescript
772
- interface ContextRequest {
773
- headers?: Record<string, PropertyDefinition>;
774
- query?: Record<string, PropertyDefinition>;
775
- path?: Record<string, PropertyDefinition>;
776
- body?: Record<string, PropertyDefinition>;
777
- files?: Record<string, FilePropertyDefinition>;
778
- auth?: Record<string, any>;
779
- }
780
- ```
781
-
782
- There's an example for request with path property only:
783
-
784
- ```typescript
785
- const request: ContextRequest = {
786
- path: {
787
- id: {
788
- documentProperty: {
789
- schema: {
790
- type: 'string',
791
- },
792
- example: '123456',
793
- description: 'User id',
794
- },
795
- validation: {
796
- required: true,
797
- handler(value) {
798
- return /^\d+$/.test(value);
799
- },
800
- transform(value) {
801
- return parseInt(value, 10);
802
- },
803
- error: {
804
- statusCode: 400,
805
- message: '400.1.0',
806
- },
807
- },
808
- },
809
- },
810
- };
811
- ```
585
+ ```ts
586
+ // src/modules/user/dto/user-files.dto.ts
587
+ import { fileUploadProperty } from 'alado';
812
588
 
813
- Validation.handler returns boolean (any => boolean | any => Promise<boolean>) if received value fits to provided requirements,
814
- and in falsy case it throws an error and app returns 400 Bad Request with a json body contains specified message
815
- Handler can be async and can use complex logic under the hood; it allows to use
816
- Avoid using an arrow functions as handlers - in this case You can lose execution context,
817
- The execution context of validation.handler (and validation transform) is the request
818
- (aggregating object - the argument used at the route handler)
819
-
820
- ```typescript
821
- const request: ContextRequest = {
822
- body: {
823
- role: {
824
- documentProperty: {
825
- schema: {
826
- type: 'string',
827
- },
828
- example: 'admin',
829
- description: 'User role',
830
- },
831
- validation: {
832
- required: true,
833
- async handler(value) {
834
- if (this.auth.user.role === 'admin') {
835
- return true;
836
- }
837
- await userService.makeSomeChecking(this.query.scope);
838
- return value === 'customer';
839
- },
840
- error: {
841
- statusCode: 400,
842
- message: '400.1.0',
843
- },
844
- },
589
+ export class UserFilesDto {
590
+ @fileUploadProperty({
591
+ mimetypes: ['image/png'],
592
+ maxSize: 1048576, // 1MB
593
+ required: true,
594
+ maxSizeError: {
595
+ statusCode: 413,
596
+ message: 'Max size is 1MB',
845
597
  },
846
- },
847
- };
848
- ```
849
-
850
- In the same way You can describe the request headers, query, body. Remember, if context.options.allowUnknownFields
851
- is set to false, all non-described request properties (or non-described nested properties in the described request properties) never be accessible.
852
-
853
- ```typescript
854
- const request: ContextRequest = {
855
- query: {
856
- sortBy: {
857
- documentProperty: {
858
- schema: {
859
- type: 'string',
860
- },
861
- example: 'createdAt',
862
- description: 'Sort by field',
863
- },
864
- validation: {
865
- required: true,
866
- handler(value) {
867
- return [
868
- 'createdAt',
869
- 'updatedAt',
870
- ].includes(value);
871
- },
872
- error: {
873
- statusCode: 400,
874
- message: 'sortBy should be createdAt or updatedAt',
875
- },
876
- },
598
+ mimetypeError: {
599
+ statusCode: 415,
600
+ message: 'Only PNG allowed',
877
601
  },
878
- sortOrder: {
879
- documentProperty: {
880
- schema: {
881
- type: 'string',
882
- },
883
- example: 'asc',
884
- description: 'Sort order',
885
- },
886
- validation: {
887
- required: true,
888
- handler(value) {
889
- return [
890
- 'asc',
891
- 'desc',
892
- ].includes(value);
893
- },
894
- error: {
895
- statusCode: 400,
896
- message: 'sortOrder should be asc or desc',
897
- },
898
- },
602
+ requiredError: {
603
+ statusCode: 400,
604
+ message: 'File is required',
899
605
  },
900
- },
901
- };
606
+ })
607
+ avatar: string = '';
608
+ }
902
609
  ```
903
610
 
904
- Now, if you send GET /user?sortBy=updateAt&sortOrder=desc&limit=10, the limit parameter will be ignored since it wasn't described:
905
-
906
- ```typescript
907
- app.post('/user', context, (request: Request) => {
908
- console.log(request.query);
611
+ 2. Use in controller:
909
612
 
910
- // {
911
- // sortBy: 'updatedAt',
912
- // sortOrder: 'desc'
913
- // }
613
+ ```ts
614
+ import { createWriteStream } from 'fs';
914
615
 
915
- console.log(request.ip);
616
+ @post('/user/:id/avatar', { tags: ['User'] })
617
+ @withAuth(bearerAuth)
618
+ @defineRequest({ path: Id, files: UserFilesDto })
619
+ setAvatar(req) {
620
+ const { avatar } = req.files;
621
+ // avatar = { stream: Readable, size: number, mimetype: string }
916
622
 
917
- // 127.0.0.1
623
+ const writeStream = createWriteStream(`./uploads/user-${req.path.id}.png`);
624
+ avatar.stream.pipe(writeStream);
918
625
 
919
626
  return {
920
- statusCode: 200,
921
- headers: {
922
- 'Content-Type': 'application/json',
923
- },
924
- body: [],
627
+ statusCode: 202,
628
+ headers: { 'Content-Type': 'application/json' },
629
+ body: { message: 'Upload accepted' },
925
630
  };
926
- });
631
+ }
927
632
  ```
928
633
 
929
- Regarding the request body even it described properly it will be available only for POST, PUT and PATCH HTTP requests.
930
- If You want dealing with uploaded files You just should to describe it in the request:
931
-
932
- ```typescript
933
- const request: ContextRequest = {
934
- files: {
935
- avatar: {
936
- mimetypes: ['image/png'],
937
- maxSize: 1048576,
938
- required: true,
939
- maxSizeError: {
940
- statusCode: 413,
941
- message: 'The avatar should not be larger than 1MB',
942
- },
943
- mimetypeError: {
944
- statusCode: 415,
945
- message: 'The avatar should be a PNG image',
946
- },
947
- requiredError: {
948
- statusCode: 400,
949
- message: 'The avatar file is required',
634
+ ---
635
+
636
+ ## OpenAPI / Swagger
637
+
638
+ Alado generates OpenAPI 3.0 documentation automatically from your decorators. No extra config needed — just enable it in server options:
639
+
640
+ ```ts
641
+ const app = initializeApplication({
642
+ serverOptions: {
643
+ port: 3000,
644
+ openApiDoc: {
645
+ enable: true,
646
+ route: '/', // serve Swagger UI at this path
647
+ info: {
648
+ title: 'My API',
649
+ description: 'Auto-generated with Alado',
650
+ version: '1.0.0',
950
651
  },
951
652
  },
952
653
  },
953
- };
654
+ controllers: [UserController],
655
+ });
954
656
  ```
955
657
 
956
- and uploaded file will be acceptable in the request:
658
+ Open `http://localhost:3000/` to view the Swagger UI.
957
659
 
958
- ```typescript
959
- import { createWriteStream } from 'fs';
960
-
961
- app.post('/user/:id/avatar', context, (request: Request) => {
962
- console.log(request.files);
963
- // {
964
- // avatar: {
965
- // stream: Readable,
966
- // size: 65025,
967
- // mimetype: 'image/png'
968
- // }
969
- // }
970
-
971
- // You can play with request.files.avatar.stream as You want e.g. save it locally
660
+ Documentation is built from:
972
661
 
973
- const writeStream = createWriteStream(`/path/to/user/avatars/${req.path.id}.png`);
974
- request.files.avatar.stream.pipe(writeStream);
975
- return {
976
- statusCode: 200,
977
- headers: {
978
- 'Content-Type': 'application/json',
979
- },
980
- body: user,
981
- };
982
- });
983
- ```
662
+ - `@get`, `@post`, etc. → endpoint paths and methods
663
+ - `tags`, `title`, `description` in decorator options → grouping and descriptions
664
+ - `@defineRequest` + DTO `@documentProperty` → request schema
665
+ - `@defineResponse` → response schema
666
+ - `isHidden: true` → excludes endpoint from docs
984
667
 
985
- 3.2.4 Context response
668
+ ---
986
669
 
987
- Context.response is always the same as the return type of the route handler
670
+ ## API Reference
988
671
 
989
- ```typescript
990
- interface Response<T> {
991
- title?: string;
992
- description?: string;
993
- statusCode: number;
994
- headers?: Record<string, string>;
995
- body?: T;
996
- }
997
- ```
672
+ ### `initializeApplication(options)`
998
673
 
999
- 3.3 Route handler
674
+ | Property | Type | Description |
675
+ | --------------- | ---------------------------------- | ------------------------------ |
676
+ | `serverOptions` | `AladoServerOptions` | Server configuration |
677
+ | `controllers` | `AnyClass[] \| ControllerConfig[]` | Controller classes to register |
1000
678
 
1001
- The route handler is the function that implements the following contract:
679
+ ### `AladoServerOptions`
1002
680
 
1003
- ```typescript
1004
- (request: Request) => Response<T> | Promise<Response<T>>;
1005
- ```
681
+ | Property | Type | Default | Description |
682
+ | -------- | ---------------------- | ------- | ----------------------------------- |
683
+ | `port` | `number` | `3000` | HTTP port |
684
+ | `appId` | `string` | — | App identifier for multi-app setups |
685
+ | `logger` | `AladoServerLogger` | — | Custom logger |
686
+ | `ssl` | `SecureContextOptions` | — | TLS/HTTPS options |
1006
687
 
1007
- Despite having a strict contract for the return type,
1008
- the response body can be an object (for application/json content-type), text, or even a stream
688
+ ### `Request`
1009
689
 
1010
- ```typescript
1011
- interface UserDto {
1012
- id: number;
1013
- name: string;
1014
- }
690
+ The object received in every route handler:
1015
691
 
1016
- const handlerWithJSONBody = (request: Request): Response<UserDto> => {
1017
- const body: UserDto = {
1018
- id: 1,
1019
- name: 'John Doe',
1020
- };
1021
- return {
1022
- statusCode: 200,
1023
- headers: { 'Content-Type': 'application/json' },
1024
- body,
1025
- };
1026
- };
692
+ | Property | Type | Description |
693
+ | --------- | ---------------------------- | ---------------------------------- |
694
+ | `request` | `IncomingMessage` | Raw Node.js request |
695
+ | `ip` | `string` | Client IP address |
696
+ | `method` | `string` | HTTP method |
697
+ | `url` | `string` | Request URL |
698
+ | `headers` | `IncomingHttpHeaders` | Request headers |
699
+ | `path` | `Record<string, string>` | Path parameters |
700
+ | `query` | `Record<string, any>` | Query parameters |
701
+ | `body` | `Record<string, any>` | Request body (POST/PUT/PATCH only) |
702
+ | `rawBody` | `string` | Unparsed body string |
703
+ | `files` | `Record<string, FileResult>` | Uploaded files |
704
+ | `auth` | `Record<string, any>` | Auth handler output |
1027
705
 
1028
- const handlerWithStringBody = (request: Request): Response<string> => {
1029
- const body: string = 'Not Found';
1030
- return {
1031
- statusCode: 404,
1032
- headers: { 'Content-Type': 'text/plain' },
1033
- body,
1034
- };
1035
- };
706
+ ### `Response<T>`
1036
707
 
1037
- const handlerWithStreamBody = (request: Request): Response<ReadStream> => {
1038
- const body = createReadStream('/path/to/image.png');
1039
- return {
1040
- statusCode: 200,
1041
- headers: { 'Content-Type': 'image/png' },
1042
- body,
1043
- };
1044
- };
1045
- ```
708
+ The object your handler must return:
1046
709
 
1047
- 4. Running
710
+ | Property | Type | Required | Description |
711
+ | ------------ | ------------------------ | -------- | ----------------------------------------------- |
712
+ | `statusCode` | `number` | required | HTTP status code |
713
+ | `headers` | `Record<string, string>` | — | Response headers |
714
+ | `body` | `T` | — | Response body — object, string, or `ReadStream` |
1048
715
 
1049
- ```typescript
1050
- app.start(() => console.log('Your awesome application has been successfully launched'));
716
+ ---
1051
717
 
1052
- // And stop the application when needed
1053
- app.stop(() => console.log('Your awesome application has been successfully launched'));
1054
- ```
718
+ ## License
1055
719
 
1056
- Don't forget to check swagger at the defined above options.documentProperty.route
720
+ MIT © [Databikers Limited](https://databikers.com)