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.
@@ -0,0 +1,2 @@
1
+ dist/
2
+ node_modules/
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "trailingComma": "all",
5
+ "printWidth": 80,
6
+ "tabWidth": 2
7
+ }
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
@@ -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 };
@@ -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
+ }