alado 2.2.0 → 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,184 +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:
71
+
72
+ ```ts
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
+ ```
18
96
 
19
97
  ```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 = {})
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'));
108
+ ```
109
+
110
+ ---
111
+
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
26
132
  ```
27
133
 
28
- #### Arguments
134
+ ---
29
135
 
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
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
+ ```
37
156
 
38
157
  ---
39
158
 
40
- ### `defineRequest` decorator
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: 'User',
171
350
  headers: { 'Content-Type': 'application/json' },
172
- body: exampleUserDto,
351
+ body: {},
173
352
  })
174
353
  @defineRequest({ body: CredentialsDto })
175
- public async create(req: Request) {
176
- const { body } = req;
177
- const user = DataStore.signUp(body as CredentialsDto);
354
+ async create(req) {
178
355
  return {
179
356
  statusCode: 201,
180
357
  headers: { 'Content-Type': 'application/json' },
181
- body: user,
358
+ body: req.body,
182
359
  };
183
360
  }
184
361
 
@@ -186,68 +363,29 @@ export class UserController {
186
363
  @withAuth(bearerAuth)
187
364
  @defineResponse({
188
365
  statusCode: 200,
189
- title: 'User',
190
366
  headers: { 'Content-Type': 'application/json' },
191
- body: exampleUserDto,
367
+ body: {},
192
368
  })
193
- @defineRequest({ body: UserDto, path: Id })
194
- public async update(req: Request) {
195
- const isMyId = this.isMyId(req);
196
- if (!isMyId) {
197
- return {
198
- statusCode: 403,
199
- headers: { 'Content-Type': 'application/json' },
200
- body: { message: 'Access denied' },
201
- };
202
- }
203
- const { path, body } = req;
204
- const user = DataStore.getUser(path.id);
205
- if (user) {
206
- DataStore.setUser(path.id, body as UserDto);
207
- }
369
+ @defineRequest({
370
+ path: Id,
371
+ body: UserDto,
372
+ })
373
+ async update(req) {
208
374
  return {
209
- statusCode: user ? 200 : 404,
375
+ statusCode: 200,
210
376
  headers: { 'Content-Type': 'application/json' },
211
- body: DataStore.getUser(path.id) || { message: 'Not Found' },
377
+ body: req.body,
212
378
  };
213
379
  }
214
380
 
215
- @post('/user/:id/avatar', { tags: ['User'] })
381
+ @del('/user/:id', { tags: ['User'] })
216
382
  @withAuth(bearerAuth)
217
- @defineResponse({
218
- statusCode: 200,
219
- title: 'User',
220
- headers: { 'Content-Type': 'application/json' },
221
- body: exampleUserDto,
222
- })
223
- @defineRequest({ path: Id, files: UserFilesDto })
224
- public setAvatar(req: Request) {
225
- const isMyId = this.isMyId(req);
226
- if (!isMyId) {
227
- return {
228
- statusCode: 403,
229
- headers: { 'Content-Type': 'application/json' },
230
- body: { message: 'Access denied' },
231
- };
232
- }
233
- const { path, files } = req;
234
- const user = DataStore.getUser(path.id);
235
- if (!user) {
236
- return {
237
- statusCode: 404,
238
- headers: { 'Content-Type': 'application/json' },
239
- body: { message: 'Not Found' },
240
- };
241
- }
242
- const { avatar } = files;
243
- const writeStream = createWriteStream(`${process.cwd()}/uploads/user-${path.id}-avatar.png`, {
244
- encoding: 'latin1',
245
- });
246
- avatar.stream.pipe(writeStream);
383
+ @defineRequest({ path: Id })
384
+ async remove(req) {
247
385
  return {
248
- statusCode: 202,
249
- headers: { 'Content-Type': 'application/json' },
250
- body: {},
386
+ statusCode: 204,
387
+ headers: {},
388
+ body: null,
251
389
  };
252
390
  }
253
391
  }
@@ -255,93 +393,77 @@ export class UserController {
255
393
 
256
394
  ---
257
395
 
258
- ## Use these decorators inside DTO classes:
396
+ ## DTOs
259
397
 
260
- ### `fileUploadProperty`
398
+ DTOs are plain TypeScript classes with decorated properties. They define how request data is validated, transformed, and documented.
261
399
 
262
- ```ts
263
- fileUploadProperty(filePropertyDefinition: [FilePropertyDefinition](#filepropertydefinition))
264
- ```
265
-
266
- #### Arguments
267
-
268
- - **filePropertyDefinition**: Object describing file upload rules, including:
269
- - `mimetypes`: Array of allowed MIME types.
270
- - `maxSize`: Maximum file size (bytes).
271
- - `required`: Whether the file must be provided.
272
- - `mimetypeError`, `maxSizeError`, `requiredError`: Error objects describing validation failures.
400
+ ### `validateProperty`
273
401
 
274
- #### `FilePropertyDefinition`
402
+ Validates an incoming property value. Runs before your handler.
275
403
 
276
404
  ```ts
277
- interface FilePropertyDefinition {
278
- mimetypes: string[];
279
- maxSize: number;
280
- required: boolean;
281
- mimetypeError: AladoServerError;
282
- maxSizeError: AladoServerError;
283
- 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';
284
417
  }
285
418
  ```
286
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
+
287
425
  ---
288
426
 
289
427
  ### `documentProperty`
290
428
 
291
- ```ts
292
- documentProperty(propertyDocumentation: [PropertyDocumentation](#propertydocumentation))
293
- ```
294
-
295
- #### Arguments
296
-
297
- - **propertyDocumentation**: Documentation metadata for a property, including:
298
- - `schema`: JSON Schema–like definition (`type`, `format`, validation structure).
299
- - `description`: Human-readable explanation.
300
- - `example`: Example value.
301
-
302
- #### `PropertyDocumentation`
429
+ Adds OpenAPI/Swagger metadata to a property.
303
430
 
304
431
  ```ts
305
- interface PropertyDocumentation {
306
- schema: PropertyDefinitionSchema;
307
- description?: string;
308
- 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';
309
445
  }
310
446
  ```
311
447
 
312
- #### `PropertyDefinitionSchema`
448
+ Supports the full OpenAPI `PropertyDefinitionSchema`:
313
449
 
314
450
  ```ts
315
451
  interface PropertyDefinitionSchema {
316
- type?: string;
317
- format?: string;
452
+ type?: string; // 'string' | 'number' | 'boolean' | 'object' | 'array'
453
+ format?: string; // 'email' | 'uuid' | 'date-time' | ...
318
454
  default?: any;
319
- $ref?: string;
320
455
  nullable?: boolean;
321
- readOnly?: boolean;
322
- writeOnly?: boolean;
323
- deprecated?: boolean;
324
456
  enum?: any[];
325
- oneOf?: Array<PropertyDefinitionSchema>;
326
- anyOf?: Array<PropertyDefinitionSchema>;
327
- allOf?: Array<PropertyDefinitionSchema>;
328
- not?: Array<PropertyDefinitionSchema>;
329
- properties?: Record<string, PropertyDefinitionSchema>;
330
- additionalProperties?: PropertyDefinitionSchema;
331
- minProperties?: number;
332
- maxProperties?: number;
333
457
  minimum?: number;
334
458
  maximum?: number;
335
- exclusiveMinimum?: number;
336
- exclusiveMaximum?: number;
337
- multipleOf?: number;
338
459
  minLength?: number;
339
460
  maxLength?: number;
340
461
  pattern?: string;
341
- items?: PropertyDefinitionSchema;
342
- minItems?: number;
343
- maxItems?: number;
344
- uniqueItems?: boolean;
462
+ items?: PropertyDefinitionSchema; // for arrays
463
+ properties?: Record<string, PropertyDefinitionSchema>; // for objects
464
+ oneOf?: PropertyDefinitionSchema[];
465
+ anyOf?: PropertyDefinitionSchema[];
466
+ allOf?: PropertyDefinitionSchema[];
345
467
  }
346
468
  ```
347
469
 
@@ -349,350 +471,100 @@ interface PropertyDefinitionSchema {
349
471
 
350
472
  ### `transformProperty`
351
473
 
352
- ```ts
353
- transformProperty(propertyTransformer: (value:any) => any | Promise<any>)
354
-
355
- ```
356
-
357
- ### `validateProperty`
474
+ Transforms a property value before it reaches your handler.
358
475
 
359
476
  ```ts
360
- validateProperty(propertyValidation: [PropertyValidation](#propertyvalidation))
361
- ```
362
-
363
- #### Arguments
477
+ import { transformProperty } from 'alado';
364
478
 
365
- - **propertyValidation**: Validation rules, including:
366
- - `required`: Whether the property is mandatory.
367
- - `handler`: Function to validate the property value (sync or async).
368
- - `error`: Error object returned when validation fails.
479
+ class QueryDto {
480
+ @transformProperty((value) => parseInt(value.toString(), 10))
481
+ page: number = 1;
369
482
 
370
- #### `PropertyValidation`
371
-
372
- ```ts
373
- interface PropertyValidation {
374
- required?: boolean;
375
- handler: (value: any) => boolean | Promise<boolean>; // throws error when returns false
376
- error: AladoServerError;
483
+ @transformProperty((value) => value.trim().toLowerCase())
484
+ email: string = '';
377
485
  }
378
486
  ```
379
487
 
380
488
  ---
381
489
 
382
- DTOs examples:
383
-
384
- ```typescript
385
- import { validateProperty, documentProperty } from 'alado';
490
+ ### `fileUploadProperty`
386
491
 
387
- export class Id {
388
- @validateProperty({
389
- required: true,
390
- handler: async (value: any) => idValidator(value),
391
- error: {
392
- statusCode: 400,
393
- message: 'Invalid id',
394
- },
395
- })
396
- @documentProperty({
397
- schema: {
398
- type: 'string',
399
- },
400
- example: '7ef5ed25-53b1-432f-96ec-8e35d830eb9c',
401
- description: 'User Id',
402
- })
403
- id: string = '7ef5ed25-53b1-432f-96ec-8e35d830eb9c';
404
- }
405
- ```
492
+ Marks a DTO property as a file upload field with MIME type and size validation.
406
493
 
407
- ```typescript
494
+ ```ts
408
495
  import { fileUploadProperty } from 'alado';
409
496
 
410
497
  export class UserFilesDto {
411
498
  @fileUploadProperty({
412
- mimetypes: ['image/png'],
413
- maxSize: 1048576,
499
+ mimetypes: [
500
+ 'image/png',
501
+ 'image/jpeg',
502
+ ],
503
+ maxSize: 2 * 1024 * 1024, // 2MB in bytes
414
504
  required: true,
415
505
  maxSizeError: {
416
506
  statusCode: 413,
417
- message: 'The avatar should not be larger than 1MB',
507
+ message: 'File must not exceed 2MB',
418
508
  },
419
509
  mimetypeError: {
420
510
  statusCode: 415,
421
- message: 'The avatar should be a PNG image',
511
+ message: 'Only PNG and JPEG images are allowed',
422
512
  },
423
513
  requiredError: {
424
514
  statusCode: 400,
425
- message: 'The avatar file is required',
515
+ message: 'Avatar file is required',
426
516
  },
427
517
  })
428
- avatar: string = '/path/to/image.png';
518
+ avatar: string = '/path/to/avatar.png';
429
519
  }
430
520
  ```
431
521
 
432
- ```typescript
433
- import { validateProperty, documentProperty } from 'alado';
522
+ ---
434
523
 
435
- export class CredentialsDto {
436
- @validateProperty({
437
- required: true,
438
- handler: async (value: any) => emailValidator(value),
439
- error: {
440
- statusCode: 400,
441
- message: 'Invalid email',
442
- },
443
- })
444
- @documentProperty({
445
- schema: {
446
- type: 'string',
447
- },
448
- example: 'example@example.com',
449
- description: 'User email',
450
- })
451
- email: string = 'example@example.com';
524
+ ### Combining decorators
452
525
 
526
+ Decorators stack — use them together for full validation + documentation:
527
+
528
+ ```ts
529
+ export class Id {
453
530
  @validateProperty({
454
531
  required: true,
455
- handler: async (value: any) => passwordValidator(value),
532
+ handler: (value) => /^[0-9a-f-]{36}$/.test(value),
456
533
  error: {
457
534
  statusCode: 400,
458
- message: 'Invalid password',
535
+ message: 'Invalid UUID',
459
536
  },
460
537
  })
461
538
  @documentProperty({
462
539
  schema: {
463
540
  type: 'string',
541
+ format: 'uuid',
464
542
  },
465
- example: 'securePassword',
466
- description: 'User password',
543
+ example: '7ef5ed25-53b1-432f-96ec-8e35d830eb9c',
544
+ description: 'Resource identifier',
467
545
  })
468
- password: string = 'securePassword';
546
+ id: string = '7ef5ed25-53b1-432f-96ec-8e35d830eb9c';
469
547
  }
470
-
471
- export const credentialsDto = new CredentialsDto();
472
548
  ```
473
549
 
474
550
  ---
475
551
 
476
- ## Initialize the application
477
-
478
- Finally, use the `initializeApplication` function to get an instance you would like to run:
479
-
480
- ```ts
481
- import { initializeApplication } from 'alado';
482
- import { aladoServerOptions } from '@config';
483
- import { UserController } from '@user';
484
-
485
- export const app = initializeApplication({
486
- serverOptions: aladoServerOptions,
487
- controllers: [UserController],
488
- });
489
-
490
- app.start(() => console.log('Application has been successfully started'));
491
- ```
492
-
493
- There's ability to pass args to controller`s constructor:
494
-
495
- ```ts
496
- import { initializeApplication } from 'alado';
497
- import { aladoServerOptions } from '@config';
498
- import { UserController } from '@user';
499
-
500
- export const app = initializeApplication({
501
- serverOptions: aladoServerOptions,
502
- controllers: [{ controller: UserController, options: [{ service: SomeService }] }],
503
- });
504
-
505
- app.start(() => console.log('Application has been successfully started'));
506
- ```
507
-
508
- #### Arguments
509
-
510
- - **initializeApplicationOptions**: Object containing:
511
- - **serverOptions** ([AladoServerOptions](#aladoserveroptions)): Configuration for starting the server (e.g., `appId`, port, logger).
512
- - **controllers**: Array of controller classes to initialize at startup.
513
-
514
- #### `InitializeApplicationOptions`
552
+ ## Authentication
515
553
 
516
- ```ts
517
- interface InitializeApplicationOptions {
518
- serverOptions: AladoServerOptions;
519
- controllers: AnyClass[];
520
- }
521
- ```
522
-
523
- #### `AladoServerOptions`
554
+ Define a `RequestAuthentication` config object and pass it to `@withAuth`.
524
555
 
525
556
  ```ts
526
- interface AladoServerOptions {
527
- appId?: string;
528
- port?: number;
529
- logger?: AladoServerLogger;
530
- }
531
- ```
532
-
533
- #### `AladoServerLogger`
534
-
535
- ```ts
536
- interface AladoServerLogger {
537
- log: (...args: any[]) => void;
538
- error: (error: Error) => void;
539
- }
540
- ```
541
-
542
- There is an example of the API creating with Alado (version < 2.0.0):
543
-
544
- The basis of use is a combination of interface simplicity and separation of the “routine” part of writing API
545
- from business logic implementation - due to a declarative approach to describing all request parameters
546
- in the form of static reusable structures.
547
-
548
- [https://github.com/databikers/alado-api-example](https://github.com/databikers/alado-api-example)
549
-
550
- 1. Installation
551
-
552
- ```shell
553
- npm i -s alado
554
- ```
555
-
556
- 2. Instancing
557
-
558
- ```typescript
559
- import { AladoServer } from 'alado';
560
-
561
- const app: AladoServer = new AladoServer({
562
- port: 3000,
563
- cors: {
564
- enable: true,
565
- allowedOrigin: '*',
566
- allowedHeaders: ['Authorization'],
567
- exposeHeaders: ['x-total-count'],
568
- },
569
- documentProperty: {
570
- enable: true,
571
- route: '/open-api',
572
- info: {
573
- title: 'My API title',
574
- description: 'My API description',
575
- version: '1.0.0',
576
- },
577
- },
578
- verbose: true,
579
- maxBodySizeBytes: 1024,
580
- });
581
- ```
582
-
583
- In case you want to run an HTTPs server (even though normally a Node.js app runs behind nginx/ingress etc.)
584
- You can do it using the ssl parameter (look at SecureContextOptions in the "tls" module -
585
- [Node.js TLS module](https://nodejs.org/api/tls.html#tlscreateserveroptions-secureconnectionlistener):
586
-
587
- ```typescript
588
- import { readFileSync } from 'fs';
589
- import { AladoServer } from 'alado';
590
-
591
- const app = new AladoServer({
592
- port: 3000,
593
- ssl: {
594
- cert: readFileSync('/path/to/cert'),
595
- key: readFileSync('/path/to/key'),
596
- },
597
- // https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener
598
- serverOptions: {
599
- keepAliveTimeout: 5000,
600
- },
601
- cors: {
602
- enable: true,
603
- allowedOrigin: '*',
604
- allowedHeaders: ['Authorization'],
605
- exposeHeaders: ['x-total-count'],
606
- allowedCredentials: true,
607
- maxAge: 3600,
608
- },
609
- headers: {
610
- a: 'b', //any additional header
611
- },
612
- openApiDoc: {
613
- enable: true,
614
- route: '/',
615
- info: {
616
- title: 'My API title',
617
- description: 'My API description',
618
- version: '1.0.0',
619
- },
620
- },
621
- verbose: true,
622
- maxBodySizeBytes: 1024,
623
- });
624
- ```
625
-
626
- 3. Defining routes
627
-
628
- Here is how you define routes in Alado:
629
-
630
- ```typescript
631
- app.get('/user/:id', context, (request: Request): Response<UserDto> => {
632
- const user = {
633
- name: 'John Doe',
634
- id: request.path.id,
635
- };
636
- return {
637
- statusCode: 200,
638
- headers: {
639
- 'Content-Type': 'application/json',
640
- },
641
- body: user,
642
- };
643
- });
644
-
645
- app.start(() => console.log(`[API]: application has been launched successfully`));
646
- ```
647
-
648
- The application provides "get", "post", "delete", "patch", "put" and "head" methods for defining routes.
649
- These methods accept three required arguments:
650
-
651
- - route or path: A string that may contain variables to specify the route.
652
- - context: An aggregating object that fully describes the request.
653
- - handler: A function that handles the request.
654
-
655
- 3.1 Route or path.
656
-
657
- 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)
658
- as ":id" and receive the passed value as request.path.id).
659
-
660
- 3.2 Context
661
-
662
- Here's the contract of the request context:
663
-
664
- ```typescript
665
- interface Context<T> {
666
- title: string;
667
- auth?: RequestAuthentication;
668
- isHidden?: boolean; //Just exclude some route from Your API specification
669
- options: ContextOptions;
670
- request: ContextRequest;
671
- response: Response<T>;
672
- }
673
- ```
674
-
675
- Unlike context.title, which is simply a unique string, the other properties may not be immediately clear.
676
- However, once explained, they become quite obvious and extremely logical.
677
-
678
- **Note that all parts of the context and its nested properties can be reused**
679
-
680
- 3.2.1 Context.auth
681
-
682
- ```typescript
557
+ // src/modules/user/auth/bearer.auth.ts
683
558
  import { RequestAuthentication } from 'alado';
559
+ import { DataStore } from '@data-store';
684
560
 
685
- const bearerAuth: RequestAuthentication = {
686
- inputProperty: 'headers.authorization',
687
- outputProperty: 'auth.user',
688
- handler: async (bearer: string) => {
689
- // validateProperty header, return user
690
- return {
691
- name: 'John Doe',
692
- id: 1,
693
- };
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
694
567
  },
695
- required: true, // false You want "soft" checking
696
568
  error: {
697
569
  statusCode: 401,
698
570
  message: 'Unauthorized',
@@ -700,43 +572,26 @@ const bearerAuth: RequestAuthentication = {
700
572
  };
701
573
  ```
702
574
 
703
- This means that the application takes an "Authorization" header
704
- (or "api_token" query parameter if you provide "query.api_token" as an inputProperty,
705
- "body.someThing" and "path.token" work also)
706
- use the provided handler to get the user and set the received user to request.auth.user,
707
- (and the mentioned "request" here is the request argument in your route handler)
708
- Or just returns 401 Unauthorized.
709
- This means that the application can authenticate using an "Authorization" header, or alternatively,
710
- using an "api_token" query parameter (if specified as "query.api_token" in the inputProperty).
711
- It can also authenticate using values like "body.someThing" or "path.token".
712
- In the example the provided handler retrieves the user and assigns it to request.auth.user
713
- (where request is the argument in your route handler).
714
- If authentication fails, it returns a 401 Unauthorized response.
715
-
716
- This also works:
717
-
718
- ```typescript
719
- import { RequestAuthentication } from 'alado';
575
+ The `inputProperty` supports dot-notation and can point to any part of the request:
720
576
 
721
- const bearerAuth: RequestAuthentication = {
722
- inputProperty: 'header.x-api-token',
723
- outputProperty: 'auth',
724
- handler: async (xApiToken: string) => {
725
- // You will receive user and company as nested properties in request auth
726
- return {
727
- user: {
728
- name: 'John Doe',
729
- id: 1,
730
- },
731
- company: {
732
- name: 'Databikers Limited',
733
- id: 2,
734
- },
735
- };
736
- },
737
- //optional property if You want to specify execution context for the auth handler
738
- handlerContext: {},
739
- 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),
740
595
  error: {
741
596
  statusCode: 401,
742
597
  message: 'Unauthorized',
@@ -744,310 +599,145 @@ const bearerAuth: RequestAuthentication = {
744
599
  };
745
600
  ```
746
601
 
747
- 3.2.2 Context.options
748
-
749
- ```typescript
750
- interface ContextOptions {
751
- allowUnknownFields?: boolean;
752
- documentProperty?: {
753
- description?: string;
754
- operationId: string;
755
- tags?: string[];
756
- };
757
- }
758
- ```
759
-
760
- 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.
761
-
762
- The "documentProperty" property is part of the Open API route definition and is optional.
763
-
764
- 3.2.3 Context.request
602
+ ---
765
603
 
766
- Context.request is aggregating object that describes all HTTP request properties You expect
604
+ ## File Uploads
767
605
 
768
- ```typescript
769
- interface ContextRequest {
770
- headers?: Record<string, PropertyDefinition>;
771
- query?: Record<string, PropertyDefinition>;
772
- path?: Record<string, PropertyDefinition>;
773
- body?: Record<string, PropertyDefinition>;
774
- files?: Record<string, FilePropertyDefinition>;
775
- auth?: Record<string, any>;
776
- }
777
- ```
606
+ 1. Define a files DTO:
778
607
 
779
- There's an example for request with path property only:
780
-
781
- ```typescript
782
- const request: ContextRequest = {
783
- path: {
784
- id: {
785
- documentProperty: {
786
- schema: {
787
- type: 'string',
788
- },
789
- example: '123456',
790
- description: 'User id',
791
- },
792
- validation: {
793
- required: true,
794
- handler(value) {
795
- return /^\d+$/.test(value);
796
- },
797
- transform(value) {
798
- return parseInt(value, 10);
799
- },
800
- error: {
801
- statusCode: 400,
802
- message: '400.1.0',
803
- },
804
- },
805
- },
806
- },
807
- };
808
- ```
608
+ ```ts
609
+ // src/modules/user/dto/user-files.dto.ts
610
+ import { fileUploadProperty } from 'alado';
809
611
 
810
- Validation.handler returns boolean (any => boolean | any => Promise<boolean>) if received value fits to provided requirements,
811
- and in falsy case it throws an error and app returns 400 Bad Request with a json body contains specified message
812
- Handler can be async and can use complex logic under the hood; it allows to use
813
- Avoid using an arrow functions as handlers - in this case You can lose execution context,
814
- The execution context of validation.handler (and validation transform) is the request
815
- (aggregating object - the argument used at the route handler)
816
-
817
- ```typescript
818
- const request: ContextRequest = {
819
- body: {
820
- role: {
821
- documentProperty: {
822
- schema: {
823
- type: 'string',
824
- },
825
- example: 'admin',
826
- description: 'User role',
827
- },
828
- validation: {
829
- required: true,
830
- async handler(value) {
831
- if (this.auth.user.role === 'admin') {
832
- return true;
833
- }
834
- await userService.makeSomeChecking(this.query.scope);
835
- return value === 'customer';
836
- },
837
- error: {
838
- statusCode: 400,
839
- message: '400.1.0',
840
- },
841
- },
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',
842
620
  },
843
- },
844
- };
845
- ```
846
-
847
- In the same way You can describe the request headers, query, body. Remember, if context.options.allowUnknownFields
848
- is set to false, all non-described request properties (or non-described nested properties in the described request properties) never be accessible.
849
-
850
- ```typescript
851
- const request: ContextRequest = {
852
- query: {
853
- sortBy: {
854
- documentProperty: {
855
- schema: {
856
- type: 'string',
857
- },
858
- example: 'createdAt',
859
- description: 'Sort by field',
860
- },
861
- validation: {
862
- required: true,
863
- handler(value) {
864
- return [
865
- 'createdAt',
866
- 'updatedAt',
867
- ].includes(value);
868
- },
869
- error: {
870
- statusCode: 400,
871
- message: 'sortBy should be createdAt or updatedAt',
872
- },
873
- },
621
+ mimetypeError: {
622
+ statusCode: 415,
623
+ message: 'Only PNG allowed',
874
624
  },
875
- sortOrder: {
876
- documentProperty: {
877
- schema: {
878
- type: 'string',
879
- },
880
- example: 'asc',
881
- description: 'Sort order',
882
- },
883
- validation: {
884
- required: true,
885
- handler(value) {
886
- return [
887
- 'asc',
888
- 'desc',
889
- ].includes(value);
890
- },
891
- error: {
892
- statusCode: 400,
893
- message: 'sortOrder should be asc or desc',
894
- },
895
- },
625
+ requiredError: {
626
+ statusCode: 400,
627
+ message: 'File is required',
896
628
  },
897
- },
898
- };
629
+ })
630
+ avatar: string = '';
631
+ }
899
632
  ```
900
633
 
901
- Now, if you send GET /user?sortBy=updateAt&sortOrder=desc&limit=10, the limit parameter will be ignored since it wasn't described:
902
-
903
- ```typescript
904
- app.post('/user', context, (request: Request) => {
905
- console.log(request.query);
634
+ 2. Use in controller:
906
635
 
907
- // {
908
- // sortBy: 'updatedAt',
909
- // sortOrder: 'desc'
910
- // }
636
+ ```ts
637
+ import { createWriteStream } from 'fs';
911
638
 
912
- 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 }
913
645
 
914
- // 127.0.0.1
646
+ const writeStream = createWriteStream(`./uploads/user-${req.path.id}.png`);
647
+ avatar.stream.pipe(writeStream);
915
648
 
916
649
  return {
917
- statusCode: 200,
918
- headers: {
919
- 'Content-Type': 'application/json',
920
- },
921
- body: [],
650
+ statusCode: 202,
651
+ headers: { 'Content-Type': 'application/json' },
652
+ body: { message: 'Upload accepted' },
922
653
  };
923
- });
654
+ }
924
655
  ```
925
656
 
926
- Regarding the request body even it described properly it will be available only for POST, PUT and PATCH HTTP requests.
927
- If You want dealing with uploaded files You just should to describe it in the request:
928
-
929
- ```typescript
930
- const request: ContextRequest = {
931
- files: {
932
- avatar: {
933
- mimetypes: ['image/png'],
934
- maxSize: 1048576,
935
- required: true,
936
- maxSizeError: {
937
- statusCode: 413,
938
- message: 'The avatar should not be larger than 1MB',
939
- },
940
- mimetypeError: {
941
- statusCode: 415,
942
- message: 'The avatar should be a PNG image',
943
- },
944
- requiredError: {
945
- statusCode: 400,
946
- 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',
947
674
  },
948
675
  },
949
676
  },
950
- };
677
+ controllers: [UserController],
678
+ });
951
679
  ```
952
680
 
953
- and uploaded file will be acceptable in the request:
681
+ Open `http://localhost:3000/` to view the Swagger UI.
954
682
 
955
- ```typescript
956
- import { createWriteStream } from 'fs';
957
-
958
- app.post('/user/:id/avatar', context, (request: Request) => {
959
- console.log(request.files);
960
- // {
961
- // avatar: {
962
- // stream: Readable,
963
- // size: 65025,
964
- // mimetype: 'image/png'
965
- // }
966
- // }
683
+ Documentation is built from:
967
684
 
968
- // You can play with request.files.avatar.stream as You want e.g. save it locally
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
969
690
 
970
- const writeStream = createWriteStream(`/path/to/user/avatars/${req.path.id}.png`);
971
- request.files.avatar.stream.pipe(writeStream);
972
- return {
973
- statusCode: 200,
974
- headers: {
975
- 'Content-Type': 'application/json',
976
- },
977
- body: user,
978
- };
979
- });
980
- ```
981
-
982
- 3.2.4 Context response
691
+ ---
983
692
 
984
- Context.response is always the same as the return type of the route handler
693
+ ## API Reference
985
694
 
986
- ```typescript
987
- interface Response<T> {
988
- title?: string;
989
- description?: string;
990
- statusCode: number;
991
- headers?: Record<string, string>;
992
- body?: T;
993
- }
994
- ```
695
+ ### `initializeApplication(options)`
995
696
 
996
- 3.3 Route handler
697
+ | Property | Type | Description |
698
+ | --------------- | ---------------------------------- | ------------------------------ |
699
+ | `serverOptions` | `AladoServerOptions` | Server configuration |
700
+ | `controllers` | `AnyClass[] \| ControllerConfig[]` | Controller classes to register |
997
701
 
998
- The route handler is the function that implements the following contract:
702
+ ### `AladoServerOptions`
999
703
 
1000
- ```typescript
1001
- (request: Request) => Response<T> | Promise<Response<T>>;
1002
- ```
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 |
1003
710
 
1004
- Despite having a strict contract for the return type,
1005
- the response body can be an object (for application/json content-type), text, or even a stream
711
+ ### `Request`
1006
712
 
1007
- ```typescript
1008
- interface UserDto {
1009
- id: number;
1010
- name: string;
1011
- }
713
+ The object received in every route handler:
1012
714
 
1013
- const handlerWithJSONBody = (request: Request): Response<UserDto> => {
1014
- const body: UserDto = {
1015
- id: 1,
1016
- name: 'John Doe',
1017
- };
1018
- return {
1019
- statusCode: 200,
1020
- headers: { 'Content-Type': 'application/json' },
1021
- body,
1022
- };
1023
- };
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 |
1024
728
 
1025
- const handlerWithStringBody = (request: Request): Response<string> => {
1026
- const body: string = 'Not Found';
1027
- return {
1028
- statusCode: 404,
1029
- headers: { 'Content-Type': 'text/plain' },
1030
- body,
1031
- };
1032
- };
729
+ ### `Response<T>`
1033
730
 
1034
- const handlerWithStreamBody = (request: Request): Response<ReadStream> => {
1035
- const body = createReadStream('/path/to/image.png');
1036
- return {
1037
- statusCode: 200,
1038
- headers: { 'Content-Type': 'image/png' },
1039
- body,
1040
- };
1041
- };
1042
- ```
731
+ The object your handler must return:
1043
732
 
1044
- 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` |
1045
738
 
1046
- ```typescript
1047
- app.start(() => console.log('Your awesome application has been successfully launched'));
739
+ ---
1048
740
 
1049
- // And stop the application when needed
1050
- app.stop(() => console.log('Your awesome application has been successfully launched'));
1051
- ```
741
+ ## License
1052
742
 
1053
- Don't forget to check swagger at the defined above options.documentProperty.route
743
+ MIT © [Databikers Limited](https://databikers.com)