alado 1.2.3 → 1.2.5
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 +200 -224
- package/accets/swagger-initializer.js +5 -10
- package/dist/alado-server/alado-server.d.ts +6 -2
- package/dist/alado-server/alado-server.js +9 -5
- package/dist/alado-server/alado-server.js.map +1 -1
- package/dist/constants/default-doc-object.js +8 -8
- package/dist/constants/default-doc-object.js.map +1 -1
- package/dist/constants/http-server.d.ts +2 -0
- package/dist/constants/http-server.js +6 -0
- package/dist/constants/http-server.js.map +1 -0
- package/dist/constants/http-status.js +6 -6
- package/dist/constants/http-status.js.map +1 -1
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/constants/reg-exp.js +1 -1
- package/dist/constants/reg-exp.js.map +1 -1
- package/dist/factory/open-api-doc-factory.js +20 -23
- package/dist/factory/open-api-doc-factory.js.map +1 -1
- package/dist/helpers/accessor/get-property.js.map +1 -1
- package/dist/helpers/accessor/set-property.js +1 -1
- package/dist/helpers/accessor/set-property.js.map +1 -1
- package/dist/helpers/parser/authenticate.js +1 -1
- package/dist/helpers/parser/authenticate.js.map +1 -1
- package/dist/helpers/parser/body-parser.js +3 -3
- package/dist/helpers/parser/body-parser.js.map +1 -1
- package/dist/helpers/parser/query-parser.js.map +1 -1
- package/dist/helpers/service/clear-route.js.map +1 -1
- package/dist/helpers/service/is-readable-stream.js +8 -8
- package/dist/helpers/service/is-readable-stream.js.map +1 -1
- package/dist/helpers/validate/alado-options/validate-alado-options.js +1 -1
- package/dist/helpers/validate/alado-options/validate-alado-options.js.map +1 -1
- package/dist/helpers/validate/context/validate-context.js +2 -2
- package/dist/helpers/validate/context/validate-context.js.map +1 -1
- package/dist/helpers/validate/context/validate-request-files.js.map +1 -1
- package/dist/helpers/validate/context/validate-request-part.js +3 -2
- package/dist/helpers/validate/context/validate-request-part.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/options/alado-server-options.d.ts +2 -0
- package/dist/request-processor/request-processor.js +10 -9
- package/dist/request-processor/request-processor.js.map +1 -1
- package/dist/router/router.js +14 -7
- package/dist/router/router.js.map +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
Alado is an extremely fast, simple, robust, and lightweight (no-dependency)
|
|
1
|
+
Alado is an extremely fast, simple, robust, and lightweight (no-dependency)
|
|
2
2
|
for creating server applications written in Node.js and running over the HTTP protocol.
|
|
3
3
|
|
|
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),
|
|
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
6
|
parsing query and path parameters, and the request body, file uploading, request authentication, etc.
|
|
7
7
|
|
|
8
|
-
The basis of use is a combination of interface simplicity and separation of the “routine” part of writing API
|
|
9
|
-
from business logic implementation - due to a declarative approach to describing all request parameters
|
|
8
|
+
The basis of use is a combination of interface simplicity and separation of the “routine” part of writing API
|
|
9
|
+
from business logic implementation - due to a declarative approach to describing all request parameters
|
|
10
10
|
in the form of static reusable structures.
|
|
11
11
|
|
|
12
12
|
There is an example of the API creating with Alado:
|
|
@@ -22,7 +22,6 @@ npm i -s alado
|
|
|
22
22
|
2. Instancing
|
|
23
23
|
|
|
24
24
|
```typescript
|
|
25
|
-
|
|
26
25
|
import { AladoServer } from 'alado';
|
|
27
26
|
|
|
28
27
|
const app: AladoServer = new AladoServer({
|
|
@@ -30,8 +29,8 @@ const app: AladoServer = new AladoServer({
|
|
|
30
29
|
cors: {
|
|
31
30
|
enable: true,
|
|
32
31
|
allowedOrigin: '*',
|
|
33
|
-
allowedHeaders: [
|
|
34
|
-
exposeHeaders: [
|
|
32
|
+
allowedHeaders: ['Authorization'],
|
|
33
|
+
exposeHeaders: ['x-total-count'],
|
|
35
34
|
},
|
|
36
35
|
openApiDoc: {
|
|
37
36
|
enable: true,
|
|
@@ -39,20 +38,17 @@ const app: AladoServer = new AladoServer({
|
|
|
39
38
|
info: {
|
|
40
39
|
title: 'My API title',
|
|
41
40
|
description: 'My API description',
|
|
42
|
-
version: '1.0.0'
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
|
|
41
|
+
version: '1.0.0',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
In case you want to run an HTTPs server (even though normally a Node.js app runs behind nginx/ingress etc.)
|
|
50
|
-
You can do it using the ssl parameter (look at SecureContextOptions in the "tls" module -
|
|
47
|
+
In case you want to run an HTTPs server (even though normally a Node.js app runs behind nginx/ingress etc.)
|
|
48
|
+
You can do it using the ssl parameter (look at SecureContextOptions in the "tls" module -
|
|
51
49
|
[Node.js TLS module](https://nodejs.org/api/tls.html#tlscreateserveroptions-secureconnectionlistener):
|
|
52
50
|
|
|
53
|
-
|
|
54
51
|
```typescript
|
|
55
|
-
|
|
56
52
|
import { readFileSync } from 'fs';
|
|
57
53
|
import { AladoServer } from 'alado';
|
|
58
54
|
|
|
@@ -65,8 +61,8 @@ const app = new AladoServer({
|
|
|
65
61
|
cors: {
|
|
66
62
|
enable: true,
|
|
67
63
|
allowedOrigin: '*',
|
|
68
|
-
allowedHeaders: [
|
|
69
|
-
exposeHeaders: [
|
|
64
|
+
allowedHeaders: ['Authorization'],
|
|
65
|
+
exposeHeaders: ['x-total-count'],
|
|
70
66
|
},
|
|
71
67
|
openApiDoc: {
|
|
72
68
|
enable: true,
|
|
@@ -74,46 +70,44 @@ const app = new AladoServer({
|
|
|
74
70
|
info: {
|
|
75
71
|
title: 'My API title',
|
|
76
72
|
description: 'My API description',
|
|
77
|
-
version: '1.0.0'
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
82
77
|
```
|
|
78
|
+
|
|
83
79
|
3. Defining routes
|
|
84
80
|
|
|
85
81
|
Here is how you define routes in Alado:
|
|
86
82
|
|
|
87
83
|
```typescript
|
|
88
|
-
|
|
89
84
|
app.get('/user/:id', context, (request: Request): Response<UserDto> => {
|
|
90
85
|
const user = {
|
|
91
86
|
name: 'John Doe',
|
|
92
|
-
id: request.path.id
|
|
93
|
-
}
|
|
87
|
+
id: request.path.id,
|
|
88
|
+
};
|
|
94
89
|
return {
|
|
95
90
|
statusCode: 200,
|
|
96
|
-
headers: {
|
|
91
|
+
headers: {
|
|
97
92
|
'Content-Type': 'application/json',
|
|
98
93
|
},
|
|
99
|
-
body: user
|
|
100
|
-
}
|
|
94
|
+
body: user,
|
|
95
|
+
};
|
|
101
96
|
});
|
|
102
97
|
|
|
103
|
-
|
|
104
|
-
app.start(() => console.log(`[API]: application has been launched successfully`))
|
|
105
|
-
|
|
98
|
+
app.start(() => console.log(`[API]: application has been launched successfully`));
|
|
106
99
|
```
|
|
107
|
-
|
|
100
|
+
|
|
101
|
+
The application provides "get", "post", "delete", "patch", "put" and "head" methods for defining routes.
|
|
108
102
|
These methods accept three required arguments:
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
- route or path: A string that may contain variables to specify the route.
|
|
105
|
+
- context: An aggregating object that fully describes the request.
|
|
106
|
+
- handler: A function that handles the request.
|
|
113
107
|
|
|
114
|
-
3.1 Route or path.
|
|
108
|
+
3.1 Route or path.
|
|
115
109
|
|
|
116
|
-
Path variables defining works in the same way as it works in other frameworks:
|
|
110
|
+
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)
|
|
117
111
|
as ":id" and receive the passed value as request.path.id).
|
|
118
112
|
|
|
119
113
|
3.2 Context
|
|
@@ -121,56 +115,54 @@ as ":id" and receive the passed value as request.path.id).
|
|
|
121
115
|
Here's the contract of the request context:
|
|
122
116
|
|
|
123
117
|
```typescript
|
|
124
|
-
|
|
125
|
-
interface Context <T> {
|
|
118
|
+
interface Context<T> {
|
|
126
119
|
title: string;
|
|
127
120
|
auth?: RequestAuthentication;
|
|
128
121
|
options: ContextOptions;
|
|
129
122
|
request: ContextRequest;
|
|
130
123
|
response: Response<T>;
|
|
131
124
|
}
|
|
132
|
-
|
|
133
125
|
```
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
|
|
127
|
+
Unlike context.title, which is simply a unique string, the other properties may not be immediately clear.
|
|
128
|
+
However, once explained, they become quite obvious and extremely logical.
|
|
136
129
|
|
|
137
130
|
**Note that all parts of the context and its nested properties can be reused**
|
|
138
131
|
|
|
139
132
|
3.2.1 Context.auth
|
|
140
133
|
|
|
141
134
|
```typescript
|
|
142
|
-
|
|
143
135
|
import { RequestAuthentication } from 'alado';
|
|
144
136
|
|
|
145
|
-
const bearerAuth: RequestAuthentication =
|
|
137
|
+
const bearerAuth: RequestAuthentication = {
|
|
146
138
|
inputProperty: 'headers.authorization',
|
|
147
139
|
outputProperty: 'auth.user',
|
|
148
140
|
handler: async (bearer: string) => {
|
|
149
|
-
|
|
141
|
+
// validate header, return user
|
|
150
142
|
return {
|
|
151
143
|
name: 'John Doe',
|
|
152
|
-
id: 1
|
|
153
|
-
}
|
|
144
|
+
id: 1,
|
|
145
|
+
};
|
|
154
146
|
},
|
|
155
147
|
required: true, // false You want "soft" checking
|
|
156
148
|
error: {
|
|
157
149
|
statusCode: 401,
|
|
158
|
-
message: 'Unauthorized'
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
150
|
+
message: 'Unauthorized',
|
|
151
|
+
},
|
|
152
|
+
};
|
|
162
153
|
```
|
|
154
|
+
|
|
163
155
|
This means that the application takes an "Authorization" header
|
|
164
156
|
(or "api_token" query parameter if you provide "query.api_token" as an inputProperty,
|
|
165
157
|
"body.someThing" and "path.token" work also)
|
|
166
|
-
use the provided handler to get the user and set the received user to request.auth.user,
|
|
158
|
+
use the provided handler to get the user and set the received user to request.auth.user,
|
|
167
159
|
(and the mentioned "request" here is the request argument in your route handler)
|
|
168
160
|
Or just returns 401 Unauthorized.
|
|
169
|
-
This means that the application can authenticate using an "Authorization" header, or alternatively,
|
|
170
|
-
using an "api_token" query parameter (if specified as "query.api_token" in the inputProperty).
|
|
171
|
-
It can also authenticate using values like "body.someThing" or "path.token".
|
|
172
|
-
In the example the provided handler retrieves the user and assigns it to request.auth.user
|
|
173
|
-
(where request is the argument in your route handler).
|
|
161
|
+
This means that the application can authenticate using an "Authorization" header, or alternatively,
|
|
162
|
+
using an "api_token" query parameter (if specified as "query.api_token" in the inputProperty).
|
|
163
|
+
It can also authenticate using values like "body.someThing" or "path.token".
|
|
164
|
+
In the example the provided handler retrieves the user and assigns it to request.auth.user
|
|
165
|
+
(where request is the argument in your route handler).
|
|
174
166
|
If authentication fails, it returns a 401 Unauthorized response.
|
|
175
167
|
|
|
176
168
|
This also works:
|
|
@@ -178,30 +170,30 @@ This also works:
|
|
|
178
170
|
```typescript
|
|
179
171
|
import { RequestAuthentication } from 'alado';
|
|
180
172
|
|
|
181
|
-
const bearerAuth: RequestAuthentication =
|
|
173
|
+
const bearerAuth: RequestAuthentication = {
|
|
182
174
|
inputProperty: 'header.x-api-token',
|
|
183
175
|
outputProperty: 'auth',
|
|
184
176
|
handler: async (xApiToken: string) => {
|
|
185
|
-
|
|
177
|
+
// You will receive user and company as nested properties in request auth
|
|
186
178
|
return {
|
|
187
179
|
user: {
|
|
188
180
|
name: 'John Doe',
|
|
189
|
-
id: 1
|
|
181
|
+
id: 1,
|
|
190
182
|
},
|
|
191
183
|
company: {
|
|
192
184
|
name: 'Databikers Limited',
|
|
193
|
-
id: 2
|
|
194
|
-
}
|
|
195
|
-
}
|
|
185
|
+
id: 2,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
196
188
|
},
|
|
197
189
|
//optional property if You want to specify execution context for the auth handler
|
|
198
|
-
handlerContext: {},
|
|
190
|
+
handlerContext: {},
|
|
199
191
|
required: true, // false You want "soft" checking
|
|
200
192
|
error: {
|
|
201
193
|
statusCode: 401,
|
|
202
|
-
message: 'Unauthorized'
|
|
203
|
-
}
|
|
204
|
-
}
|
|
194
|
+
message: 'Unauthorized',
|
|
195
|
+
},
|
|
196
|
+
};
|
|
205
197
|
```
|
|
206
198
|
|
|
207
199
|
3.2.2 Context.options
|
|
@@ -213,9 +205,10 @@ interface ContextOptions {
|
|
|
213
205
|
description?: string;
|
|
214
206
|
operationId: string;
|
|
215
207
|
tags?: string[];
|
|
216
|
-
}
|
|
208
|
+
};
|
|
217
209
|
}
|
|
218
210
|
```
|
|
211
|
+
|
|
219
212
|
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.
|
|
220
213
|
|
|
221
214
|
The "openApiDoc" property is part of the Open API route definition and is optional.
|
|
@@ -225,7 +218,6 @@ The "openApiDoc" property is part of the Open API route definition and is option
|
|
|
225
218
|
Context.request is aggregating object that describes all HTTP request properties You expect
|
|
226
219
|
|
|
227
220
|
```typescript
|
|
228
|
-
|
|
229
221
|
interface ContextRequest {
|
|
230
222
|
headers?: Record<string, PropertyDefinition>;
|
|
231
223
|
query?: Record<string, PropertyDefinition>;
|
|
@@ -235,145 +227,142 @@ interface ContextRequest {
|
|
|
235
227
|
auth?: Record<string, any>;
|
|
236
228
|
}
|
|
237
229
|
```
|
|
230
|
+
|
|
238
231
|
There's an example for request with path property only:
|
|
239
232
|
|
|
240
233
|
```typescript
|
|
241
234
|
const request: ContextRequest = {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
},
|
|
248
|
-
example: '123456',
|
|
249
|
-
description: 'User id'
|
|
235
|
+
path: {
|
|
236
|
+
id: {
|
|
237
|
+
openApiDoc: {
|
|
238
|
+
schema: {
|
|
239
|
+
type: 'string',
|
|
250
240
|
},
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
241
|
+
example: '123456',
|
|
242
|
+
description: 'User id',
|
|
243
|
+
},
|
|
244
|
+
validation: {
|
|
245
|
+
required: true,
|
|
246
|
+
handler(value) {
|
|
247
|
+
return /^\d+$/.test(value);
|
|
248
|
+
},
|
|
249
|
+
transform(value) {
|
|
250
|
+
return parseInt(value, 10);
|
|
251
|
+
},
|
|
252
|
+
error: {
|
|
253
|
+
statusCode: 400,
|
|
254
|
+
message: '400.1.0',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
};
|
|
267
260
|
```
|
|
261
|
+
|
|
268
262
|
Validation.handler returns boolean (any => boolean | any => Promise<boolean>) if received value fits to provided requirements,
|
|
269
263
|
and in falsy case it throws an error and app returns 400 Bad Request with a json body contains specified message
|
|
270
|
-
Handler can be async and can use complex logic under the hood; it allows to use
|
|
271
|
-
Avoid using an arrow functions as handlers - in this case You can lose execution context,
|
|
272
|
-
The execution context of validation.handler (and validation transform) is the request
|
|
264
|
+
Handler can be async and can use complex logic under the hood; it allows to use
|
|
265
|
+
Avoid using an arrow functions as handlers - in this case You can lose execution context,
|
|
266
|
+
The execution context of validation.handler (and validation transform) is the request
|
|
273
267
|
(aggregating object - the argument used at the route handler)
|
|
274
268
|
|
|
275
269
|
```typescript
|
|
276
|
-
|
|
277
270
|
const request: ContextRequest = {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
},
|
|
284
|
-
example: 'admin',
|
|
285
|
-
description: 'User role'
|
|
271
|
+
body: {
|
|
272
|
+
role: {
|
|
273
|
+
openApiDoc: {
|
|
274
|
+
schema: {
|
|
275
|
+
type: 'string',
|
|
286
276
|
},
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
return
|
|
295
|
-
},
|
|
296
|
-
error: {
|
|
297
|
-
statusCode: 400,
|
|
298
|
-
message: '400.1.0'
|
|
277
|
+
example: 'admin',
|
|
278
|
+
description: 'User role',
|
|
279
|
+
},
|
|
280
|
+
validation: {
|
|
281
|
+
required: true,
|
|
282
|
+
async handler(value) {
|
|
283
|
+
if (this.auth.user.role === 'admin') {
|
|
284
|
+
return true;
|
|
299
285
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
286
|
+
await userService.makeSomeChecking(this.query.scope);
|
|
287
|
+
return value === 'customer';
|
|
288
|
+
},
|
|
289
|
+
error: {
|
|
290
|
+
statusCode: 400,
|
|
291
|
+
message: '400.1.0',
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
};
|
|
305
297
|
```
|
|
298
|
+
|
|
306
299
|
In the same way You can describe the request headers, query, body. Remember, if context.options.allowUnknownFields
|
|
307
300
|
is set to false, all non-described request properties (or non-described nested properties in the described request properties) never be accessible.
|
|
308
301
|
|
|
309
302
|
```typescript
|
|
310
|
-
|
|
311
303
|
const request: ContextRequest = {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
},
|
|
318
|
-
example: 'createdAt',
|
|
319
|
-
description: 'Sort by field'
|
|
304
|
+
query: {
|
|
305
|
+
sortBy: {
|
|
306
|
+
openApiDoc: {
|
|
307
|
+
schema: {
|
|
308
|
+
type: 'string',
|
|
320
309
|
},
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
handler (value) {
|
|
324
|
-
return ['createdAt','updatedAt'].includes(value);
|
|
325
|
-
},
|
|
326
|
-
error: {
|
|
327
|
-
statusCode: 400,
|
|
328
|
-
message: 'sortBy should be createdAt or updatedAt'
|
|
329
|
-
}
|
|
330
|
-
}
|
|
310
|
+
example: 'createdAt',
|
|
311
|
+
description: 'Sort by field',
|
|
331
312
|
},
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
},
|
|
337
|
-
example: 'asc',
|
|
338
|
-
description: 'Sort order'
|
|
313
|
+
validation: {
|
|
314
|
+
required: true,
|
|
315
|
+
handler(value) {
|
|
316
|
+
return ['createdAt', 'updatedAt'].includes(value);
|
|
339
317
|
},
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
318
|
+
error: {
|
|
319
|
+
statusCode: 400,
|
|
320
|
+
message: 'sortBy should be createdAt or updatedAt',
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
sortOrder: {
|
|
325
|
+
openApiDoc: {
|
|
326
|
+
schema: {
|
|
327
|
+
type: 'string',
|
|
328
|
+
},
|
|
329
|
+
example: 'asc',
|
|
330
|
+
description: 'Sort order',
|
|
331
|
+
},
|
|
332
|
+
validation: {
|
|
333
|
+
required: true,
|
|
334
|
+
handler(value) {
|
|
335
|
+
return ['asc', 'desc'].includes(value);
|
|
336
|
+
},
|
|
337
|
+
error: {
|
|
338
|
+
statusCode: 400,
|
|
339
|
+
message: 'sortOrder should be asc or desc',
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
354
345
|
```
|
|
346
|
+
|
|
355
347
|
Now, if you send GET /user?sortBy=updateAt&sortOrder=desc&limit=10, the limit parameter will be ignored since it wasn't described:
|
|
356
|
-
```typescript
|
|
357
348
|
|
|
349
|
+
```typescript
|
|
358
350
|
app.post('/user', context, (request: Request) => {
|
|
359
|
-
|
|
360
351
|
console.log(request.query);
|
|
361
|
-
|
|
352
|
+
|
|
362
353
|
// {
|
|
363
354
|
// sortBy: 'updatedAt',
|
|
364
355
|
// sortOrder: 'desc'
|
|
365
356
|
// }
|
|
366
|
-
|
|
357
|
+
|
|
367
358
|
return {
|
|
368
359
|
statusCode: 200,
|
|
369
360
|
headers: {
|
|
370
|
-
'Content-Type': 'application/json'
|
|
361
|
+
'Content-Type': 'application/json',
|
|
371
362
|
},
|
|
372
|
-
body: []
|
|
373
|
-
}
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
|
|
363
|
+
body: [],
|
|
364
|
+
};
|
|
365
|
+
});
|
|
377
366
|
```
|
|
378
367
|
|
|
379
368
|
Regarding the request body even it described properly it will be available only for POST, PUT and PATCH HTTP requests.
|
|
@@ -383,41 +372,38 @@ If You want dealing with uploaded files You just should to describe it in the re
|
|
|
383
372
|
const request: ContextRequest = {
|
|
384
373
|
files: {
|
|
385
374
|
avatar: {
|
|
386
|
-
mimetypes: [
|
|
387
|
-
'image/png'
|
|
388
|
-
],
|
|
375
|
+
mimetypes: ['image/png'],
|
|
389
376
|
maxSize: 1048576,
|
|
390
377
|
required: true,
|
|
391
378
|
maxSizeError: {
|
|
392
379
|
statusCode: 413,
|
|
393
|
-
message: 'The avatar should not be larger than 1MB'
|
|
380
|
+
message: 'The avatar should not be larger than 1MB',
|
|
394
381
|
},
|
|
395
382
|
mimetypeError: {
|
|
396
383
|
statusCode: 415,
|
|
397
|
-
message: 'The avatar should be a PNG image'
|
|
384
|
+
message: 'The avatar should be a PNG image',
|
|
398
385
|
},
|
|
399
386
|
requiredError: {
|
|
400
387
|
statusCode: 400,
|
|
401
|
-
message: 'The avatar file is required'
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
388
|
+
message: 'The avatar file is required',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
405
392
|
};
|
|
406
|
-
|
|
407
393
|
```
|
|
394
|
+
|
|
408
395
|
and uploaded file will be acceptable in the request:
|
|
409
396
|
|
|
410
397
|
```typescript
|
|
411
398
|
import { createWriteStream } from 'fs';
|
|
412
399
|
|
|
413
400
|
app.post('/user/:id/avatar', context, (request: Request) => {
|
|
414
|
-
|
|
415
401
|
console.log(request.files);
|
|
416
402
|
// {
|
|
417
|
-
// avatar: {
|
|
418
|
-
// stream: Readable,
|
|
419
|
-
// size: 65025,
|
|
420
|
-
// mimetype: 'image/png'
|
|
403
|
+
// avatar: {
|
|
404
|
+
// stream: Readable,
|
|
405
|
+
// size: 65025,
|
|
406
|
+
// mimetype: 'image/png'
|
|
421
407
|
// }
|
|
422
408
|
// }
|
|
423
409
|
|
|
@@ -428,13 +414,11 @@ app.post('/user/:id/avatar', context, (request: Request) => {
|
|
|
428
414
|
return {
|
|
429
415
|
statusCode: 200,
|
|
430
416
|
headers: {
|
|
431
|
-
'Content-Type': 'application/json'
|
|
417
|
+
'Content-Type': 'application/json',
|
|
432
418
|
},
|
|
433
|
-
body: user
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
|
|
419
|
+
body: user,
|
|
420
|
+
};
|
|
421
|
+
});
|
|
438
422
|
```
|
|
439
423
|
|
|
440
424
|
3.2.4 Context response
|
|
@@ -442,15 +426,13 @@ app.post('/user/:id/avatar', context, (request: Request) => {
|
|
|
442
426
|
Context.response is always the same as the return type of the route handler
|
|
443
427
|
|
|
444
428
|
```typescript
|
|
445
|
-
|
|
446
|
-
interface Response<T> {
|
|
429
|
+
interface Response<T> {
|
|
447
430
|
title?: string;
|
|
448
431
|
description?: string;
|
|
449
432
|
statusCode: number;
|
|
450
433
|
headers?: Record<string, string>;
|
|
451
|
-
body?: T
|
|
434
|
+
body?: T;
|
|
452
435
|
}
|
|
453
|
-
|
|
454
436
|
```
|
|
455
437
|
|
|
456
438
|
3.3 Route handler
|
|
@@ -458,12 +440,10 @@ interface Response<T> {
|
|
|
458
440
|
The route handler is the function that implements the following contract:
|
|
459
441
|
|
|
460
442
|
```typescript
|
|
461
|
-
|
|
462
443
|
(request: Request) => Response<T> | Promise<Response<T>>;
|
|
463
|
-
|
|
464
444
|
```
|
|
465
445
|
|
|
466
|
-
Despite having a strict contract for the return type,
|
|
446
|
+
Despite having a strict contract for the return type,
|
|
467
447
|
the response body can be an object (for application/json content-type), text, or even a stream
|
|
468
448
|
|
|
469
449
|
```typescript
|
|
@@ -475,45 +455,41 @@ interface UserDto {
|
|
|
475
455
|
const handlerWithJSONBody = (request: Request): Response<UserDto> => {
|
|
476
456
|
const body: UserDto = {
|
|
477
457
|
id: 1,
|
|
478
|
-
name: 'John Doe'
|
|
479
|
-
}
|
|
458
|
+
name: 'John Doe',
|
|
459
|
+
};
|
|
480
460
|
return {
|
|
481
461
|
statusCode: 200,
|
|
482
|
-
headers: {'Content-Type': 'application/json' },
|
|
483
|
-
body
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
462
|
+
headers: { 'Content-Type': 'application/json' },
|
|
463
|
+
body,
|
|
464
|
+
};
|
|
465
|
+
};
|
|
487
466
|
|
|
488
467
|
const handlerWithStringBody = (request: Request): Response<string> => {
|
|
489
|
-
const body: string = 'Not Found'
|
|
468
|
+
const body: string = 'Not Found';
|
|
490
469
|
return {
|
|
491
470
|
statusCode: 404,
|
|
492
|
-
headers: {'Content-Type': 'text/plain' },
|
|
493
|
-
body
|
|
494
|
-
}
|
|
495
|
-
}
|
|
471
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
472
|
+
body,
|
|
473
|
+
};
|
|
474
|
+
};
|
|
496
475
|
|
|
497
476
|
const handlerWithStreamBody = (request: Request): Response<ReadStream> => {
|
|
498
|
-
const body = createReadStream('/path/to/image.png')
|
|
477
|
+
const body = createReadStream('/path/to/image.png');
|
|
499
478
|
return {
|
|
500
479
|
statusCode: 200,
|
|
501
|
-
headers: {'Content-Type': 'image/png' },
|
|
502
|
-
body
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
480
|
+
headers: { 'Content-Type': 'image/png' },
|
|
481
|
+
body,
|
|
482
|
+
};
|
|
483
|
+
};
|
|
506
484
|
```
|
|
507
485
|
|
|
508
486
|
4. Running
|
|
509
487
|
|
|
510
|
-
|
|
511
488
|
```typescript
|
|
512
|
-
|
|
513
489
|
app.start(() => console.log('Your awesome application has been successfully launched'));
|
|
514
490
|
|
|
515
491
|
// And stop the application when needed
|
|
516
492
|
app.stop(() => console.log('Your awesome application has been successfully launched'));
|
|
517
|
-
|
|
518
493
|
```
|
|
519
|
-
|
|
494
|
+
|
|
495
|
+
Don't forget to check swagger at the defined above options.openApiDoc.route
|