codeweaver 1.0.15 → 2.0.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/.vscode/settings.json +3 -0
- package/README.md +261 -56
- package/package.json +21 -15
- package/src/config.ts +56 -15
- package/src/constants.ts +6 -0
- package/src/main.ts +82 -0
- package/src/routers/{index.ts → index.router.ts} +2 -2
- package/src/routers/orders/dto/order.dto.ts +41 -0
- package/src/routers/orders/index.router.ts +157 -0
- package/src/routers/orders/order.controller.ts +178 -0
- package/src/routers/products/dto/product.dto.ts +37 -0
- package/src/routers/products/index.router.ts +200 -0
- package/src/routers/products/product.controller.ts +217 -0
- package/src/routers/users/dto/user.dto.ts +17 -3
- package/src/routers/users/index.router.ts +96 -0
- package/src/routers/users/user.controller.ts +138 -18
- package/src/swagger-options.ts +54 -0
- package/src/utilities/assign.ts +81 -0
- package/src/utilities/error-handling.ts +120 -0
- package/src/utilities/types.ts +23 -0
- package/tsconfig.json +6 -3
- package/tsconfig.paths.json +8 -0
- package/src/app.ts +0 -95
- package/src/routers/orders/index.ts +0 -40
- package/src/routers/products/index.ts +0 -167
- package/src/routers/users/index.ts +0 -81
package/README.md
CHANGED
|
@@ -2,25 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
**Codeweaver** is a lightweight
|
|
5
|
+
**Codeweaver** is a lightweight microframework created with `Express`, `TypeScript`, and `Zod` (v4), seamlessly integrated with `Swagger` for comprehensive API documentation. Its modular architecture for routers promotes scalability and organized development, making it easy to expand and maintain.
|
|
6
6
|
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- **Modular Router Structure**: Each router is automatically imported and mounted, providing clean separation of endpoints and logic.
|
|
10
|
-
- **Express Framework**: A lightweight web application framework for building web applications in Node.js.
|
|
11
|
-
- **TypeScript**: Provides strong typing for better development experience and less runtime errors.
|
|
12
|
-
- **Swagger Integration**: Automatically generates interactive API documentation, making it easy for developers and consumers to understand the available endpoints.
|
|
13
|
-
- **Async Handlers**: Supports async/await syntax for writing cleaner and more maintainable asynchronous code without deeply nested callbacks.
|
|
14
|
-
|
|
15
|
-
## Technologies Used
|
|
7
|
+
## Features and Technologies Used
|
|
16
8
|
|
|
17
9
|
- **Node.js**
|
|
18
|
-
- **Express
|
|
19
|
-
- **TypeScript
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
10
|
+
- **Express**: A lightweight web framework for building server-side applications in Node.js.
|
|
11
|
+
- **TypeScript**: Adds strong typing for enhanced development experience and reduced runtime errors.
|
|
12
|
+
- **Modular Router Structure**: Automates importing and mounting routers, ensuring a clean separation of endpoints and logic for easier scalability.
|
|
13
|
+
- **Swagger Integration**: Automatically generates interactive API documentation, facilitating easier understanding of available endpoints for developers and consumers.
|
|
14
|
+
- **Async Handlers**: Utilizes async/await syntax for cleaner, more maintainable asynchronous code without callback nesting.
|
|
15
|
+
- **Zod**: Implements schema validation for input data.
|
|
16
|
+
- **ts-zod-decorators**: Enables validation using Zod schemas through decorators.
|
|
17
|
+
- **utils-decorators**: Provides middleware utilities such as throttling and error handling for a more robust application.
|
|
24
18
|
|
|
25
19
|
## Installation
|
|
26
20
|
|
|
@@ -29,6 +23,7 @@ To get started with the project, follow these steps:
|
|
|
29
23
|
1. **Clone the repository**:
|
|
30
24
|
|
|
31
25
|
```bash
|
|
26
|
+
npm install -g codeweaver
|
|
32
27
|
npx create-codeweaver-app my-app
|
|
33
28
|
cd my-app
|
|
34
29
|
```
|
|
@@ -51,63 +46,139 @@ To get started with the project, follow these steps:
|
|
|
51
46
|
|
|
52
47
|
```bash
|
|
53
48
|
npm run build
|
|
49
|
+
npm run serve
|
|
54
50
|
```
|
|
55
51
|
|
|
56
52
|
## Sample Project Structure
|
|
57
53
|
|
|
58
54
|
**/src**
|
|
59
55
|
├── **/routers** `Directory containing all router files`
|
|
60
|
-
│ ├── **/
|
|
61
|
-
│ │ ├──
|
|
62
|
-
│ │ ├──
|
|
56
|
+
│ ├── **/users** `Routers for user-related endpoints`
|
|
57
|
+
│ │ ├── index.router.ts `/users`
|
|
58
|
+
│ │ ├── user-router2.router.ts `/users/user-router2`
|
|
63
59
|
│ │ ├── user.controller.ts
|
|
64
60
|
│ │ ├── user.service.ts
|
|
65
61
|
│ │ └── user.dto.ts
|
|
66
|
-
│ ├── **/
|
|
67
|
-
│ │ ├── index.ts `/
|
|
68
|
-
│ │ ├── product_test.spec.ts
|
|
62
|
+
│ ├── **/products** `Routers for product-related endpoints`
|
|
63
|
+
│ │ ├── index.router.ts `/products`
|
|
69
64
|
│ │ ├── product.controller.ts
|
|
70
65
|
│ │ ├── product.service.ts
|
|
71
|
-
│ │ ├── **/
|
|
66
|
+
│ │ ├── **/dtos**
|
|
72
67
|
│ │ │ └── product.dto.ts
|
|
73
|
-
|
|
74
|
-
│
|
|
75
|
-
│ │ ├──
|
|
68
|
+
| | │ └── product-types.dto.ts
|
|
69
|
+
│ ├── **/orders** `Routers for order-related endpoints`
|
|
70
|
+
│ │ ├── index.router.ts `/orders`
|
|
76
71
|
│ │ ├── order.controller.ts
|
|
77
|
-
│ │
|
|
78
|
-
│
|
|
72
|
+
│ │ ├── order.controller.spec.ts
|
|
73
|
+
│ │ ├── order.service.ts
|
|
74
|
+
│ │ └── order.service.spec.ts
|
|
75
|
+
│ └── index.router.ts `Home page`
|
|
76
|
+
│ └── app.controller.ts `Home page`
|
|
79
77
|
├── app.ts `Main application file`
|
|
80
78
|
├── config.ts `Application configuration file`
|
|
81
79
|
└── ... `Other files (middleware, models, etc.)`
|
|
82
80
|
|
|
83
81
|
### Router Directory
|
|
84
82
|
|
|
85
|
-
Each router file in the `/routers` directory is organized to handle related endpoints. The `app.ts` file automatically imports all routers and mounts them
|
|
83
|
+
Each router file in the `/routers` directory is organized to handle related endpoints. The `app.ts` file automatically imports all routers and mounts them on the main Express application, making it straightforward to add new routes without touching central code.
|
|
86
84
|
|
|
87
|
-
Files
|
|
85
|
+
Files ending with `.router.ts` or `.router.js` are automatically included in the router list and can be reused for various purposes within the application.
|
|
88
86
|
|
|
89
87
|
Example of a basic router:
|
|
90
88
|
|
|
91
89
|
```typescript
|
|
92
90
|
import { Router, Request, Response } from "express";
|
|
93
91
|
import asyncHandler from "express-async-handler";
|
|
92
|
+
import UserController from "./user.controller";
|
|
94
93
|
|
|
95
94
|
const router = Router();
|
|
95
|
+
const userController = new UserController();
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @swagger
|
|
99
|
+
* /users:
|
|
100
|
+
* post:
|
|
101
|
+
* summary: Create a user
|
|
102
|
+
* description: Create a new user.
|
|
103
|
+
* consumes:
|
|
104
|
+
* - application/json
|
|
105
|
+
* produces:
|
|
106
|
+
* - application/json
|
|
107
|
+
* parameters:
|
|
108
|
+
* - in: body
|
|
109
|
+
* name: user
|
|
110
|
+
* required: true
|
|
111
|
+
* schema:
|
|
112
|
+
* type: object
|
|
113
|
+
* required:
|
|
114
|
+
* - username
|
|
115
|
+
* - email
|
|
116
|
+
* - password
|
|
117
|
+
* properties:
|
|
118
|
+
* username:
|
|
119
|
+
* type: string
|
|
120
|
+
* minLength: 3
|
|
121
|
+
* example: JessicaSmith
|
|
122
|
+
* email:
|
|
123
|
+
* type: string
|
|
124
|
+
* format: email
|
|
125
|
+
* example: user@example.com
|
|
126
|
+
* password:
|
|
127
|
+
* type: string
|
|
128
|
+
* minLength: 6
|
|
129
|
+
* example: securePassword123
|
|
130
|
+
* responses:
|
|
131
|
+
* 201:
|
|
132
|
+
* description: User created
|
|
133
|
+
*/
|
|
134
|
+
router.post(
|
|
135
|
+
"/",
|
|
136
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
137
|
+
const user = await userController.create(req.body);
|
|
138
|
+
res.status(201).json(user);
|
|
139
|
+
})
|
|
140
|
+
);
|
|
96
141
|
|
|
97
142
|
/**
|
|
98
143
|
* @swagger
|
|
99
|
-
*
|
|
144
|
+
* /users/{id}:
|
|
100
145
|
* get:
|
|
101
|
-
* summary: Get
|
|
102
|
-
*
|
|
146
|
+
* summary: Get a user by ID
|
|
147
|
+
* parameters:
|
|
148
|
+
* - name: id
|
|
149
|
+
* in: path
|
|
150
|
+
* required: true
|
|
151
|
+
* description: The ID of the product
|
|
152
|
+
* schema:
|
|
153
|
+
* type: integer
|
|
103
154
|
* responses:
|
|
104
155
|
* 200:
|
|
105
|
-
* description:
|
|
156
|
+
* description: A user object
|
|
157
|
+
* 404:
|
|
158
|
+
* description: user not found
|
|
159
|
+
*/
|
|
160
|
+
router.get(
|
|
161
|
+
"/:id",
|
|
162
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
163
|
+
const user = await userController.get(req.params.id);
|
|
164
|
+
res.json(user);
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @swagger
|
|
170
|
+
* /users:
|
|
171
|
+
* get:
|
|
172
|
+
* summary: Get users
|
|
173
|
+
* description: Returns a list of user objects.
|
|
174
|
+
* responses:
|
|
175
|
+
* 200:
|
|
176
|
+
* description: A list of user objects
|
|
106
177
|
*/
|
|
107
178
|
router.get(
|
|
108
179
|
"/",
|
|
109
180
|
asyncHandler(async (req: Request, res: Response) => {
|
|
110
|
-
res.
|
|
181
|
+
res.json(await userController.getAll());
|
|
111
182
|
})
|
|
112
183
|
);
|
|
113
184
|
|
|
@@ -131,13 +202,27 @@ Here’s a brief breakdown of key components used in the `UserController`:
|
|
|
131
202
|
```typescript
|
|
132
203
|
import { z } from "zod";
|
|
133
204
|
|
|
134
|
-
|
|
205
|
+
/**
|
|
206
|
+
* Zod schema for User entity
|
|
207
|
+
* @typedef {Object} ZodUser
|
|
208
|
+
* @property {number} id - Unique identifier (min 1)
|
|
209
|
+
* @property {string} username - Username (min 3 chars)
|
|
210
|
+
* @property {string} email - Valid email format
|
|
211
|
+
* @property {string} password - Password (min 6 chars)
|
|
212
|
+
*/
|
|
213
|
+
export const ZodUser = z.object({
|
|
214
|
+
id: z.number().min(1).int(),
|
|
135
215
|
username: z.string().min(3),
|
|
136
|
-
email: z.
|
|
216
|
+
email: z.email(),
|
|
137
217
|
password: z.string().min(6),
|
|
138
218
|
});
|
|
139
219
|
|
|
140
|
-
export
|
|
220
|
+
export const ZodUserCreationDto = ZodUser.omit({ id: true });
|
|
221
|
+
export const ZodUserDto = ZodUser.omit({ password: true });
|
|
222
|
+
|
|
223
|
+
export type User = z.infer<typeof ZodUser>;
|
|
224
|
+
export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
|
|
225
|
+
export type UserDto = z.infer<typeof ZodUserDto>;
|
|
141
226
|
```
|
|
142
227
|
|
|
143
228
|
- **Throttling and Rate Limiting**: The `@rateLimit` decorator is applied to safeguard the application's endpoints from abuse by limiting how frequently a particular method can be invoked.
|
|
@@ -149,37 +234,157 @@ By using a well-organized controller structure, this project makes it easier to
|
|
|
149
234
|
Here is a quick reference to the UserController in practice:
|
|
150
235
|
|
|
151
236
|
```typescript
|
|
152
|
-
import {
|
|
153
|
-
|
|
154
|
-
|
|
237
|
+
import {
|
|
238
|
+
User,
|
|
239
|
+
ZodUserCreationDto,
|
|
240
|
+
UserCreationDto,
|
|
241
|
+
UserDto,
|
|
242
|
+
} from "./dto/user.dto";
|
|
243
|
+
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
244
|
+
import { Validate, ZodInput } from "ts-zod4-decorators";
|
|
245
|
+
import { ResponseError } from "@/types";
|
|
246
|
+
import { parseId } from "@/utilities/error-handling";
|
|
247
|
+
import config from "@/config";
|
|
248
|
+
|
|
249
|
+
// Array to store users (as a mock database)
|
|
250
|
+
const users = [
|
|
251
|
+
{
|
|
252
|
+
id: 1,
|
|
253
|
+
username: "johndoe",
|
|
254
|
+
email: "johndoe@gmail.com",
|
|
255
|
+
password: "S3cur3P@ssw0rd",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: 2,
|
|
259
|
+
username: "janesmith",
|
|
260
|
+
email: "janesmith@yahoo.com",
|
|
261
|
+
password: "P@ssw0rd2024",
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 3,
|
|
265
|
+
username: "michael89",
|
|
266
|
+
email: "michael89@hotmail.com",
|
|
267
|
+
password: "M1chael!2024",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: 4,
|
|
271
|
+
username: "lisa.wong",
|
|
272
|
+
email: "lisa.wong@example.com",
|
|
273
|
+
password: "L1saW0ng!2024",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 5,
|
|
277
|
+
username: "alex_k",
|
|
278
|
+
email: "alex.k@gmail.com",
|
|
279
|
+
password: "A1ex#Key2024",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: 6,
|
|
283
|
+
username: "emilyj",
|
|
284
|
+
email: "emilyj@hotmail.com",
|
|
285
|
+
password: "Em!ly0101",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: 7,
|
|
289
|
+
username: "davidparker",
|
|
290
|
+
email: "david.parker@yahoo.com",
|
|
291
|
+
password: "D@v!d2024",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: 8,
|
|
295
|
+
username: "sophia_m",
|
|
296
|
+
email: "sophia.m@gmail.com",
|
|
297
|
+
password: "Sophi@2024",
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: 9,
|
|
301
|
+
username: "chrisw",
|
|
302
|
+
email: "chrisw@outlook.com",
|
|
303
|
+
password: "Chri$Wong21",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: 10,
|
|
307
|
+
username: "natalie_b",
|
|
308
|
+
email: "natalie_b@gmail.com",
|
|
309
|
+
password: "N@talie#B2024",
|
|
310
|
+
},
|
|
311
|
+
];
|
|
155
312
|
|
|
156
313
|
function exceedHandler() {
|
|
157
|
-
|
|
314
|
+
const message = "Too much call in allowed window";
|
|
315
|
+
throw new ResponseError(message, 429);
|
|
158
316
|
}
|
|
159
317
|
|
|
160
|
-
function
|
|
161
|
-
|
|
318
|
+
function getUserErrorHandler(e: Error) {
|
|
319
|
+
const message = "User not found.";
|
|
320
|
+
throw new ResponseError(message, 404, e.message);
|
|
162
321
|
}
|
|
163
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Controller for handling user-related operations
|
|
325
|
+
* @class UserController
|
|
326
|
+
* @desc Provides methods for user management including CRUD operations
|
|
327
|
+
*/
|
|
164
328
|
export default class UserController {
|
|
165
|
-
//constructor(private readonly userService: UserService) { }
|
|
329
|
+
// constructor(private readonly userService: UserService) { }
|
|
166
330
|
|
|
167
|
-
// Throttle the createUser method to 1 request per 200 milliseconds
|
|
168
331
|
@rateLimit({
|
|
169
|
-
timeSpanMs:
|
|
170
|
-
allowedCalls:
|
|
332
|
+
timeSpanMs: config.rateLimitTimeSpan,
|
|
333
|
+
allowedCalls: config.rateLimitAllowedCalls,
|
|
171
334
|
exceedHandler,
|
|
172
335
|
})
|
|
336
|
+
@Validate
|
|
337
|
+
/**
|
|
338
|
+
* Create a new user
|
|
339
|
+
* @param {UserCreationDto} user - User creation data validated by Zod schema
|
|
340
|
+
* @returns {Promise<void>}
|
|
341
|
+
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
342
|
+
* @throws {ResponseError} 400 - Invalid input data
|
|
343
|
+
*/
|
|
344
|
+
public async create(@ZodInput(ZodUserCreationDto) user: UserCreationDto) {
|
|
345
|
+
users.push({ ...user, id: users.length + 1 });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
@memoizeAsync(config.memoizeTime)
|
|
173
349
|
@onError({
|
|
174
|
-
func:
|
|
350
|
+
func: getUserErrorHandler,
|
|
175
351
|
})
|
|
176
|
-
@
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
352
|
+
@rateLimit({
|
|
353
|
+
timeSpanMs: config.rateLimitTimeSpan,
|
|
354
|
+
allowedCalls: config.rateLimitAllowedCalls,
|
|
355
|
+
exceedHandler,
|
|
356
|
+
})
|
|
357
|
+
/**
|
|
358
|
+
* Get user by ID
|
|
359
|
+
* @param {string} id - User ID as string
|
|
360
|
+
* @returns {Promise<User>} User details or error object
|
|
361
|
+
* @throws {ResponseError} 404 - User not found
|
|
362
|
+
* @throws {ResponseError} 400 - Invalid ID format
|
|
363
|
+
*/
|
|
364
|
+
public async get(id: string): Promise<UserDto> {
|
|
365
|
+
const response = parseId(id);
|
|
366
|
+
const user = users.find((user) => user.id === response);
|
|
367
|
+
if (user == null) throw new ResponseError("User dose not exist.", 404);
|
|
368
|
+
return user satisfies User;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@memoizeAsync(config.memoizeTime)
|
|
372
|
+
@timeout(config.timeout)
|
|
373
|
+
@rateLimit({
|
|
374
|
+
timeSpanMs: config.rateLimitTimeSpan,
|
|
375
|
+
allowedCalls: config.rateLimitAllowedCalls,
|
|
376
|
+
exceedHandler,
|
|
377
|
+
})
|
|
378
|
+
/**
|
|
379
|
+
* Get all users with masked passwords
|
|
380
|
+
* @returns {Promise<UserDto[]>} List of users with hidden password fields
|
|
381
|
+
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
382
|
+
*/
|
|
383
|
+
public async getAll(): Promise<UserDto[]> {
|
|
384
|
+
return users.map((user) => ({
|
|
385
|
+
...user,
|
|
386
|
+
password: "?",
|
|
387
|
+
}));
|
|
183
388
|
}
|
|
184
389
|
}
|
|
185
390
|
```
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeweaver",
|
|
3
|
-
"version": "
|
|
4
|
-
"main": "src/
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"main": "src/main.ts",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-codeweaver-app": "./command.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"start": "nodemon src/
|
|
10
|
-
"
|
|
11
|
-
"
|
|
9
|
+
"start": "nodemon -r tsconfig-paths/register src/main.ts",
|
|
10
|
+
"dev": "nodemon -r tsconfig-paths/register src/main.ts",
|
|
11
|
+
"build": "tsc && tsc-alias && copyfiles -u 1 \"src/**/*.{json,env,sqlite,db,sql,yaml,yml,xml,txt,md}\" dist",
|
|
12
|
+
"serve": "cross-env TSCONFIG_PATH=tsconfig.paths.json node -r tsconfig-paths/register dist/main.js"
|
|
12
13
|
},
|
|
13
14
|
"keywords": [
|
|
14
15
|
"codeweaver",
|
|
@@ -27,23 +28,28 @@
|
|
|
27
28
|
"swagger",
|
|
28
29
|
"zod"
|
|
29
30
|
],
|
|
30
|
-
"author": "
|
|
31
|
+
"author": "js-code-crafter",
|
|
31
32
|
"license": "MIT",
|
|
32
33
|
"description": "A lightweight framework built on top of Express and TypeScript",
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"
|
|
35
|
+
"dotenv": "^17.2.3",
|
|
36
|
+
"express": "^5.1.0",
|
|
35
37
|
"express-async-handler": "^1.2.0",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
38
|
+
"swagger-jsdoc": "^3.7.0",
|
|
39
|
+
"ts-zod4-decorators": "^1.0.0",
|
|
40
|
+
"utils-decorators": "^2.10.0",
|
|
41
|
+
"zod": "^4.0.14"
|
|
39
42
|
},
|
|
40
43
|
"devDependencies": {
|
|
41
|
-
"@types/express": "^5.0.
|
|
42
|
-
"@types/node": "^
|
|
43
|
-
"
|
|
44
|
-
"
|
|
44
|
+
"@types/express": "^5.0.3",
|
|
45
|
+
"@types/node": "^24.1.0",
|
|
46
|
+
"copyfiles": "^2.4.1",
|
|
47
|
+
"cross-env": "^10.0.0",
|
|
48
|
+
"nodemon": "^3.1.10",
|
|
45
49
|
"swagger-ui-express": "^5.0.1",
|
|
46
50
|
"ts-node": "^10.9.2",
|
|
47
|
-
"
|
|
51
|
+
"tsc-alias": "^1.8.16",
|
|
52
|
+
"tsconfig-paths": "^4.2.0",
|
|
53
|
+
"typescript": "^5.9.2"
|
|
48
54
|
}
|
|
49
55
|
}
|
package/src/config.ts
CHANGED
|
@@ -1,32 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
memoizeTime,
|
|
3
|
+
productionEnvironment,
|
|
4
|
+
rateLimitTimeSpan,
|
|
5
|
+
rateLimitAllowedCalls,
|
|
6
|
+
timeout,
|
|
7
|
+
portNumber,
|
|
8
|
+
} from "./constants";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Server configuration interface
|
|
12
|
+
* @interface
|
|
13
|
+
* @property {string} url - Base server URL
|
|
14
|
+
*/
|
|
1
15
|
interface Server {
|
|
2
16
|
url: string;
|
|
3
17
|
}
|
|
4
18
|
|
|
19
|
+
/**
|
|
20
|
+
* API information structure
|
|
21
|
+
* @interface
|
|
22
|
+
* @property {string} title - API title
|
|
23
|
+
* @property {string} version - API version
|
|
24
|
+
* @property {string} description - API description
|
|
25
|
+
*/
|
|
5
26
|
interface Info {
|
|
6
27
|
title: string;
|
|
7
28
|
version: string;
|
|
8
29
|
description: string;
|
|
9
30
|
}
|
|
10
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Swagger definition structure
|
|
34
|
+
* @interface
|
|
35
|
+
* @property {string} openApi - OpenAPI specification version
|
|
36
|
+
* @property {Info} info - API information
|
|
37
|
+
* @property {Server[]} servers - List of server configurations
|
|
38
|
+
*/
|
|
11
39
|
interface SwaggerDefinition {
|
|
12
40
|
openApi: string;
|
|
13
41
|
info: Info;
|
|
14
42
|
servers: Server[];
|
|
15
43
|
}
|
|
16
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Swagger configuration options
|
|
47
|
+
* @interface
|
|
48
|
+
* @property {SwaggerDefinition} swaggerDefinition - Swagger definition object
|
|
49
|
+
* @property {string[]} apis - Paths to API documentation files
|
|
50
|
+
*/
|
|
17
51
|
interface SwaggerOptions {
|
|
18
52
|
swaggerDefinition: SwaggerDefinition;
|
|
19
53
|
apis: string[];
|
|
20
54
|
}
|
|
21
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Main application configuration
|
|
58
|
+
* @interface
|
|
59
|
+
* @property {boolean} devMode - Development mode flag
|
|
60
|
+
* @property {string} port - Server port
|
|
61
|
+
* @property {SwaggerOptions} swaggerOptions - Swagger configuration
|
|
62
|
+
*/
|
|
22
63
|
interface Config {
|
|
23
64
|
devMode: boolean;
|
|
24
|
-
port:
|
|
65
|
+
port: number;
|
|
25
66
|
swaggerOptions: SwaggerOptions;
|
|
67
|
+
timeout: number;
|
|
68
|
+
rateLimitTimeSpan: number;
|
|
69
|
+
rateLimitAllowedCalls: number;
|
|
70
|
+
memoizeTime: number;
|
|
26
71
|
}
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
72
|
+
|
|
73
|
+
const port = Number(process.env.PORT) || portNumber;
|
|
74
|
+
|
|
75
|
+
let config: Config = {
|
|
76
|
+
devMode: process.env.NODE_ENV !== productionEnvironment,
|
|
30
77
|
port,
|
|
31
78
|
swaggerOptions: {
|
|
32
79
|
swaggerDefinition: {
|
|
@@ -49,17 +96,11 @@ const config: Config = {
|
|
|
49
96
|
"./src/routers/**/*.js",
|
|
50
97
|
], // Path to the API docs
|
|
51
98
|
},
|
|
99
|
+
timeout: Number(process.env.TIMEOUT) || timeout,
|
|
100
|
+
rateLimitTimeSpan: Number(process.env.RATE_LIMIT) || rateLimitTimeSpan,
|
|
101
|
+
rateLimitAllowedCalls:
|
|
102
|
+
Number(process.env.RATE_LIMIT) || rateLimitAllowedCalls,
|
|
103
|
+
memoizeTime: Number(process.env.MEMOIZE_TIME) || memoizeTime,
|
|
52
104
|
};
|
|
53
105
|
|
|
54
|
-
// Other configurations:
|
|
55
|
-
//
|
|
56
|
-
// config.jwt_key = config.devMode ? "" : "";
|
|
57
|
-
// config.jwt_expiration = config.devMode ? 360000 : 360000;
|
|
58
|
-
// config.dbConnectionString = config.devMode ? `mongoDb url` : `mongoDb url`;
|
|
59
|
-
// config.mongoDebug = config.devMode;
|
|
60
|
-
// config.port = config.devMode ? 3000 : 3000;
|
|
61
|
-
// config.host = config.devMode ? "localhost" : "localhost";
|
|
62
|
-
// config.env = config.devMode ? "development" : "production";
|
|
63
|
-
// config.mongoUrl = config.devMode ? "mongodb://localhost:27017/test" : "mongodb://localhost:27017/test";
|
|
64
|
-
|
|
65
106
|
export default config;
|
package/src/constants.ts
ADDED
package/src/main.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import express, { NextFunction, Request, Response } from "express";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import config from "./config";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Recursively loads Express routers from directory
|
|
8
|
+
* @param {string} routerPath - Directory path to scan
|
|
9
|
+
* @param {string} [basePath=""] - Base route path
|
|
10
|
+
*/
|
|
11
|
+
function loadRouters(routerPath: string, basePath: string = "") {
|
|
12
|
+
// Read entries with their type info
|
|
13
|
+
const entries = fs.readdirSync(routerPath, { withFileTypes: true });
|
|
14
|
+
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(routerPath, entry.name);
|
|
17
|
+
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
// Recurse into subdirectories
|
|
20
|
+
const subRoutePath = path
|
|
21
|
+
.join(basePath, entry.name)
|
|
22
|
+
.replace(/\\/g, "/")
|
|
23
|
+
.replace(/\/?index$/g, "");
|
|
24
|
+
loadRouters(fullPath, subRoutePath);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Only handle router files
|
|
29
|
+
if (
|
|
30
|
+
!entry.name.endsWith(".router.ts") &&
|
|
31
|
+
!entry.name.endsWith(".router.js")
|
|
32
|
+
) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Build route path safely
|
|
37
|
+
const routePath = path
|
|
38
|
+
.join(basePath, entry.name.replace(/\.router\.[tj]s$/, ""))
|
|
39
|
+
.replace(/\\/g, "/")
|
|
40
|
+
.replace(/\/?index$/g, "");
|
|
41
|
+
|
|
42
|
+
// Optional: skip if the target path would be empty (maps to /)
|
|
43
|
+
const mountPath = "/" + (routePath || "");
|
|
44
|
+
|
|
45
|
+
// Import and mount
|
|
46
|
+
const router = require(fullPath);
|
|
47
|
+
app.use(mountPath, router);
|
|
48
|
+
console.log(`Mounted ${entry.name} at ${mountPath}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const app = express();
|
|
53
|
+
app.use(express.json());
|
|
54
|
+
app.use(express.urlencoded({ extended: true }));
|
|
55
|
+
|
|
56
|
+
//app.use(cors());
|
|
57
|
+
|
|
58
|
+
// Automatically import all routers from the /src/routers directory
|
|
59
|
+
const routersPath = path.join(__dirname, "/routers");
|
|
60
|
+
loadRouters(routersPath);
|
|
61
|
+
|
|
62
|
+
// Swagger setup
|
|
63
|
+
if (config.devMode) {
|
|
64
|
+
const swaggerJsDoc = require("swagger-jsdoc");
|
|
65
|
+
const swaggerUi = require("swagger-ui-express");
|
|
66
|
+
const swaggerDocs = swaggerJsDoc(config.swaggerOptions);
|
|
67
|
+
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// General error handler
|
|
71
|
+
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
|
|
72
|
+
res.status(500).json(err);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Start the server
|
|
76
|
+
app.listen(config.port, () => {
|
|
77
|
+
console.log(`Server is running on http://localhost:${config.port}`);
|
|
78
|
+
if (config.devMode)
|
|
79
|
+
console.log(
|
|
80
|
+
`Swagger UI is available at http://localhost:${config.port}/api-docs`
|
|
81
|
+
);
|
|
82
|
+
});
|
|
@@ -11,12 +11,12 @@ const router = Router();
|
|
|
11
11
|
* description: Returns the home page.
|
|
12
12
|
* responses:
|
|
13
13
|
* 200:
|
|
14
|
-
* description:
|
|
14
|
+
* description: Hi there!
|
|
15
15
|
*/
|
|
16
16
|
router.get(
|
|
17
17
|
"/",
|
|
18
18
|
asyncHandler(async (req: Request, res: Response) => {
|
|
19
|
-
res.send("
|
|
19
|
+
res.send("Hi there!");
|
|
20
20
|
})
|
|
21
21
|
);
|
|
22
22
|
|