codeweaver 1.1.0 → 2.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/.vscode/settings.json +3 -0
- package/README.md +114 -181
- package/package.json +12 -7
- package/src/app.ts +2 -112
- package/src/config.ts +33 -64
- package/src/constants.ts +7 -0
- package/src/db.ts +183 -0
- package/src/entities/order.entity.ts +68 -0
- package/src/entities/product.entity.ts +75 -0
- package/src/entities/user.entity.ts +38 -0
- package/src/main.ts +85 -0
- package/src/routers/orders/dto/order.dto.ts +54 -29
- package/src/routers/orders/{index.ts → index.router.ts} +13 -13
- package/src/routers/orders/order.controller.ts +118 -120
- package/src/routers/products/dto/product.dto.ts +86 -30
- package/src/routers/products/{index.ts → index.router.ts} +14 -15
- package/src/routers/products/product.controller.ts +136 -161
- package/src/routers/users/dto/user.dto.ts +14 -18
- package/src/routers/users/{index.ts → index.router.ts} +6 -7
- package/src/routers/users/user.controller.ts +87 -118
- package/src/swagger-options.ts +39 -0
- package/src/utilities/assign.ts +66 -0
- package/src/utilities/cache/memory-cache.ts +74 -0
- package/src/utilities/cache/redis-cache.ts +111 -0
- package/src/utilities/conversion.ts +158 -0
- package/src/utilities/error-handling.ts +156 -0
- package/src/utilities/router.ts +0 -0
- package/tsconfig.json +1 -4
- package/tsconfig.paths.json +8 -10
- package/src/packages/ts-zod-decorators/index.ts +0 -3
- package/src/packages/ts-zod-decorators/validate.decorator.ts +0 -20
- package/src/packages/ts-zod-decorators/validator.class.ts +0 -72
- package/src/packages/ts-zod-decorators/zod-input.decorator.ts +0 -12
- package/src/packages/ts-zod-decorators/zod-output.decorator.ts +0 -11
- package/src/types.ts +0 -16
- package/src/utilities.ts +0 -47
- /package/src/routers/{index.ts → index.router.ts} +0 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
**Codeweaver** is
|
|
5
|
+
**Codeweaver** is an unopinionated microframework built 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. Routers are automatically discovered and wired up through a conventional folder structure, simplifying project organization and reducing boilerplate. Routers can be nested, allowing you to compose complex route trees by placing sub-routers inside parent router folders. It also uses `utils-decorators`, a collection of middleware utilities (throttling, caching, and error handling) designed to strengthen application resilience.
|
|
6
6
|
|
|
7
7
|
## Features and Technologies Used
|
|
8
8
|
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
- **Swagger Integration**: Automatically generates interactive API documentation, facilitating easier understanding of available endpoints for developers and consumers.
|
|
14
14
|
- **Async Handlers**: Utilizes async/await syntax for cleaner, more maintainable asynchronous code without callback nesting.
|
|
15
15
|
- **Zod**: Implements schema validation for input data.
|
|
16
|
-
- **
|
|
17
|
-
- **utils-decorators**: Provides middleware utilities such as throttling and error handling for a more robust application.
|
|
16
|
+
- **utils-decorators**: A collection of middleware utilities (throttling, caching, and error handling) designed to strengthen application resilience.
|
|
18
17
|
|
|
19
18
|
## Installation
|
|
20
19
|
|
|
@@ -53,34 +52,36 @@ To get started with the project, follow these steps:
|
|
|
53
52
|
|
|
54
53
|
**/src**
|
|
55
54
|
├── **/routers** `Directory containing all router files`
|
|
56
|
-
│ ├── **/
|
|
57
|
-
│ │ ├──
|
|
58
|
-
│ │ ├──
|
|
55
|
+
│ ├── **/users** `Routers for user-related endpoints`
|
|
56
|
+
│ │ ├── index.router.ts `/users`
|
|
57
|
+
│ │ ├── user-router2.router.ts `/users/user-router2`
|
|
59
58
|
│ │ ├── user.controller.ts
|
|
60
59
|
│ │ ├── user.service.ts
|
|
61
60
|
│ │ └── user.dto.ts
|
|
62
|
-
│ ├── **/
|
|
63
|
-
│ │ ├── index.ts `/
|
|
64
|
-
│ │ ├── product_test.spec.ts
|
|
61
|
+
│ ├── **/products** `Routers for product-related endpoints`
|
|
62
|
+
│ │ ├── index.router.ts `/products`
|
|
65
63
|
│ │ ├── product.controller.ts
|
|
66
64
|
│ │ ├── product.service.ts
|
|
67
|
-
│ │ ├── **/
|
|
65
|
+
│ │ ├── **/dtos**
|
|
68
66
|
│ │ │ └── product.dto.ts
|
|
69
|
-
|
|
70
|
-
│
|
|
71
|
-
│ │ ├──
|
|
67
|
+
| | │ └── product-types.dto.ts
|
|
68
|
+
│ ├── **/orders** `Routers for order-related endpoints`
|
|
69
|
+
│ │ ├── index.router.ts `/orders`
|
|
72
70
|
│ │ ├── order.controller.ts
|
|
73
|
-
│ │
|
|
74
|
-
│
|
|
71
|
+
│ │ ├── order.controller.spec.ts
|
|
72
|
+
│ │ ├── order.service.ts
|
|
73
|
+
│ │ └── order.service.spec.ts
|
|
74
|
+
│ └── index.router.ts `Home page`
|
|
75
|
+
│ └── app.controller.ts `Home page`
|
|
75
76
|
├── app.ts `Main application file`
|
|
76
77
|
├── config.ts `Application configuration file`
|
|
77
78
|
└── ... `Other files (middleware, models, etc.)`
|
|
78
79
|
|
|
79
80
|
### Router Directory
|
|
80
81
|
|
|
81
|
-
Each router file in the `/routers` directory is organized to handle related endpoints. The `app.ts` file automatically imports all routers and mounts them
|
|
82
|
+
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.
|
|
82
83
|
|
|
83
|
-
Files
|
|
84
|
+
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.
|
|
84
85
|
|
|
85
86
|
Example of a basic router:
|
|
86
87
|
|
|
@@ -88,7 +89,6 @@ Example of a basic router:
|
|
|
88
89
|
import { Router, Request, Response } from "express";
|
|
89
90
|
import asyncHandler from "express-async-handler";
|
|
90
91
|
import UserController from "./user.controller";
|
|
91
|
-
import { sendError } from "@src/utilities";
|
|
92
92
|
|
|
93
93
|
const router = Router();
|
|
94
94
|
const userController = new UserController();
|
|
@@ -133,8 +133,9 @@ const userController = new UserController();
|
|
|
133
133
|
router.post(
|
|
134
134
|
"/",
|
|
135
135
|
asyncHandler(async (req: Request, res: Response) => {
|
|
136
|
-
const user = await userController.
|
|
137
|
-
|
|
136
|
+
const user = await userController.validateUserCreationDto(req.body);
|
|
137
|
+
await userController.create(user);
|
|
138
|
+
res.status(201).send();
|
|
138
139
|
})
|
|
139
140
|
);
|
|
140
141
|
|
|
@@ -159,10 +160,9 @@ router.post(
|
|
|
159
160
|
router.get(
|
|
160
161
|
"/:id",
|
|
161
162
|
asyncHandler(async (req: Request, res: Response) => {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
else res.json(user);
|
|
163
|
+
const id = await userController.validateId(req.params.id);
|
|
164
|
+
const user = await userController.get(id);
|
|
165
|
+
res.json(user);
|
|
166
166
|
})
|
|
167
167
|
);
|
|
168
168
|
|
|
@@ -191,134 +191,44 @@ export = router;
|
|
|
191
191
|
**Controllers** in this Express TypeScript framework act as the intermediary between the incoming HTTP requests and the application logic. Each controller is responsible for handling specific routes and defining the behavior associated with those routes. This organization promotes a clean architecture by separating business logic, validation, and routing concerns.
|
|
192
192
|
|
|
193
193
|
Controllers can be organized within the router folders, allowing them to stay closely related to their respective routes. However, they are not limited to this structure and can be placed anywhere within the `src` folder as needed, providing flexibility in organizing the codebase.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
For example, in the provided `UserController`, the `createUser` method demonstrates how to apply input validation and error handling through decorators. It employs `@rateLimit` to restrict the number of allowed requests within a specified timeframe, effectively guarding against too many rapid submissions. When an error arises, the `@onError` decorator provides a systematic way to handle exceptions, allowing for logging or other error management processes to be performed centrally.
|
|
194
|
+
Controllers leverage decorators from the `utils-decorators` package to implement throttling, caching, and error handling gracefully.
|
|
195
|
+
For example, in the provided `UserController`, the `createUser` method demonstrates how to apply error handling through decorators. It also employs `@rateLimit` to restrict the number of allowed requests within a specified timeframe, effectively guarding against too many rapid submissions. When an error arises, the `@onError` decorator provides a systematic way to handle exceptions, allowing for logging or other error management processes to be performed centrally.
|
|
198
196
|
|
|
199
197
|
Here’s a brief breakdown of key components used in the `UserController`:
|
|
200
198
|
|
|
201
|
-
- **Validation**: The `CreateUserDto` is validated against incoming data using the `@ZodInput` decorator, ensuring that only well-formed data is passed to the business logic, which is crucial for maintaining application stability.
|
|
202
|
-
|
|
203
199
|
```typescript
|
|
204
|
-
import {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
email: z.string().email(),
|
|
218
|
-
password: z.string().min(6),
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
export const ZodUserCreationDto = ZodUser.omit({ id: true });
|
|
222
|
-
|
|
223
|
-
export type User = z.infer<typeof ZodUser>;
|
|
224
|
-
export type UserCreationDto = z.infer<typeof ZodUserCreationDto>;
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
- **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.
|
|
228
|
-
|
|
229
|
-
- **Error Handling**: The `@onError` decorator captures any exceptions that occur during the execution of the createUser method, allowing for centralized error management, which can greatly simplify debugging and improve maintainability.
|
|
230
|
-
|
|
231
|
-
By using a well-organized controller structure, this project makes it easier to add, modify, and manage endpoints as the application grows. Developers can focus on implementing business logic while the controllers handle the intricacies of request parsing, validation, and response formatting. Additionally, this separation of concerns improves unit testing, as controllers can be tested independently from the rest of the application logic, ensuring robust and reliable API behavior.
|
|
232
|
-
|
|
233
|
-
Here is a quick reference to the UserController in practice:
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
import { User, ZodUserCreationDto, UserCreationDto } from "./dto/user.dto";
|
|
237
|
-
import { onError, rateLimit, timeout } from "utils-decorators";
|
|
238
|
-
import { Validate, ZodInput } from "@pkg/ts-zod-decorators";
|
|
239
|
-
import { ResponseError } from "@src/types";
|
|
240
|
-
import { tryParseId } from "@src/utilities";
|
|
241
|
-
|
|
242
|
-
// Array to store users (as a mock database)
|
|
243
|
-
const users = [
|
|
244
|
-
{
|
|
245
|
-
id: 1,
|
|
246
|
-
username: "johndoe",
|
|
247
|
-
email: "johndoe@gmail.com",
|
|
248
|
-
password: "S3cur3P@ssw0rd",
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
id: 2,
|
|
252
|
-
username: "janesmith",
|
|
253
|
-
email: "janesmith@yahoo.com",
|
|
254
|
-
password: "P@ssw0rd2024",
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
id: 3,
|
|
258
|
-
username: "michael89",
|
|
259
|
-
email: "michael89@hotmail.com",
|
|
260
|
-
password: "M1chael!2024",
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
id: 4,
|
|
264
|
-
username: "lisa.wong",
|
|
265
|
-
email: "lisa.wong@example.com",
|
|
266
|
-
password: "L1saW0ng!2024",
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
id: 5,
|
|
270
|
-
username: "alex_k",
|
|
271
|
-
email: "alex.k@gmail.com",
|
|
272
|
-
password: "A1ex#Key2024",
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
id: 6,
|
|
276
|
-
username: "emilyj",
|
|
277
|
-
email: "emilyj@hotmail.com",
|
|
278
|
-
password: "Em!ly0101",
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
id: 7,
|
|
282
|
-
username: "davidparker",
|
|
283
|
-
email: "david.parker@yahoo.com",
|
|
284
|
-
password: "D@v!d2024",
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
id: 8,
|
|
288
|
-
username: "sophia_m",
|
|
289
|
-
email: "sophia.m@gmail.com",
|
|
290
|
-
password: "Sophi@2024",
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
id: 9,
|
|
294
|
-
username: "chrisw",
|
|
295
|
-
email: "chrisw@outlook.com",
|
|
296
|
-
password: "Chri$Wong21",
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
id: 10,
|
|
300
|
-
username: "natalie_b",
|
|
301
|
-
email: "natalie_b@gmail.com",
|
|
302
|
-
password: "N@talie#B2024",
|
|
303
|
-
},
|
|
304
|
-
];
|
|
200
|
+
import {
|
|
201
|
+
ZodUserCreationDto,
|
|
202
|
+
UserCreationDto,
|
|
203
|
+
UserDto,
|
|
204
|
+
ZodUserDto,
|
|
205
|
+
} from "./dto/user.dto";
|
|
206
|
+
import { memoizeAsync, onError, rateLimit, timeout } from "utils-decorators";
|
|
207
|
+
import { ResponseError } from "@/utilities/error-handling";
|
|
208
|
+
import { convert, stringToInteger } from "@/utilities/conversion";
|
|
209
|
+
import config from "@/config";
|
|
210
|
+
import { users } from "@/db";
|
|
211
|
+
import { User } from "@/entities/user.entity";
|
|
212
|
+
import { MapAsyncCache } from "@/utilities/cache/memory-cache";
|
|
305
213
|
|
|
306
214
|
function exceedHandler() {
|
|
307
215
|
const message = "Too much call in allowed window";
|
|
308
|
-
|
|
309
|
-
throw new Error(message, {
|
|
310
|
-
cause: { status: 500, message } satisfies ResponseError,
|
|
311
|
-
});
|
|
216
|
+
throw new ResponseError(message, 429);
|
|
312
217
|
}
|
|
313
218
|
|
|
314
|
-
function
|
|
219
|
+
function userNotFoundHandler(e: ResponseError) {
|
|
315
220
|
const message = "User not found.";
|
|
221
|
+
throw new ResponseError(message, 404, e?.message);
|
|
222
|
+
}
|
|
316
223
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
224
|
+
function invalidInputHandler(e: ResponseError) {
|
|
225
|
+
const message = "Invalid input";
|
|
226
|
+
throw new ResponseError(message, 400, e?.message);
|
|
320
227
|
}
|
|
321
228
|
|
|
229
|
+
const usersCache = new MapAsyncCache<UserDto[]>(config.cacheSize);
|
|
230
|
+
const userCache = new MapAsyncCache<UserDto>(config.cacheSize);
|
|
231
|
+
|
|
322
232
|
/**
|
|
323
233
|
* Controller for handling user-related operations
|
|
324
234
|
* @class UserController
|
|
@@ -327,71 +237,94 @@ function getUserErrorHandler(e: Error) {
|
|
|
327
237
|
export default class UserController {
|
|
328
238
|
// constructor(private readonly userService: UserService) { }
|
|
329
239
|
|
|
240
|
+
@onError({
|
|
241
|
+
func: invalidInputHandler,
|
|
242
|
+
})
|
|
243
|
+
/**
|
|
244
|
+
* Validates a string ID and converts it to a number.
|
|
245
|
+
*
|
|
246
|
+
* @param {string} id - The ID to validate and convert.
|
|
247
|
+
* @returns {number} The numeric value of the provided ID.
|
|
248
|
+
*/
|
|
249
|
+
public async validateId(id: string): Promise<number> {
|
|
250
|
+
return stringToInteger(id);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@onError({
|
|
254
|
+
func: invalidInputHandler,
|
|
255
|
+
})
|
|
256
|
+
/**
|
|
257
|
+
* Validates and creates a new User from the given DTO.
|
|
258
|
+
*
|
|
259
|
+
* @param {UserCreationDto} user - The incoming UserCreationDto to validate and transform.
|
|
260
|
+
* @returns {User} A fully formed User object ready for persistence.
|
|
261
|
+
*/
|
|
262
|
+
public async validateUserCreationDto(user: UserCreationDto): Promise<User> {
|
|
263
|
+
const newUser = await ZodUserCreationDto.parseAsync(user);
|
|
264
|
+
return { ...newUser, id: users.length + 1 };
|
|
265
|
+
}
|
|
266
|
+
|
|
330
267
|
@rateLimit({
|
|
331
|
-
timeSpanMs:
|
|
332
|
-
allowedCalls:
|
|
268
|
+
timeSpanMs: config.rateLimitTimeSpan,
|
|
269
|
+
allowedCalls: config.rateLimitAllowedCalls,
|
|
333
270
|
exceedHandler,
|
|
334
271
|
})
|
|
335
|
-
@Validate
|
|
336
272
|
/**
|
|
337
273
|
* Create a new user
|
|
338
|
-
* @param {
|
|
274
|
+
* @param {User} user - User creation data validated by Zod schema
|
|
339
275
|
* @returns {Promise<void>}
|
|
340
276
|
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
341
277
|
* @throws {ResponseError} 400 - Invalid input data
|
|
342
278
|
*/
|
|
343
|
-
public async create(
|
|
344
|
-
users.push(
|
|
279
|
+
public async create(user: User): Promise<void> {
|
|
280
|
+
users.push(user);
|
|
281
|
+
await userCache.set(user.id.toString(), user as User);
|
|
282
|
+
await usersCache.delete("key");
|
|
345
283
|
}
|
|
346
284
|
|
|
347
|
-
@
|
|
348
|
-
|
|
285
|
+
@memoizeAsync({
|
|
286
|
+
cache: usersCache,
|
|
287
|
+
keyResolver: () => "key",
|
|
288
|
+
expirationTimeMs: config.memoizeTime,
|
|
349
289
|
})
|
|
290
|
+
@timeout(config.timeout)
|
|
350
291
|
@rateLimit({
|
|
351
|
-
timeSpanMs:
|
|
352
|
-
allowedCalls:
|
|
292
|
+
timeSpanMs: config.rateLimitTimeSpan,
|
|
293
|
+
allowedCalls: config.rateLimitAllowedCalls,
|
|
353
294
|
exceedHandler,
|
|
354
295
|
})
|
|
355
296
|
/**
|
|
356
|
-
* Get
|
|
357
|
-
* @
|
|
358
|
-
* @
|
|
359
|
-
* @throws {ResponseError} 404 - User not found
|
|
360
|
-
* @throws {ResponseError} 400 - Invalid ID format
|
|
297
|
+
* Get all users
|
|
298
|
+
* @returns {Promise<UserDto[]>} List of users with hidden password fields
|
|
299
|
+
* @throws {ResponseError} 500 - When rate limit exceeded
|
|
361
300
|
*/
|
|
362
|
-
public async
|
|
363
|
-
|
|
364
|
-
if (typeof userId != "number") return userId satisfies ResponseError;
|
|
365
|
-
const user = users.find((user) => user.id === userId);
|
|
366
|
-
|
|
367
|
-
if (!user)
|
|
368
|
-
return {
|
|
369
|
-
status: 404,
|
|
370
|
-
message: "User dose not exist.",
|
|
371
|
-
} satisfies ResponseError;
|
|
372
|
-
|
|
373
|
-
return user satisfies User;
|
|
301
|
+
public async getAll(): Promise<UserDto[]> {
|
|
302
|
+
return users as UserDto[];
|
|
374
303
|
}
|
|
375
304
|
|
|
376
|
-
@
|
|
305
|
+
@memoizeAsync({
|
|
306
|
+
cache: userCache,
|
|
307
|
+
keyResolver: (id: number) => id.toString(),
|
|
308
|
+
expirationTimeMs: config.memoizeTime,
|
|
309
|
+
})
|
|
377
310
|
@rateLimit({
|
|
378
|
-
timeSpanMs:
|
|
379
|
-
allowedCalls:
|
|
311
|
+
timeSpanMs: config.rateLimitTimeSpan,
|
|
312
|
+
allowedCalls: config.rateLimitAllowedCalls,
|
|
380
313
|
exceedHandler,
|
|
381
314
|
})
|
|
382
315
|
/**
|
|
383
|
-
* Get
|
|
384
|
-
* @
|
|
385
|
-
* @
|
|
316
|
+
* Get user by ID
|
|
317
|
+
* @param {number} id - User ID as string
|
|
318
|
+
* @returns {Promise<UserDto>} User details or error object
|
|
319
|
+
* @throws {ResponseError} 404 - User not found
|
|
320
|
+
* @throws {ResponseError} 400 - Invalid ID format
|
|
386
321
|
*/
|
|
387
|
-
public async
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
} satisfies User)
|
|
394
|
-
);
|
|
322
|
+
public async get(id: number): Promise<UserDto> {
|
|
323
|
+
const user = users.find((user) => user.id === id);
|
|
324
|
+
if (user == null) {
|
|
325
|
+
throw new ResponseError("Product not found");
|
|
326
|
+
}
|
|
327
|
+
return convert(user!, ZodUserDto);
|
|
395
328
|
}
|
|
396
329
|
}
|
|
397
330
|
```
|
|
@@ -408,7 +341,7 @@ Once the application is running, visit the Swagger UI at http://localhost:3000/a
|
|
|
408
341
|
|
|
409
342
|
### Decorators
|
|
410
343
|
|
|
411
|
-
To prevent abuse of your API, you can utilize throttling, and
|
|
344
|
+
To prevent abuse of your API, you can utilize throttling, caching, and error handling decorators from the `utils-decorators` packages respectively. This packages provides decorators that can be applied directly to your service and controller classes.
|
|
412
345
|
|
|
413
346
|
### Contributing
|
|
414
347
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeweaver",
|
|
3
|
-
"version": "
|
|
4
|
-
"main": "src/
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"main": "src/main.ts",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-codeweaver-app": "./command.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"start": "nodemon -r tsconfig-paths/register src/
|
|
10
|
-
"dev": "nodemon -r tsconfig-paths/register src/
|
|
9
|
+
"start": "nodemon -r tsconfig-paths/register src/main.ts",
|
|
10
|
+
"dev": "nodemon -r tsconfig-paths/register src/main.ts",
|
|
11
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/
|
|
12
|
+
"serve": "cross-env TSCONFIG_PATH=tsconfig.paths.json node -r tsconfig-paths/register dist/main.js"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"codeweaver",
|
|
@@ -26,24 +26,29 @@
|
|
|
26
26
|
"nested routers",
|
|
27
27
|
"decorator",
|
|
28
28
|
"swagger",
|
|
29
|
-
"zod"
|
|
29
|
+
"zod",
|
|
30
|
+
"utils-decorators"
|
|
30
31
|
],
|
|
31
32
|
"author": "js-code-crafter",
|
|
32
33
|
"license": "MIT",
|
|
33
34
|
"description": "A lightweight framework built on top of Express and TypeScript",
|
|
34
35
|
"dependencies": {
|
|
36
|
+
"cors": "^2.8.5",
|
|
37
|
+
"dotenv": "^17.2.3",
|
|
35
38
|
"express": "^5.1.0",
|
|
36
39
|
"express-async-handler": "^1.2.0",
|
|
40
|
+
"ioredis": "^5.8.2",
|
|
41
|
+
"swagger-jsdoc": "^3.7.0",
|
|
37
42
|
"utils-decorators": "^2.10.0",
|
|
38
43
|
"zod": "^4.0.14"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|
|
46
|
+
"@types/cors": "^2.8.19",
|
|
41
47
|
"@types/express": "^5.0.3",
|
|
42
48
|
"@types/node": "^24.1.0",
|
|
43
49
|
"copyfiles": "^2.4.1",
|
|
44
50
|
"cross-env": "^10.0.0",
|
|
45
51
|
"nodemon": "^3.1.10",
|
|
46
|
-
"swagger-jsdoc": "^6.2.8",
|
|
47
52
|
"swagger-ui-express": "^5.0.1",
|
|
48
53
|
"ts-node": "^10.9.2",
|
|
49
54
|
"tsc-alias": "^1.8.16",
|
package/src/app.ts
CHANGED
|
@@ -1,113 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
ErrorRequestHandler,
|
|
3
|
-
NextFunction,
|
|
4
|
-
Request,
|
|
5
|
-
Response,
|
|
6
|
-
} from "express";
|
|
7
|
-
import fs from "fs";
|
|
8
|
-
import path from "path";
|
|
9
|
-
import config from "./config";
|
|
1
|
+
// TODO:
|
|
10
2
|
|
|
11
|
-
|
|
12
|
-
* Recursively loads Express routers from directory
|
|
13
|
-
* @param {string} routerPath - Directory path to scan
|
|
14
|
-
* @param {string} [basePath=""] - Base route path
|
|
15
|
-
*/
|
|
16
|
-
function loadRouters(routerPath: string, basePath: string = "") {
|
|
17
|
-
fs.readdirSync(routerPath).forEach((file) => {
|
|
18
|
-
// Check if the filename should be excluded based on certain criteria
|
|
19
|
-
if (
|
|
20
|
-
// Exclude files that start with an underscore (_)
|
|
21
|
-
file.startsWith("_") ||
|
|
22
|
-
// Exclude files that start with an at symbol (@)
|
|
23
|
-
file.startsWith("@") ||
|
|
24
|
-
// Exclude JavaScript controller files
|
|
25
|
-
file.endsWith(".controller.js") ||
|
|
26
|
-
// Exclude TypeScript controller files
|
|
27
|
-
file.endsWith(".controller.ts") ||
|
|
28
|
-
// Exclude JavaScript service files
|
|
29
|
-
file.endsWith(".service.js") ||
|
|
30
|
-
// Exclude TypeScript service files
|
|
31
|
-
file.endsWith(".service.ts") ||
|
|
32
|
-
// Exclude JavaScript middleware files
|
|
33
|
-
file.endsWith(".middleware.js") ||
|
|
34
|
-
// Exclude TypeScript middleware files
|
|
35
|
-
file.endsWith(".middleware.ts") ||
|
|
36
|
-
// Exclude JavaScript error middleware files
|
|
37
|
-
file.endsWith(".error.js") ||
|
|
38
|
-
// Exclude TypeScript error middleware files
|
|
39
|
-
file.endsWith(".error.ts") ||
|
|
40
|
-
// Exclude JavaScript service files
|
|
41
|
-
file.endsWith(".decorator.js") ||
|
|
42
|
-
// Exclude TypeScript service files
|
|
43
|
-
file.endsWith(".decorator.ts") ||
|
|
44
|
-
// Exclude JavaScript DTO files
|
|
45
|
-
file.endsWith(".dto.js") ||
|
|
46
|
-
// Exclude TypeScript DTO files
|
|
47
|
-
file.endsWith(".dto.ts") ||
|
|
48
|
-
// Exclude JavaScript test specification files
|
|
49
|
-
file.endsWith(".spec.js") ||
|
|
50
|
-
// Exclude TypeScript test specification files
|
|
51
|
-
file.endsWith(".spec.ts") ||
|
|
52
|
-
// Exclude JavaScript test specification files
|
|
53
|
-
file.endsWith(".class.js") ||
|
|
54
|
-
// Exclude TypeScript test specification files
|
|
55
|
-
file.endsWith(".class.ts")
|
|
56
|
-
)
|
|
57
|
-
// If any of the above conditions are true, exit the function early
|
|
58
|
-
return;
|
|
59
|
-
|
|
60
|
-
const fullPath = path.join(routerPath, file);
|
|
61
|
-
|
|
62
|
-
// Construct a route path by combining the base path with the filename
|
|
63
|
-
const routePath = path
|
|
64
|
-
// Join the base path and the filename, removing the file extension (.js or .ts)
|
|
65
|
-
.join(basePath, file.replace(/(\.js|\.ts)/g, ""))
|
|
66
|
-
// Replace all backslashes with forward slashes for consistent file path formatting
|
|
67
|
-
.replaceAll("\\", "/")
|
|
68
|
-
// Remove the trailing '/index' if it exists, to clean up the route
|
|
69
|
-
.replace(/\/?index$/g, "");
|
|
70
|
-
|
|
71
|
-
if (fs.lstatSync(fullPath).isDirectory()) {
|
|
72
|
-
// If the file is a directory, call the function again
|
|
73
|
-
loadRouters(fullPath, routePath);
|
|
74
|
-
} else if (file.endsWith(".ts") || file.endsWith(".js")) {
|
|
75
|
-
// Import the router module and mount it to the app
|
|
76
|
-
const router = require(fullPath);
|
|
77
|
-
app.use("/" + routePath, router);
|
|
78
|
-
console.log(`Mounted ${file} at ${"/" + routePath}`);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const app = express();
|
|
84
|
-
app.use(express.json());
|
|
85
|
-
app.use(express.urlencoded({ extended: true }));
|
|
86
|
-
|
|
87
|
-
//app.use(cors());
|
|
88
|
-
|
|
89
|
-
// Automatically import all routers from the /src/routers directory
|
|
90
|
-
const routersPath = path.join(__dirname, "/routers");
|
|
91
|
-
loadRouters(routersPath);
|
|
92
|
-
|
|
93
|
-
// Swagger setup
|
|
94
|
-
if (config.devMode) {
|
|
95
|
-
const swaggerJsDoc = require("swagger-jsdoc");
|
|
96
|
-
const swaggerUi = require("swagger-ui-express");
|
|
97
|
-
const swaggerDocs = swaggerJsDoc(config.swaggerOptions);
|
|
98
|
-
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// General error handler
|
|
102
|
-
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
|
|
103
|
-
res.status(500).json(err);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Start the server
|
|
107
|
-
app.listen(config.port, () => {
|
|
108
|
-
console.log(`Server is running on http://localhost:${config.port}`);
|
|
109
|
-
if (config.devMode)
|
|
110
|
-
console.log(
|
|
111
|
-
`Swagger UI is available at http://localhost:${config.port}/api-docs`
|
|
112
|
-
);
|
|
113
|
-
});
|
|
3
|
+
export = null;
|