deco-express 0.1.0
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/.prettierignore +2 -0
- package/.prettierrc +7 -0
- package/README.md +174 -0
- package/dist/index.d.mts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +114 -0
- package/dist/index.mjs +78 -0
- package/eslint.config.mjs +33 -0
- package/package.json +52 -0
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# deco-express
|
|
2
|
+
|
|
3
|
+
> Decorator-based utilities for building Express.js REST APIs in TypeScript.
|
|
4
|
+
|
|
5
|
+
`deco-express` reduces boilerplate when building Express applications by providing a small set of TypeScript decorators for defining controllers, routes, and request validation.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install deco-express reflect-metadata
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> `reflect-metadata` is a required peer dependency. Import it **once** at the entry point of your application before using any decorators.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// src/main.ts
|
|
19
|
+
import "reflect-metadata";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Also ensure your `tsconfig.json` includes:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"compilerOptions": {
|
|
27
|
+
"experimentalDecorators": true,
|
|
28
|
+
"emitDecoratorMetadata": true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import "reflect-metadata";
|
|
39
|
+
import express from "express";
|
|
40
|
+
import Joi from "joi";
|
|
41
|
+
import { Controller, Route, Validate, defineRoutes } from "deco-express";
|
|
42
|
+
|
|
43
|
+
const createUserSchema = Joi.object({
|
|
44
|
+
name: Joi.string().required(),
|
|
45
|
+
email: Joi.string().email().required(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
@Controller("/users")
|
|
49
|
+
class UserController {
|
|
50
|
+
@Route("get", "/list")
|
|
51
|
+
async list(req, res) {
|
|
52
|
+
res.json({ users: [] });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Validate(createUserSchema)
|
|
56
|
+
@Route("post", "/create")
|
|
57
|
+
async create(req, res) {
|
|
58
|
+
res.status(201).json({ created: req.body });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const app = express();
|
|
63
|
+
app.use(express.json());
|
|
64
|
+
|
|
65
|
+
defineRoutes([UserController], app);
|
|
66
|
+
// Registers:
|
|
67
|
+
// GET /api/v1/users/list
|
|
68
|
+
// POST /api/v1/users/create
|
|
69
|
+
|
|
70
|
+
app.listen(3000);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### `@Controller(baseRoute?)`
|
|
78
|
+
|
|
79
|
+
Class decorator. Marks a class as a route controller and sets its base path.
|
|
80
|
+
|
|
81
|
+
| Parameter | Type | Default | Description |
|
|
82
|
+
| ----------- | -------- | ------- | ------------------------------- |
|
|
83
|
+
| `baseRoute` | `string` | `''` | Base path prefix for all routes |
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
@Controller('/products')
|
|
87
|
+
class ProductController { ... }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `@Route(method, path?, ...middleware)`
|
|
93
|
+
|
|
94
|
+
Method decorator. Maps a class method to an HTTP route.
|
|
95
|
+
|
|
96
|
+
| Parameter | Type | Default | Description |
|
|
97
|
+
| ------------ | ------------------ | ------- | ------------------------------------ |
|
|
98
|
+
| `method` | `keyof Express` | — | HTTP method (`get`, `post`, etc.) |
|
|
99
|
+
| `path` | `string` | `''` | Route path (appended to base route) |
|
|
100
|
+
| `middleware` | `RequestHandler[]` | `[]` | Optional Express middleware to apply |
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
@Route('get', '/list', authMiddleware)
|
|
104
|
+
async list(req, res) { ... }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### `@Validate(schema)`
|
|
110
|
+
|
|
111
|
+
Method decorator. Validates `req.body` against a Joi schema before the handler runs.
|
|
112
|
+
Returns `422 Unprocessable Entity` on validation failure with structured error details.
|
|
113
|
+
|
|
114
|
+
| Parameter | Type | Description |
|
|
115
|
+
| --------- | ------------ | ------------------------------ |
|
|
116
|
+
| `schema` | `Joi.Schema` | Joi schema to validate against |
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
@Validate(Joi.object({ name: Joi.string().required() }))
|
|
120
|
+
@Route('post', '/create')
|
|
121
|
+
async create(req, res) { ... }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Error response format (422):**
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"message": "Validation failed",
|
|
129
|
+
"details": [{ "field": "name", "message": "\"name\" is required" }]
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### `defineRoutes(controllers, app, addApi?, version?)`
|
|
136
|
+
|
|
137
|
+
Registers all decorated controller routes to an Express application.
|
|
138
|
+
|
|
139
|
+
| Parameter | Type | Default | Description |
|
|
140
|
+
| ------------- | --------------- | ------- | -------------------------------------- |
|
|
141
|
+
| `controllers` | `Constructor[]` | — | Array of controller class constructors |
|
|
142
|
+
| `app` | `Express` | — | Express application instance |
|
|
143
|
+
| `addApi` | `boolean` | `true` | Prepend `/api` to all routes |
|
|
144
|
+
| `version` | `number` | `1` | API version number (e.g. `/v1`) |
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
defineRoutes([UserController, ProductController], app, true, 2);
|
|
148
|
+
// Routes become: /api/v2/...
|
|
149
|
+
|
|
150
|
+
defineRoutes([UserController], app, false);
|
|
151
|
+
// Routes become: /users/...
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Route Path Construction
|
|
157
|
+
|
|
158
|
+
The final route path is built as:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
/{api}/{version}/{baseRoute}/{path}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| `addApi` | `version` | Result prefix |
|
|
165
|
+
| -------- | --------- | ------------- |
|
|
166
|
+
| `true` | `1` | `/api/v1` |
|
|
167
|
+
| `true` | `2` | `/api/v2` |
|
|
168
|
+
| `false` | any | (no prefix) |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Express, RequestHandler } from 'express';
|
|
2
|
+
import Joi from 'joi';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Method decorator that registers a route handler on the enclosing controller.
|
|
6
|
+
*
|
|
7
|
+
* The decorator stores the handler (and any preceding middleware) in
|
|
8
|
+
* `reflect-metadata` under the `"routeHandlers"` key so that
|
|
9
|
+
* {@link defineRoutes} can later mount them on an Express application.
|
|
10
|
+
*
|
|
11
|
+
* @param method - The HTTP method to register (must be a valid method key on
|
|
12
|
+
* the Express application, e.g. `"get"`, `"post"`, `"put"`, `"delete"`).
|
|
13
|
+
* @param path - The route path relative to the controller's base route.
|
|
14
|
+
* Defaults to `""` (i.e. the controller root).
|
|
15
|
+
* @param middleware - Zero or more Express middleware functions that are
|
|
16
|
+
* executed **before** the decorated handler.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* class UserController {
|
|
21
|
+
* \@Route('get', '/:id', authMiddleware)
|
|
22
|
+
* getUser(req: Request, res: Response) {
|
|
23
|
+
* res.json({ id: req.params.id });
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function Route(method: keyof Express, path?: string, ...middleware: RequestHandler[]): (target: object, _key: string, descriptor: PropertyDescriptor) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Class decorator that marks a class as an Express controller and sets its
|
|
31
|
+
* base route prefix.
|
|
32
|
+
*
|
|
33
|
+
* The base route is stored in `reflect-metadata` under the `"baseRoute"` key
|
|
34
|
+
* and is prepended to every route path registered by {@link Route} decorators
|
|
35
|
+
* on the class's methods.
|
|
36
|
+
*
|
|
37
|
+
* @param baseRoute - The base path prefix for all routes in the controller
|
|
38
|
+
* (e.g. `"/users"`). Defaults to `""` (no prefix).
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* \@Controller('/users')
|
|
43
|
+
* class UserController {
|
|
44
|
+
* \@Route('get', '/:id')
|
|
45
|
+
* getUser(req: Request, res: Response) { ... }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function Controller(baseRoute?: string): (target: object) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Method decorator that validates `req.body` against a Joi schema before the
|
|
53
|
+
* decorated handler is invoked.
|
|
54
|
+
*
|
|
55
|
+
* When validation fails the decorator short-circuits the request and responds
|
|
56
|
+
* with HTTP **422 Unprocessable Entity** and a structured error body:
|
|
57
|
+
*
|
|
58
|
+
* ```json
|
|
59
|
+
* {
|
|
60
|
+
* "message": "Validation failed",
|
|
61
|
+
* "details": [{ "field": "email", "message": "\"email\" must be a valid email" }]
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* Any non-validation error thrown by Joi is forwarded to the next Express
|
|
66
|
+
* error handler via `next(error)`.
|
|
67
|
+
*
|
|
68
|
+
* @param schema - A Joi schema used to validate `req.body`.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const createUserSchema = Joi.object({ email: Joi.string().email().required() });
|
|
73
|
+
*
|
|
74
|
+
* class UserController {
|
|
75
|
+
* \@Route('post', '/')
|
|
76
|
+
* \@Validate(createUserSchema)
|
|
77
|
+
* createUser(req: Request, res: Response) {
|
|
78
|
+
* res.status(201).json({ email: req.body.email });
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function Validate(schema: Joi.Schema): (_target: object, _propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
84
|
+
|
|
85
|
+
/** Any class constructor that produces instances of type `T`. */
|
|
86
|
+
type Constructor<T = object> = new (...args: unknown[]) => T;
|
|
87
|
+
/**
|
|
88
|
+
* Registers routes from one or more controller classes onto an Express
|
|
89
|
+
* application.
|
|
90
|
+
*
|
|
91
|
+
* For each controller the function:
|
|
92
|
+
* 1. Instantiates the class with `new`.
|
|
93
|
+
* 2. Reads the `"baseRoute"` metadata set by {@link Controller}.
|
|
94
|
+
* 3. Reads the `"routeHandlers"` metadata set by {@link Route}.
|
|
95
|
+
* 4. Mounts each handler at the resolved full path on the Express app.
|
|
96
|
+
*
|
|
97
|
+
* The full path is built as:
|
|
98
|
+
* ```
|
|
99
|
+
* [/api][/v{version}]{baseRoute}{routePath}
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @param controllers - Array of controller class constructors to register.
|
|
103
|
+
* @param application - The Express application instance to mount routes on.
|
|
104
|
+
* @param addApi - When `true` (default), prepends `/api` to every route.
|
|
105
|
+
* @param version - API version number. When `addApi` is `true` and `version`
|
|
106
|
+
* is non-zero, a `/v{version}` segment is inserted after `/api`.
|
|
107
|
+
* Defaults to `1`.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* import express from 'express';
|
|
112
|
+
* import { defineRoutes } from 'deco-express';
|
|
113
|
+
* import { UserController } from './controllers/user';
|
|
114
|
+
*
|
|
115
|
+
* const app = express();
|
|
116
|
+
* defineRoutes([UserController], app);
|
|
117
|
+
* // Routes are now available at /api/v1/...
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function defineRoutes(controllers: Constructor[], application: Express, addApi?: boolean, version?: number): void;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A nested map that stores route handlers organised by HTTP method and path.
|
|
124
|
+
*
|
|
125
|
+
* Outer key – an HTTP method name that exists on the Express application
|
|
126
|
+
* (e.g. `"get"`, `"post"`, `"put"`, `"delete"`).
|
|
127
|
+
*
|
|
128
|
+
* Inner key – the route path string (e.g. `"/users/:id"`).
|
|
129
|
+
*
|
|
130
|
+
* Inner value – an ordered array of Express `RequestHandler` functions,
|
|
131
|
+
* where middleware precedes the actual route handler.
|
|
132
|
+
*/
|
|
133
|
+
type RouteHandlers = Map<keyof Express, Map<string, RequestHandler[]>>;
|
|
134
|
+
|
|
135
|
+
export { Controller, Route, type RouteHandlers, Validate, defineRoutes };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Express, RequestHandler } from 'express';
|
|
2
|
+
import Joi from 'joi';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Method decorator that registers a route handler on the enclosing controller.
|
|
6
|
+
*
|
|
7
|
+
* The decorator stores the handler (and any preceding middleware) in
|
|
8
|
+
* `reflect-metadata` under the `"routeHandlers"` key so that
|
|
9
|
+
* {@link defineRoutes} can later mount them on an Express application.
|
|
10
|
+
*
|
|
11
|
+
* @param method - The HTTP method to register (must be a valid method key on
|
|
12
|
+
* the Express application, e.g. `"get"`, `"post"`, `"put"`, `"delete"`).
|
|
13
|
+
* @param path - The route path relative to the controller's base route.
|
|
14
|
+
* Defaults to `""` (i.e. the controller root).
|
|
15
|
+
* @param middleware - Zero or more Express middleware functions that are
|
|
16
|
+
* executed **before** the decorated handler.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* class UserController {
|
|
21
|
+
* \@Route('get', '/:id', authMiddleware)
|
|
22
|
+
* getUser(req: Request, res: Response) {
|
|
23
|
+
* res.json({ id: req.params.id });
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function Route(method: keyof Express, path?: string, ...middleware: RequestHandler[]): (target: object, _key: string, descriptor: PropertyDescriptor) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Class decorator that marks a class as an Express controller and sets its
|
|
31
|
+
* base route prefix.
|
|
32
|
+
*
|
|
33
|
+
* The base route is stored in `reflect-metadata` under the `"baseRoute"` key
|
|
34
|
+
* and is prepended to every route path registered by {@link Route} decorators
|
|
35
|
+
* on the class's methods.
|
|
36
|
+
*
|
|
37
|
+
* @param baseRoute - The base path prefix for all routes in the controller
|
|
38
|
+
* (e.g. `"/users"`). Defaults to `""` (no prefix).
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* \@Controller('/users')
|
|
43
|
+
* class UserController {
|
|
44
|
+
* \@Route('get', '/:id')
|
|
45
|
+
* getUser(req: Request, res: Response) { ... }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function Controller(baseRoute?: string): (target: object) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Method decorator that validates `req.body` against a Joi schema before the
|
|
53
|
+
* decorated handler is invoked.
|
|
54
|
+
*
|
|
55
|
+
* When validation fails the decorator short-circuits the request and responds
|
|
56
|
+
* with HTTP **422 Unprocessable Entity** and a structured error body:
|
|
57
|
+
*
|
|
58
|
+
* ```json
|
|
59
|
+
* {
|
|
60
|
+
* "message": "Validation failed",
|
|
61
|
+
* "details": [{ "field": "email", "message": "\"email\" must be a valid email" }]
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* Any non-validation error thrown by Joi is forwarded to the next Express
|
|
66
|
+
* error handler via `next(error)`.
|
|
67
|
+
*
|
|
68
|
+
* @param schema - A Joi schema used to validate `req.body`.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const createUserSchema = Joi.object({ email: Joi.string().email().required() });
|
|
73
|
+
*
|
|
74
|
+
* class UserController {
|
|
75
|
+
* \@Route('post', '/')
|
|
76
|
+
* \@Validate(createUserSchema)
|
|
77
|
+
* createUser(req: Request, res: Response) {
|
|
78
|
+
* res.status(201).json({ email: req.body.email });
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function Validate(schema: Joi.Schema): (_target: object, _propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
84
|
+
|
|
85
|
+
/** Any class constructor that produces instances of type `T`. */
|
|
86
|
+
type Constructor<T = object> = new (...args: unknown[]) => T;
|
|
87
|
+
/**
|
|
88
|
+
* Registers routes from one or more controller classes onto an Express
|
|
89
|
+
* application.
|
|
90
|
+
*
|
|
91
|
+
* For each controller the function:
|
|
92
|
+
* 1. Instantiates the class with `new`.
|
|
93
|
+
* 2. Reads the `"baseRoute"` metadata set by {@link Controller}.
|
|
94
|
+
* 3. Reads the `"routeHandlers"` metadata set by {@link Route}.
|
|
95
|
+
* 4. Mounts each handler at the resolved full path on the Express app.
|
|
96
|
+
*
|
|
97
|
+
* The full path is built as:
|
|
98
|
+
* ```
|
|
99
|
+
* [/api][/v{version}]{baseRoute}{routePath}
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @param controllers - Array of controller class constructors to register.
|
|
103
|
+
* @param application - The Express application instance to mount routes on.
|
|
104
|
+
* @param addApi - When `true` (default), prepends `/api` to every route.
|
|
105
|
+
* @param version - API version number. When `addApi` is `true` and `version`
|
|
106
|
+
* is non-zero, a `/v{version}` segment is inserted after `/api`.
|
|
107
|
+
* Defaults to `1`.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* import express from 'express';
|
|
112
|
+
* import { defineRoutes } from 'deco-express';
|
|
113
|
+
* import { UserController } from './controllers/user';
|
|
114
|
+
*
|
|
115
|
+
* const app = express();
|
|
116
|
+
* defineRoutes([UserController], app);
|
|
117
|
+
* // Routes are now available at /api/v1/...
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function defineRoutes(controllers: Constructor[], application: Express, addApi?: boolean, version?: number): void;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A nested map that stores route handlers organised by HTTP method and path.
|
|
124
|
+
*
|
|
125
|
+
* Outer key – an HTTP method name that exists on the Express application
|
|
126
|
+
* (e.g. `"get"`, `"post"`, `"put"`, `"delete"`).
|
|
127
|
+
*
|
|
128
|
+
* Inner key – the route path string (e.g. `"/users/:id"`).
|
|
129
|
+
*
|
|
130
|
+
* Inner value – an ordered array of Express `RequestHandler` functions,
|
|
131
|
+
* where middleware precedes the actual route handler.
|
|
132
|
+
*/
|
|
133
|
+
type RouteHandlers = Map<keyof Express, Map<string, RequestHandler[]>>;
|
|
134
|
+
|
|
135
|
+
export { Controller, Route, type RouteHandlers, Validate, defineRoutes };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
Controller: () => Controller,
|
|
35
|
+
Route: () => Route,
|
|
36
|
+
Validate: () => Validate,
|
|
37
|
+
defineRoutes: () => defineRoutes
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
41
|
+
|
|
42
|
+
// src/decorators/express.ts
|
|
43
|
+
function Route(method, path = "", ...middleware) {
|
|
44
|
+
return (target, _key, descriptor) => {
|
|
45
|
+
const routeHandlers = Reflect.getMetadata("routeHandlers", target) || /* @__PURE__ */ new Map();
|
|
46
|
+
if (!routeHandlers.has(method)) {
|
|
47
|
+
routeHandlers.set(method, /* @__PURE__ */ new Map());
|
|
48
|
+
}
|
|
49
|
+
routeHandlers.get(method)?.set(path, [...middleware, descriptor.value]);
|
|
50
|
+
Reflect.defineMetadata("routeHandlers", routeHandlers, target);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
__name(Route, "Route");
|
|
54
|
+
function Controller(baseRoute = "") {
|
|
55
|
+
return (target) => {
|
|
56
|
+
Reflect.defineMetadata("baseRoute", baseRoute, target);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
__name(Controller, "Controller");
|
|
60
|
+
|
|
61
|
+
// src/decorators/validation.ts
|
|
62
|
+
var import_joi = __toESM(require("joi"));
|
|
63
|
+
function Validate(schema) {
|
|
64
|
+
return function(_target, _propertyKey, descriptor) {
|
|
65
|
+
const originalMethod = descriptor.value;
|
|
66
|
+
descriptor.value = async function(req, res, next) {
|
|
67
|
+
try {
|
|
68
|
+
await schema.validateAsync(req.body);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof import_joi.default.ValidationError) {
|
|
71
|
+
return res.status(422).json({
|
|
72
|
+
message: "Validation failed",
|
|
73
|
+
details: error.details.map((d) => ({
|
|
74
|
+
field: d.path.join("."),
|
|
75
|
+
message: d.message
|
|
76
|
+
}))
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return next(error);
|
|
80
|
+
}
|
|
81
|
+
return originalMethod.call(this, req, res, next);
|
|
82
|
+
};
|
|
83
|
+
return descriptor;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
__name(Validate, "Validate");
|
|
87
|
+
|
|
88
|
+
// src/functions/routes.ts
|
|
89
|
+
function defineRoutes(controllers, application, addApi = true, version = 1) {
|
|
90
|
+
for (const ControllerClass of controllers) {
|
|
91
|
+
const controller = new ControllerClass();
|
|
92
|
+
const routeHandlers = Reflect.getMetadata(
|
|
93
|
+
"routeHandlers",
|
|
94
|
+
controller
|
|
95
|
+
);
|
|
96
|
+
const controllerPath = Reflect.getMetadata("baseRoute", controller.constructor) ?? "";
|
|
97
|
+
if (!routeHandlers) continue;
|
|
98
|
+
for (const [method, routes] of routeHandlers) {
|
|
99
|
+
for (const [routePath, handlers] of routes) {
|
|
100
|
+
const versionSegment = addApi && version ? `/v${version}` : "";
|
|
101
|
+
const fullPath = `${addApi ? "/api" : ""}${versionSegment}${controllerPath}${routePath}`;
|
|
102
|
+
application[method](fullPath, handlers);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
__name(defineRoutes, "defineRoutes");
|
|
108
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
109
|
+
0 && (module.exports = {
|
|
110
|
+
Controller,
|
|
111
|
+
Route,
|
|
112
|
+
Validate,
|
|
113
|
+
defineRoutes
|
|
114
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import "reflect-metadata";
|
|
6
|
+
|
|
7
|
+
// src/decorators/express.ts
|
|
8
|
+
function Route(method, path = "", ...middleware) {
|
|
9
|
+
return (target, _key, descriptor) => {
|
|
10
|
+
const routeHandlers = Reflect.getMetadata("routeHandlers", target) || /* @__PURE__ */ new Map();
|
|
11
|
+
if (!routeHandlers.has(method)) {
|
|
12
|
+
routeHandlers.set(method, /* @__PURE__ */ new Map());
|
|
13
|
+
}
|
|
14
|
+
routeHandlers.get(method)?.set(path, [...middleware, descriptor.value]);
|
|
15
|
+
Reflect.defineMetadata("routeHandlers", routeHandlers, target);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
__name(Route, "Route");
|
|
19
|
+
function Controller(baseRoute = "") {
|
|
20
|
+
return (target) => {
|
|
21
|
+
Reflect.defineMetadata("baseRoute", baseRoute, target);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
__name(Controller, "Controller");
|
|
25
|
+
|
|
26
|
+
// src/decorators/validation.ts
|
|
27
|
+
import Joi from "joi";
|
|
28
|
+
function Validate(schema) {
|
|
29
|
+
return function(_target, _propertyKey, descriptor) {
|
|
30
|
+
const originalMethod = descriptor.value;
|
|
31
|
+
descriptor.value = async function(req, res, next) {
|
|
32
|
+
try {
|
|
33
|
+
await schema.validateAsync(req.body);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error instanceof Joi.ValidationError) {
|
|
36
|
+
return res.status(422).json({
|
|
37
|
+
message: "Validation failed",
|
|
38
|
+
details: error.details.map((d) => ({
|
|
39
|
+
field: d.path.join("."),
|
|
40
|
+
message: d.message
|
|
41
|
+
}))
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return next(error);
|
|
45
|
+
}
|
|
46
|
+
return originalMethod.call(this, req, res, next);
|
|
47
|
+
};
|
|
48
|
+
return descriptor;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
__name(Validate, "Validate");
|
|
52
|
+
|
|
53
|
+
// src/functions/routes.ts
|
|
54
|
+
function defineRoutes(controllers, application, addApi = true, version = 1) {
|
|
55
|
+
for (const ControllerClass of controllers) {
|
|
56
|
+
const controller = new ControllerClass();
|
|
57
|
+
const routeHandlers = Reflect.getMetadata(
|
|
58
|
+
"routeHandlers",
|
|
59
|
+
controller
|
|
60
|
+
);
|
|
61
|
+
const controllerPath = Reflect.getMetadata("baseRoute", controller.constructor) ?? "";
|
|
62
|
+
if (!routeHandlers) continue;
|
|
63
|
+
for (const [method, routes] of routeHandlers) {
|
|
64
|
+
for (const [routePath, handlers] of routes) {
|
|
65
|
+
const versionSegment = addApi && version ? `/v${version}` : "";
|
|
66
|
+
const fullPath = `${addApi ? "/api" : ""}${versionSegment}${controllerPath}${routePath}`;
|
|
67
|
+
application[method](fullPath, handlers);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
__name(defineRoutes, "defineRoutes");
|
|
73
|
+
export {
|
|
74
|
+
Controller,
|
|
75
|
+
Route,
|
|
76
|
+
Validate,
|
|
77
|
+
defineRoutes
|
|
78
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
|
3
|
+
import tsParser from "@typescript-eslint/parser";
|
|
4
|
+
import prettierConfig from "eslint-config-prettier";
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
files: ["src/**/*.ts"],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
parser: tsParser,
|
|
12
|
+
parserOptions: {
|
|
13
|
+
project: "./tsconfig.json",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
plugins: {
|
|
17
|
+
"@typescript-eslint": tsPlugin,
|
|
18
|
+
},
|
|
19
|
+
rules: {
|
|
20
|
+
...tsPlugin.configs.recommended.rules,
|
|
21
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
22
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
23
|
+
"@typescript-eslint/no-unused-vars": [
|
|
24
|
+
"error",
|
|
25
|
+
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
prettierConfig,
|
|
30
|
+
{
|
|
31
|
+
ignores: ["dist/**", "node_modules/**"],
|
|
32
|
+
},
|
|
33
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deco-express",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": "ndrolp",
|
|
5
|
+
"description": "Decorator-based utilities for building Express.js REST APIs in TypeScript",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
14
|
+
"lint": "eslint src",
|
|
15
|
+
"lint:fix": "eslint src --fix",
|
|
16
|
+
"format": "prettier --write src",
|
|
17
|
+
"format:check": "prettier --check src"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"express",
|
|
21
|
+
"typescript",
|
|
22
|
+
"decorators",
|
|
23
|
+
"rest",
|
|
24
|
+
"api",
|
|
25
|
+
"controller",
|
|
26
|
+
"routing",
|
|
27
|
+
"joi",
|
|
28
|
+
"validation"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@eslint/js": "^10.0.1",
|
|
36
|
+
"@types/express": "^5.0.1",
|
|
37
|
+
"@types/node": "^22.15.17",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
|
39
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
40
|
+
"eslint": "^10.0.3",
|
|
41
|
+
"eslint-config-prettier": "^10.1.8",
|
|
42
|
+
"prettier": "^3.8.1",
|
|
43
|
+
"tsup": "^8.4.0",
|
|
44
|
+
"typescript": "^5.8.3",
|
|
45
|
+
"vitest": "^3.1.1"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"express": "^5.1.0",
|
|
49
|
+
"joi": "^17.13.3",
|
|
50
|
+
"reflect-metadata": "^0.2.2"
|
|
51
|
+
}
|
|
52
|
+
}
|