alado 1.2.3 → 1.2.4

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.
Files changed (37) hide show
  1. package/README.md +200 -224
  2. package/accets/swagger-initializer.js +5 -10
  3. package/dist/alado-server/alado-server.js +3 -3
  4. package/dist/alado-server/alado-server.js.map +1 -1
  5. package/dist/constants/default-doc-object.js +8 -8
  6. package/dist/constants/default-doc-object.js.map +1 -1
  7. package/dist/constants/http-status.js +6 -6
  8. package/dist/constants/http-status.js.map +1 -1
  9. package/dist/constants/reg-exp.js +1 -1
  10. package/dist/constants/reg-exp.js.map +1 -1
  11. package/dist/factory/open-api-doc-factory.js +20 -23
  12. package/dist/factory/open-api-doc-factory.js.map +1 -1
  13. package/dist/helpers/accessor/get-property.js.map +1 -1
  14. package/dist/helpers/accessor/set-property.js +1 -1
  15. package/dist/helpers/accessor/set-property.js.map +1 -1
  16. package/dist/helpers/parser/authenticate.js +1 -1
  17. package/dist/helpers/parser/authenticate.js.map +1 -1
  18. package/dist/helpers/parser/body-parser.js +3 -3
  19. package/dist/helpers/parser/body-parser.js.map +1 -1
  20. package/dist/helpers/parser/query-parser.js.map +1 -1
  21. package/dist/helpers/service/clear-route.js.map +1 -1
  22. package/dist/helpers/service/is-readable-stream.js +8 -8
  23. package/dist/helpers/service/is-readable-stream.js.map +1 -1
  24. package/dist/helpers/validate/alado-options/validate-alado-options.js +1 -1
  25. package/dist/helpers/validate/alado-options/validate-alado-options.js.map +1 -1
  26. package/dist/helpers/validate/context/validate-context.js +2 -2
  27. package/dist/helpers/validate/context/validate-context.js.map +1 -1
  28. package/dist/helpers/validate/context/validate-request-files.js.map +1 -1
  29. package/dist/helpers/validate/context/validate-request-part.js +3 -2
  30. package/dist/helpers/validate/context/validate-request-part.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/request-processor/request-processor.js +10 -9
  34. package/dist/request-processor/request-processor.js.map +1 -1
  35. package/dist/router/router.js +14 -7
  36. package/dist/router/router.js.map +1 -1
  37. 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: [ 'Authorization' ],
34
- exposeHeaders: [ 'x-total-count' ],
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: [ 'Authorization' ],
69
- exposeHeaders: [ 'x-total-count' ],
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
- The application provides "get", "post", "delete", "patch", "put" and "head" methods for defining routes.
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
- * route or path: A string that may contain variables to specify the route.
111
- * context: An aggregating object that fully describes the request.
112
- * handler: A function that handles the request.
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: You just define a part of your route (means part separated by slashes)
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
- Unlike context.title, which is simply a unique string, the other properties may not be immediately clear.
135
- However, once explained, they become quite obvious and extremely logical.
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
- // validate header, return user
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
- // You will receive user and company as nested properties in request auth
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
- path: {
243
- id: {
244
- openApiDoc: {
245
- schema: {
246
- type: 'string',
247
- },
248
- example: '123456',
249
- description: 'User id'
235
+ path: {
236
+ id: {
237
+ openApiDoc: {
238
+ schema: {
239
+ type: 'string',
250
240
  },
251
- validation: {
252
- required: true,
253
- handler (value) {
254
- return /^\d+$/.test(value);
255
- },
256
- transform (value) {
257
- return parseInt(value, 10);
258
- },
259
- error: {
260
- statusCode: 400,
261
- message: '400.1.0'
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
- body: {
279
- role: {
280
- openApiDoc: {
281
- schema: {
282
- type: 'string',
283
- },
284
- example: 'admin',
285
- description: 'User role'
271
+ body: {
272
+ role: {
273
+ openApiDoc: {
274
+ schema: {
275
+ type: 'string',
286
276
  },
287
- validation: {
288
- required: true,
289
- async handler (value) {
290
- if (this.auth.user.role === 'admin') {
291
- return true;
292
- }
293
- await userService.makeSomeChecking(this.query.scope);
294
- return value === 'customer'
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
- query: {
313
- sortBy: {
314
- openApiDoc: {
315
- schema: {
316
- type: 'string',
317
- },
318
- example: 'createdAt',
319
- description: 'Sort by field'
304
+ query: {
305
+ sortBy: {
306
+ openApiDoc: {
307
+ schema: {
308
+ type: 'string',
320
309
  },
321
- validation: {
322
- required: true,
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
- sortOrder: {
333
- openApiDoc: {
334
- schema: {
335
- type: 'string',
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
- validation: {
341
- required: true,
342
- handler (value) {
343
- return ['asc','desc'].includes(value);
344
- },
345
- error: {
346
- statusCode: 400,
347
- message: 'sortOrder should be asc or desc'
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
- Don't forget to check swagger at the defined above options.openApiDoc.route
494
+
495
+ Don't forget to check swagger at the defined above options.openApiDoc.route