alado 2.2.1 → 2.2.2

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,361 @@
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
+ }
26
95
  ```
27
96
 
28
- #### Arguments
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
+ });
29
106
 
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
107
+ app.start(() => console.log('Running on http://localhost:3000'));
108
+ ```
37
109
 
38
110
  ---
39
111
 
40
- ### `defineRequest` decorator
112
+ ## Project Structure
113
+
114
+ Possible layout for a decorator-based Alado project:
115
+
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
+ ```
133
+
134
+ ---
135
+
136
+ ## tsconfig Setup
137
+
138
+ Decorators require these compiler options:
139
+
140
+ ```json
141
+ {
142
+ "compilerOptions": {
143
+ "experimentalDecorators": true,
144
+ "emitDecoratorMetadata": true,
145
+ "target": "ES2017",
146
+ "module": "commonjs",
147
+ "strict": false,
148
+ "baseUrl": "./src",
149
+ "paths": {
150
+ "@user": ["modules/user/index.ts"],
151
+ "@config": ["config/index.ts"]
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Application Setup
160
+
161
+ ### `initializeApplication`
41
162
 
42
163
  ```ts
43
- defineRequest(request: Partial<Record<keyof [Request](#request), AnyClass>>)
164
+ import { initializeApplication } from 'alado';
165
+ import { UserController } from '@user';
166
+
167
+ const app = initializeApplication({
168
+ serverOptions: {
169
+ port: 3000,
170
+ appId: 'my-api', // optional — used to scope multi-app setups
171
+ logger: {
172
+ log: console.log,
173
+ error: console.error,
174
+ },
175
+ },
176
+ controllers: [UserController],
177
+ });
178
+
179
+ app.start(() => console.log('Started'));
180
+ app.stop(() => console.log('Stopped'));
44
181
  ```
45
182
 
46
- #### Arguments
183
+ ### Passing options to controller constructors
47
184
 
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.
185
+ ```ts
186
+ initializeApplication({
187
+ serverOptions: { port: 3000 },
188
+ controllers: [
189
+ {
190
+ controller: UserController,
191
+ options: [{ service: UserService }], // passed to new UserController(...)
192
+ },
193
+ ],
194
+ });
195
+ ```
54
196
 
55
- #### `Request`
197
+ ### HTTPS
56
198
 
57
199
  ```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
- }
200
+ import { readFileSync } from 'fs';
201
+
202
+ const app = initializeApplication({
203
+ serverOptions: {
204
+ port: 443,
205
+ ssl: {
206
+ cert: readFileSync('/path/to/cert.pem'),
207
+ key: readFileSync('/path/to/key.pem'),
208
+ },
209
+ },
210
+ controllers: [UserController],
211
+ });
72
212
  ```
73
213
 
74
214
  ---
75
215
 
76
- ### `defineResponse` decorator
216
+ ## Controllers
217
+
218
+ Controllers are plain TypeScript classes decorated with HTTP method decorators.
219
+
220
+ ### HTTP Method Decorators
77
221
 
78
222
  ```ts
79
- defineResponse(response: [Response](#response)<any>)
223
+ import { get, post, put, patch, del, head } from 'alado';
80
224
  ```
81
225
 
82
- #### Arguments
226
+ | Decorator | Method |
227
+ | ------------------------ | ------ |
228
+ | `@get(path, options?)` | GET |
229
+ | `@post(path, options?)` | POST |
230
+ | `@put(path, options?)` | PUT |
231
+ | `@patch(path, options?)` | PATCH |
232
+ | `@del(path, options?)` | DELETE |
233
+ | `@head(path, options?)` | HEAD |
83
234
 
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).
235
+ **Options:**
90
236
 
91
- #### `Response`
237
+ ```ts
238
+ interface HttpDecoratorOptions {
239
+ appId?: string; // scope to a specific app instance
240
+ title?: string; // endpoint name in OpenAPI
241
+ description?: string; // endpoint description in OpenAPI
242
+ tags?: string[]; // grouping tags in OpenAPI
243
+ isHidden?: boolean; // exclude from OpenAPI output
244
+ }
245
+ ```
246
+
247
+ **Example:**
92
248
 
93
249
  ```ts
94
- interface Response<T> {
95
- title?: string;
96
- description?: string;
97
- statusCode: number;
98
- headers?: Record<string, string>;
99
- body?: T;
250
+ @get('/users', { tags: ['User'], title: 'List users' })
251
+ async list(req: Request) {
252
+ return {
253
+ statusCode: 200,
254
+ headers: { 'Content-Type': 'application/json' },
255
+ body: [],
256
+ };
100
257
  }
101
258
  ```
102
259
 
103
260
  ---
104
261
 
105
- ### `withAuth` decorator
262
+ ### `defineRequest`
263
+
264
+ Declares and validates the incoming request shape.
106
265
 
107
266
  ```ts
108
- withAuth(requestAuthentication: [RequestAuthentication](#requestauthentication))
267
+ @defineRequest({
268
+ headers: AnyClass,
269
+ query: AnyClass,
270
+ path: AnyClass,
271
+ body: AnyClass,
272
+ files: AnyClass,
273
+ auth: AnyClass,
274
+ })
109
275
  ```
110
276
 
111
- #### Arguments
112
-
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.
277
+ Each value is a DTO class whose properties carry `@validateProperty`, `@documentProperty`, etc.
120
278
 
121
- #### `RequestAuthentication`
279
+ **Example:**
122
280
 
123
281
  ```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;
282
+ @post('/user', { tags: ['User'] })
283
+ @defineRequest({ body: CredentialsDto })
284
+ async create(req: Request) {
285
+ console.log(req.body); // typed, validated CredentialsDto fields
131
286
  }
132
287
  ```
133
288
 
134
- ```typescript
135
- import { RequestAuthentication } from 'alado';
136
- import { DataStore } from '@data-store';
289
+ ---
137
290
 
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
- };
291
+ ### `defineResponse`
292
+
293
+ Documents the expected response shape for OpenAPI output.
294
+
295
+ ```ts
296
+ @defineResponse({
297
+ statusCode: 201,
298
+ title: 'Created',
299
+ entity: 'User',
300
+ description: 'User was created successfully',
301
+ headers: { 'Content-Type': 'application/json' },
302
+ body: exampleUserDto, // an instance of your DTO class
303
+ })
150
304
  ```
151
305
 
152
- #### `AladoServerError`
306
+ ---
307
+
308
+ ### `withAuth`
309
+
310
+ Attaches an authentication pipeline to a route.
153
311
 
154
312
  ```ts
155
- type AladoServerError = {
156
- statusCode: number;
157
- message: string;
158
- };
313
+ @patch('/user/:id', { tags: ['User'] })
314
+ @withAuth(bearerAuth)
315
+ @defineRequest({ path: Id, body: UserDto })
316
+ async update(req: Request) {
317
+ console.log(req.auth.user); // injected by the auth handler
318
+ }
159
319
  ```
160
320
 
321
+ See [Authentication](#authentication) for full config details.
322
+
161
323
  ---
162
324
 
163
- Example of controller:
325
+ ### Full controller example
164
326
 
165
327
  ```ts
328
+ import { get, post, patch, del, defineRequest, defineResponse, withAuth } from 'alado';
329
+ import { bearerAuth } from './auth/bearer.auth';
330
+ import { CredentialsDto, UserDto, Id } from './dto';
331
+
166
332
  export class UserController {
333
+ @get('/users', { tags: ['User'] })
334
+ @defineResponse({
335
+ statusCode: 200,
336
+ headers: { 'Content-Type': 'application/json' },
337
+ body: [],
338
+ })
339
+ async list(req) {
340
+ return {
341
+ statusCode: 200,
342
+ headers: { 'Content-Type': 'application/json' },
343
+ body: [],
344
+ };
345
+ }
346
+
167
347
  @post('/user', { tags: ['User'] })
168
348
  @defineResponse({
169
349
  statusCode: 201,
170
- title: 'Created',
171
- entity: 'User',
172
350
  headers: { 'Content-Type': 'application/json' },
173
- body: exampleUserDto,
351
+ body: {},
174
352
  })
175
353
  @defineRequest({ body: CredentialsDto })
176
- public async create(req: Request) {
177
- const { body } = req;
178
- const user = DataStore.signUp(body as CredentialsDto);
354
+ async create(req) {
179
355
  return {
180
356
  statusCode: 201,
181
357
  headers: { 'Content-Type': 'application/json' },
182
- body: user,
358
+ body: req.body,
183
359
  };
184
360
  }
185
361
 
@@ -187,70 +363,29 @@ export class UserController {
187
363
  @withAuth(bearerAuth)
188
364
  @defineResponse({
189
365
  statusCode: 200,
190
- title: 'OK',
191
- enity: 'User',
192
366
  headers: { 'Content-Type': 'application/json' },
193
- body: exampleUserDto,
367
+ body: {},
194
368
  })
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
- }
369
+ @defineRequest({
370
+ path: Id,
371
+ body: UserDto,
372
+ })
373
+ async update(req) {
210
374
  return {
211
- statusCode: user ? 200 : 404,
375
+ statusCode: 200,
212
376
  headers: { 'Content-Type': 'application/json' },
213
- body: DataStore.getUser(path.id) || { message: 'Not Found' },
377
+ body: req.body,
214
378
  };
215
379
  }
216
380
 
217
- @post('/user/:id/avatar', { tags: ['User'] })
381
+ @del('/user/:id', { tags: ['User'] })
218
382
  @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);
383
+ @defineRequest({ path: Id })
384
+ async remove(req) {
250
385
  return {
251
- statusCode: 202,
252
- headers: { 'Content-Type': 'application/json' },
253
- body: {},
386
+ statusCode: 204,
387
+ headers: {},
388
+ body: null,
254
389
  };
255
390
  }
256
391
  }
@@ -258,93 +393,77 @@ export class UserController {
258
393
 
259
394
  ---
260
395
 
261
- ## Use these decorators inside DTO classes:
396
+ ## DTOs
262
397
 
263
- ### `fileUploadProperty`
264
-
265
- ```ts
266
- fileUploadProperty(filePropertyDefinition: [FilePropertyDefinition](#filepropertydefinition))
267
- ```
268
-
269
- #### Arguments
398
+ DTOs are plain TypeScript classes with decorated properties. They define how request data is validated, transformed, and documented.
270
399
 
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.
400
+ ### `validateProperty`
276
401
 
277
- #### `FilePropertyDefinition`
402
+ Validates an incoming property value. Runs before your handler.
278
403
 
279
404
  ```ts
280
- interface FilePropertyDefinition {
281
- mimetypes: string[];
282
- maxSize: number;
283
- required: boolean;
284
- mimetypeError: AladoServerError;
285
- maxSizeError: AladoServerError;
286
- requiredError: AladoServerError;
405
+ import { validateProperty } from 'alado';
406
+
407
+ class CredentialsDto {
408
+ @validateProperty({
409
+ required: true,
410
+ handler: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
411
+ error: {
412
+ statusCode: 400,
413
+ message: 'Invalid email address',
414
+ },
415
+ })
416
+ email: string = 'user@example.com';
287
417
  }
288
418
  ```
289
419
 
420
+ - `handler` receives the raw value and must return `boolean | Promise<boolean>`
421
+ - returning `false` → framework returns the configured `error` automatically
422
+ - `handler` is called with `this` bound to the full `Request` object — you can access `this.auth`, `this.query`, etc.
423
+ - **do not use arrow functions** if you need access to `this`
424
+
290
425
  ---
291
426
 
292
427
  ### `documentProperty`
293
428
 
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`
429
+ Adds OpenAPI/Swagger metadata to a property.
306
430
 
307
431
  ```ts
308
- interface PropertyDocumentation {
309
- schema: PropertyDefinitionSchema;
310
- description?: string;
311
- example?: any;
432
+ import { documentProperty } from 'alado';
433
+
434
+ class UserDto {
435
+ @documentProperty({
436
+ schema: {
437
+ type: 'string',
438
+ minLength: 2,
439
+ maxLength: 50,
440
+ },
441
+ description: 'The user display name',
442
+ example: 'John Doe',
443
+ })
444
+ name: string = 'John Doe';
312
445
  }
313
446
  ```
314
447
 
315
- #### `PropertyDefinitionSchema`
448
+ Supports the full OpenAPI `PropertyDefinitionSchema`:
316
449
 
317
450
  ```ts
318
451
  interface PropertyDefinitionSchema {
319
- type?: string;
320
- format?: string;
452
+ type?: string; // 'string' | 'number' | 'boolean' | 'object' | 'array'
453
+ format?: string; // 'email' | 'uuid' | 'date-time' | ...
321
454
  default?: any;
322
- $ref?: string;
323
455
  nullable?: boolean;
324
- readOnly?: boolean;
325
- writeOnly?: boolean;
326
- deprecated?: boolean;
327
456
  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
457
  minimum?: number;
337
458
  maximum?: number;
338
- exclusiveMinimum?: number;
339
- exclusiveMaximum?: number;
340
- multipleOf?: number;
341
459
  minLength?: number;
342
460
  maxLength?: number;
343
461
  pattern?: string;
344
- items?: PropertyDefinitionSchema;
345
- minItems?: number;
346
- maxItems?: number;
347
- uniqueItems?: boolean;
462
+ items?: PropertyDefinitionSchema; // for arrays
463
+ properties?: Record<string, PropertyDefinitionSchema>; // for objects
464
+ oneOf?: PropertyDefinitionSchema[];
465
+ anyOf?: PropertyDefinitionSchema[];
466
+ allOf?: PropertyDefinitionSchema[];
348
467
  }
349
468
  ```
350
469
 
@@ -352,350 +471,100 @@ interface PropertyDefinitionSchema {
352
471
 
353
472
  ### `transformProperty`
354
473
 
355
- ```ts
356
- transformProperty(propertyTransformer: (value:any) => any | Promise<any>)
357
-
358
- ```
359
-
360
- ### `validateProperty`
474
+ Transforms a property value before it reaches your handler.
361
475
 
362
476
  ```ts
363
- validateProperty(propertyValidation: [PropertyValidation](#propertyvalidation))
364
- ```
477
+ import { transformProperty } from 'alado';
365
478
 
366
- #### Arguments
479
+ class QueryDto {
480
+ @transformProperty((value) => parseInt(value.toString(), 10))
481
+ page: number = 1;
367
482
 
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;
483
+ @transformProperty((value) => value.trim().toLowerCase())
484
+ email: string = '';
380
485
  }
381
486
  ```
382
487
 
383
488
  ---
384
489
 
385
- DTOs examples:
386
-
387
- ```typescript
388
- import { validateProperty, documentProperty } from 'alado';
490
+ ### `fileUploadProperty`
389
491
 
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
- ```
492
+ Marks a DTO property as a file upload field with MIME type and size validation.
409
493
 
410
- ```typescript
494
+ ```ts
411
495
  import { fileUploadProperty } from 'alado';
412
496
 
413
497
  export class UserFilesDto {
414
498
  @fileUploadProperty({
415
- mimetypes: ['image/png'],
416
- maxSize: 1048576,
499
+ mimetypes: [
500
+ 'image/png',
501
+ 'image/jpeg',
502
+ ],
503
+ maxSize: 2 * 1024 * 1024, // 2MB in bytes
417
504
  required: true,
418
505
  maxSizeError: {
419
506
  statusCode: 413,
420
- message: 'The avatar should not be larger than 1MB',
507
+ message: 'File must not exceed 2MB',
421
508
  },
422
509
  mimetypeError: {
423
510
  statusCode: 415,
424
- message: 'The avatar should be a PNG image',
511
+ message: 'Only PNG and JPEG images are allowed',
425
512
  },
426
513
  requiredError: {
427
514
  statusCode: 400,
428
- message: 'The avatar file is required',
515
+ message: 'Avatar file is required',
429
516
  },
430
517
  })
431
- avatar: string = '/path/to/image.png';
518
+ avatar: string = '/path/to/avatar.png';
432
519
  }
433
520
  ```
434
521
 
435
- ```typescript
436
- import { validateProperty, documentProperty } from 'alado';
522
+ ---
437
523
 
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';
524
+ ### Combining decorators
525
+
526
+ Decorators stack — use them together for full validation + documentation:
455
527
 
528
+ ```ts
529
+ export class Id {
456
530
  @validateProperty({
457
531
  required: true,
458
- handler: async (value: any) => passwordValidator(value),
532
+ handler: (value) => /^[0-9a-f-]{36}$/.test(value),
459
533
  error: {
460
534
  statusCode: 400,
461
- message: 'Invalid password',
535
+ message: 'Invalid UUID',
462
536
  },
463
537
  })
464
538
  @documentProperty({
465
539
  schema: {
466
540
  type: 'string',
541
+ format: 'uuid',
467
542
  },
468
- example: 'securePassword',
469
- description: 'User password',
543
+ example: '7ef5ed25-53b1-432f-96ec-8e35d830eb9c',
544
+ description: 'Resource identifier',
470
545
  })
471
- password: string = 'securePassword';
546
+ id: string = '7ef5ed25-53b1-432f-96ec-8e35d830eb9c';
472
547
  }
473
-
474
- export const credentialsDto = new CredentialsDto();
475
548
  ```
476
549
 
477
550
  ---
478
551
 
479
- ## Initialize the application
480
-
481
- Finally, use the `initializeApplication` function to get an instance you would like to run:
482
-
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.
552
+ ## Authentication
516
553
 
517
- #### `InitializeApplicationOptions`
554
+ Define a `RequestAuthentication` config object and pass it to `@withAuth`.
518
555
 
519
556
  ```ts
520
- interface InitializeApplicationOptions {
521
- serverOptions: AladoServerOptions;
522
- controllers: AnyClass[];
523
- }
524
- ```
525
-
526
- #### `AladoServerOptions`
527
-
528
- ```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
557
+ // src/modules/user/auth/bearer.auth.ts
686
558
  import { RequestAuthentication } from 'alado';
559
+ import { DataStore } from '@data-store';
687
560
 
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
- };
561
+ export const bearerAuth: RequestAuthentication = {
562
+ required: true,
563
+ inputProperty: 'headers.x-api-key', // where to read the token from
564
+ outputProperty: 'auth.user', // where to write the result on req
565
+ handler(value: string) {
566
+ return DataStore.findUserByToken(value); // return user or null/undefined
697
567
  },
698
- required: true, // false You want "soft" checking
699
568
  error: {
700
569
  statusCode: 401,
701
570
  message: 'Unauthorized',
@@ -703,43 +572,26 @@ const bearerAuth: RequestAuthentication = {
703
572
  };
704
573
  ```
705
574
 
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';
575
+ The `inputProperty` supports dot-notation and can point to any part of the request:
723
576
 
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
577
+ | `inputProperty` | Reads from |
578
+ | ----------------------- | -------------------- |
579
+ | `headers.x-api-key` | Request header |
580
+ | `headers.authorization` | Authorization header |
581
+ | `query.api_token` | Query parameter |
582
+ | `body.token` | Request body field |
583
+ | `path.token` | Path parameter |
584
+
585
+ The resolved value from `handler` is set at `outputProperty` on the request object, making it available as `req.auth.user` in your handler.
586
+
587
+ **Optional auth** — set `required: false` for soft checking (handler runs but failure doesn't block the request):
588
+
589
+ ```ts
590
+ export const optionalAuth: RequestAuthentication = {
591
+ required: false, // won't return 401 if token is missing or invalid
592
+ inputProperty: 'headers.authorization',
593
+ outputProperty: 'auth.user',
594
+ handler: (token) => UserService.findByToken(token),
743
595
  error: {
744
596
  statusCode: 401,
745
597
  message: 'Unauthorized',
@@ -747,310 +599,145 @@ const bearerAuth: RequestAuthentication = {
747
599
  };
748
600
  ```
749
601
 
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.
766
-
767
- 3.2.3 Context.request
602
+ ---
768
603
 
769
- Context.request is aggregating object that describes all HTTP request properties You expect
604
+ ## File Uploads
770
605
 
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
- ```
606
+ 1. Define a files DTO:
781
607
 
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
- ```
608
+ ```ts
609
+ // src/modules/user/dto/user-files.dto.ts
610
+ import { fileUploadProperty } from 'alado';
812
611
 
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
- },
612
+ export class UserFilesDto {
613
+ @fileUploadProperty({
614
+ mimetypes: ['image/png'],
615
+ maxSize: 1048576, // 1MB
616
+ required: true,
617
+ maxSizeError: {
618
+ statusCode: 413,
619
+ message: 'Max size is 1MB',
845
620
  },
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
- },
621
+ mimetypeError: {
622
+ statusCode: 415,
623
+ message: 'Only PNG allowed',
877
624
  },
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
- },
625
+ requiredError: {
626
+ statusCode: 400,
627
+ message: 'File is required',
899
628
  },
900
- },
901
- };
629
+ })
630
+ avatar: string = '';
631
+ }
902
632
  ```
903
633
 
904
- Now, if you send GET /user?sortBy=updateAt&sortOrder=desc&limit=10, the limit parameter will be ignored since it wasn't described:
634
+ 2. Use in controller:
905
635
 
906
- ```typescript
907
- app.post('/user', context, (request: Request) => {
908
- console.log(request.query);
909
-
910
- // {
911
- // sortBy: 'updatedAt',
912
- // sortOrder: 'desc'
913
- // }
636
+ ```ts
637
+ import { createWriteStream } from 'fs';
914
638
 
915
- console.log(request.ip);
639
+ @post('/user/:id/avatar', { tags: ['User'] })
640
+ @withAuth(bearerAuth)
641
+ @defineRequest({ path: Id, files: UserFilesDto })
642
+ setAvatar(req) {
643
+ const { avatar } = req.files;
644
+ // avatar = { stream: Readable, size: number, mimetype: string }
916
645
 
917
- // 127.0.0.1
646
+ const writeStream = createWriteStream(`./uploads/user-${req.path.id}.png`);
647
+ avatar.stream.pipe(writeStream);
918
648
 
919
649
  return {
920
- statusCode: 200,
921
- headers: {
922
- 'Content-Type': 'application/json',
923
- },
924
- body: [],
650
+ statusCode: 202,
651
+ headers: { 'Content-Type': 'application/json' },
652
+ body: { message: 'Upload accepted' },
925
653
  };
926
- });
654
+ }
927
655
  ```
928
656
 
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',
657
+ ---
658
+
659
+ ## OpenAPI / Swagger
660
+
661
+ Alado generates OpenAPI 3.0 documentation automatically from your decorators. No extra config needed — just enable it in server options:
662
+
663
+ ```ts
664
+ const app = initializeApplication({
665
+ serverOptions: {
666
+ port: 3000,
667
+ openApiDoc: {
668
+ enable: true,
669
+ route: '/', // serve Swagger UI at this path
670
+ info: {
671
+ title: 'My API',
672
+ description: 'Auto-generated with Alado',
673
+ version: '1.0.0',
950
674
  },
951
675
  },
952
676
  },
953
- };
677
+ controllers: [UserController],
678
+ });
954
679
  ```
955
680
 
956
- and uploaded file will be acceptable in the request:
957
-
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
- // }
681
+ Open `http://localhost:3000/` to view the Swagger UI.
970
682
 
971
- // You can play with request.files.avatar.stream as You want e.g. save it locally
683
+ Documentation is built from:
972
684
 
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
- ```
685
+ - `@get`, `@post`, etc. → endpoint paths and methods
686
+ - `tags`, `title`, `description` in decorator options → grouping and descriptions
687
+ - `@defineRequest` + DTO `@documentProperty` → request schema
688
+ - `@defineResponse` → response schema
689
+ - `isHidden: true` → excludes endpoint from docs
984
690
 
985
- 3.2.4 Context response
691
+ ---
986
692
 
987
- Context.response is always the same as the return type of the route handler
693
+ ## API Reference
988
694
 
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
- ```
695
+ ### `initializeApplication(options)`
998
696
 
999
- 3.3 Route handler
697
+ | Property | Type | Description |
698
+ | --------------- | ---------------------------------- | ------------------------------ |
699
+ | `serverOptions` | `AladoServerOptions` | Server configuration |
700
+ | `controllers` | `AnyClass[] \| ControllerConfig[]` | Controller classes to register |
1000
701
 
1001
- The route handler is the function that implements the following contract:
702
+ ### `AladoServerOptions`
1002
703
 
1003
- ```typescript
1004
- (request: Request) => Response<T> | Promise<Response<T>>;
1005
- ```
704
+ | Property | Type | Default | Description |
705
+ | -------- | ---------------------- | ------- | ----------------------------------- |
706
+ | `port` | `number` | `3000` | HTTP port |
707
+ | `appId` | `string` | — | App identifier for multi-app setups |
708
+ | `logger` | `AladoServerLogger` | — | Custom logger |
709
+ | `ssl` | `SecureContextOptions` | — | TLS/HTTPS options |
1006
710
 
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
711
+ ### `Request`
1009
712
 
1010
- ```typescript
1011
- interface UserDto {
1012
- id: number;
1013
- name: string;
1014
- }
713
+ The object received in every route handler:
1015
714
 
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
- };
715
+ | Property | Type | Description |
716
+ | --------- | ---------------------------- | ---------------------------------- |
717
+ | `request` | `IncomingMessage` | Raw Node.js request |
718
+ | `ip` | `string` | Client IP address |
719
+ | `method` | `string` | HTTP method |
720
+ | `url` | `string` | Request URL |
721
+ | `headers` | `IncomingHttpHeaders` | Request headers |
722
+ | `path` | `Record<string, string>` | Path parameters |
723
+ | `query` | `Record<string, any>` | Query parameters |
724
+ | `body` | `Record<string, any>` | Request body (POST/PUT/PATCH only) |
725
+ | `rawBody` | `string` | Unparsed body string |
726
+ | `files` | `Record<string, FileResult>` | Uploaded files |
727
+ | `auth` | `Record<string, any>` | Auth handler output |
1027
728
 
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
- };
729
+ ### `Response<T>`
1036
730
 
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
- ```
731
+ The object your handler must return:
1046
732
 
1047
- 4. Running
733
+ | Property | Type | Required | Description |
734
+ | ------------ | ------------------------ | -------- | ----------------------------------------------- |
735
+ | `statusCode` | `number` | required | HTTP status code |
736
+ | `headers` | `Record<string, string>` | — | Response headers |
737
+ | `body` | `T` | — | Response body — object, string, or `ReadStream` |
1048
738
 
1049
- ```typescript
1050
- app.start(() => console.log('Your awesome application has been successfully launched'));
739
+ ---
1051
740
 
1052
- // And stop the application when needed
1053
- app.stop(() => console.log('Your awesome application has been successfully launched'));
1054
- ```
741
+ ## License
1055
742
 
1056
- Don't forget to check swagger at the defined above options.documentProperty.route
743
+ MIT © [Databikers Limited](https://databikers.com)