@codeenthusiast09/create-express-app 1.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.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/bin/cli.cjs +12 -0
  4. package/dist/generator.d.ts +59 -0
  5. package/dist/generator.d.ts.map +1 -0
  6. package/dist/generator.js +178 -0
  7. package/dist/generator.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +113 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/installer.d.ts +37 -0
  13. package/dist/installer.d.ts.map +1 -0
  14. package/dist/installer.js +146 -0
  15. package/dist/installer.js.map +1 -0
  16. package/dist/prompts.d.ts +19 -0
  17. package/dist/prompts.d.ts.map +1 -0
  18. package/dist/prompts.js +67 -0
  19. package/dist/prompts.js.map +1 -0
  20. package/package.json +59 -0
  21. package/templates/boilerplate/.env.example +24 -0
  22. package/templates/boilerplate/.eslintrc.cjs +27 -0
  23. package/templates/boilerplate/.prettierrc +9 -0
  24. package/templates/boilerplate/DEPENDENCIES.md +43 -0
  25. package/templates/boilerplate/README.md +282 -0
  26. package/templates/boilerplate/docker/.dockerignore +46 -0
  27. package/templates/boilerplate/docker/Dockerfile +61 -0
  28. package/templates/boilerplate/docker/docker-compose.yml +68 -0
  29. package/templates/boilerplate/drizzle/drizzle.config.ts +13 -0
  30. package/templates/boilerplate/drizzle/schema.ts +22 -0
  31. package/templates/boilerplate/jest.config.cjs +24 -0
  32. package/templates/boilerplate/nodemon.json +11 -0
  33. package/templates/boilerplate/package.json +61 -0
  34. package/templates/boilerplate/prisma/schema.prisma +0 -0
  35. package/templates/boilerplate/scripts/generate-module.cjs +397 -0
  36. package/templates/boilerplate/src/common/middleware/error.middleware.ts +121 -0
  37. package/templates/boilerplate/src/common/middleware/validation.middleware.ts +50 -0
  38. package/templates/boilerplate/src/common/utils/http-logger.ts +24 -0
  39. package/templates/boilerplate/src/common/utils/logger.ts +34 -0
  40. package/templates/boilerplate/src/common/utils/response-helper.ts +140 -0
  41. package/templates/boilerplate/src/config/env.ts +24 -0
  42. package/templates/boilerplate/src/config/index.ts +92 -0
  43. package/templates/boilerplate/src/database/drizzle.connection.ts +50 -0
  44. package/templates/boilerplate/src/database/index.ts +20 -0
  45. package/templates/boilerplate/src/database/mongoose.connection.ts +56 -0
  46. package/templates/boilerplate/src/database/prisma.connection.ts +50 -0
  47. package/templates/boilerplate/src/modules/.gitkeep +0 -0
  48. package/templates/boilerplate/src/server.ts +121 -0
  49. package/templates/boilerplate/src/types/express.types.ts +29 -0
  50. package/templates/boilerplate/src/types/index.ts +5 -0
  51. package/templates/boilerplate/src/types/response.types.ts +54 -0
  52. package/templates/boilerplate/tsconfig.json +72 -0
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Module Generator
8
+ *
9
+ * Creates a new module with all necessary files:
10
+ * - controller.ts
11
+ * - service.ts
12
+ * - routes.ts
13
+ * - validation.ts
14
+ * - repository.interface.ts
15
+ * - repository.ts
16
+ *
17
+ * Usage: npm run generate:module <module-name>
18
+ * Example: npm run generate:module hazard-matrix
19
+ */
20
+
21
+ // Get module name from command line
22
+ const moduleName = process.argv[2];
23
+
24
+ if (!moduleName) {
25
+ console.error('❌ Error: Please provide a module name');
26
+ console.log('Usage: npm run generate:module <module-name>');
27
+ console.log('Example: npm run generate:module hazard-matrix');
28
+ process.exit(1);
29
+ }
30
+
31
+ // Validate module name (allow lowercase letters, hyphens, and underscores)
32
+ if (!/^[a-z][a-z0-9-_]*$/.test(moduleName)) {
33
+ console.error(
34
+ '❌ Error: Module name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores',
35
+ );
36
+ console.log('Valid examples: users, user-profiles, hazard-matrix');
37
+ process.exit(1);
38
+ }
39
+
40
+ /**
41
+ * Convert kebab-case or snake_case to PascalCase
42
+ * Examples:
43
+ * hazard-matrix -> HazardMatrix
44
+ * user_profiles -> UserProfiles
45
+ * users -> Users
46
+ */
47
+ function toPascalCase(str) {
48
+ return str
49
+ .split(/[-_]/)
50
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
51
+ .join('');
52
+ }
53
+
54
+ /**
55
+ * Convert kebab-case or snake_case to camelCase
56
+ * Examples:
57
+ * hazard-matrix -> hazardMatrix
58
+ * user_profiles -> userProfiles
59
+ * users -> users
60
+ */
61
+ function toCamelCase(str) {
62
+ const parts = str.split(/[-_]/);
63
+ return (
64
+ parts[0] +
65
+ parts
66
+ .slice(1)
67
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
68
+ .join('')
69
+ );
70
+ }
71
+
72
+ // Generate names
73
+ const ModuleName = toPascalCase(moduleName); // HazardMatrix (for class names, schemas)
74
+ const moduleNameCamel = toCamelCase(moduleName); // hazardMatrix (for variable names)
75
+ const modulePath = path.join(process.cwd(), 'src', 'modules', moduleName); // hazard-matrix (keep original)
76
+
77
+ // Check if module already exists
78
+ if (fs.existsSync(modulePath)) {
79
+ console.error(`❌ Error: Module "${moduleName}" already exists!`);
80
+ process.exit(1);
81
+ }
82
+
83
+ // Create module directory
84
+ console.log(`📁 Creating module: ${moduleName}...`);
85
+ fs.mkdirSync(modulePath, { recursive: true });
86
+
87
+ // File templates
88
+ const templates = {
89
+ // Controller
90
+ 'controller.ts': `import { Request, Response } from 'express';
91
+ import { ${moduleNameCamel}Service } from './${moduleName}.service';
92
+ import { ResponseHelper } from '@/common/utils/response-helper';
93
+ import { asyncHandler } from '@/common/middleware/error.middleware';
94
+
95
+ /**
96
+ * ${ModuleName} Controller
97
+ */
98
+ export class ${ModuleName}Controller {
99
+ /**
100
+ * Create ${moduleName}
101
+ */
102
+ create = asyncHandler(async (req: Request, res: Response) => {
103
+ const data = await ${moduleNameCamel}Service.create(req.body);
104
+ ResponseHelper.created(res, data, '${ModuleName} created successfully');
105
+ });
106
+
107
+ /**
108
+ * Get all ${moduleName}
109
+ */
110
+ getAll = asyncHandler(async (_req: Request, res: Response) => {
111
+ const data = await ${moduleNameCamel}Service.findAll();
112
+ ResponseHelper.success(res, data, '${ModuleName} retrieved successfully');
113
+ });
114
+
115
+ /**
116
+ * Get ${moduleName} by ID
117
+ */
118
+ getById = asyncHandler(async (req: Request, res: Response) => {
119
+ const id = req.params.id;
120
+ if (!id) {
121
+ ResponseHelper.badRequest(res, 'ID is required');
122
+ return;
123
+ }
124
+ const data = await ${moduleNameCamel}Service.findById(id);
125
+ ResponseHelper.success(res, data, '${ModuleName} retrieved successfully');
126
+ });
127
+
128
+ /**
129
+ * Update ${moduleName}
130
+ */
131
+ update = asyncHandler(async (req: Request, res: Response) => {
132
+ const id = req.params.id;
133
+ if (!id) {
134
+ ResponseHelper.badRequest(res, 'ID is required');
135
+ return;
136
+ }
137
+ const data = await ${moduleNameCamel}Service.update(id, req.body);
138
+ ResponseHelper.success(res, data, '${ModuleName} updated successfully');
139
+ });
140
+
141
+ /**
142
+ * Delete ${moduleName}
143
+ */
144
+ delete = asyncHandler(async (req: Request, res: Response) => {
145
+ const id = req.params.id;
146
+ if (!id) {
147
+ ResponseHelper.badRequest(res, 'ID is required');
148
+ return;
149
+ }
150
+ await ${moduleNameCamel}Service.delete(id);
151
+ ResponseHelper.noContent(res);
152
+ });
153
+ }
154
+
155
+ export const ${moduleNameCamel}Controller = new ${ModuleName}Controller();
156
+ `,
157
+
158
+ // Service
159
+ 'service.ts': `import { ${moduleNameCamel}Repository } from './${moduleName}.repository';
160
+ import { AppError } from '@/common/middleware/error.middleware';
161
+
162
+ /**
163
+ * ${ModuleName} Service
164
+ *
165
+ * Business logic for ${moduleName} operations.
166
+ */
167
+ class ${ModuleName}Service {
168
+ /**
169
+ * Find all ${moduleName}
170
+ */
171
+ async findAll() {
172
+ return await ${moduleNameCamel}Repository.findAll();
173
+ }
174
+
175
+ /**
176
+ * Find ${moduleName} by ID
177
+ */
178
+ async findById(id: string) {
179
+ const data = await ${moduleNameCamel}Repository.findById(id);
180
+ if (!data) {
181
+ throw new AppError('${ModuleName} not found', 404, '${ModuleName.toUpperCase().replace(/-/g, '_')}_NOT_FOUND');
182
+ }
183
+ return data;
184
+ }
185
+
186
+ /**
187
+ * Create ${moduleName}
188
+ */
189
+ async create(data: any) {
190
+ return await ${moduleNameCamel}Repository.create(data);
191
+ }
192
+
193
+ /**
194
+ * Update ${moduleName}
195
+ */
196
+ async update(id: string, data: any) {
197
+ const updated = await ${moduleNameCamel}Repository.update(id, data);
198
+ if (!updated) {
199
+ throw new AppError('${ModuleName} not found', 404, '${ModuleName.toUpperCase().replace(/-/g, '_')}_NOT_FOUND');
200
+ }
201
+ return updated;
202
+ }
203
+
204
+ /**
205
+ * Delete ${moduleName}
206
+ */
207
+ async delete(id: string) {
208
+ const deleted = await ${moduleNameCamel}Repository.delete(id);
209
+ if (!deleted) {
210
+ throw new AppError('${ModuleName} not found', 404, '${ModuleName.toUpperCase().replace(/-/g, '_')}_NOT_FOUND');
211
+ }
212
+ }
213
+ }
214
+
215
+ export const ${moduleNameCamel}Service = new ${ModuleName}Service();
216
+ `,
217
+
218
+ // Routes
219
+ 'routes.ts': `import { Router } from 'express';
220
+ import { ${moduleNameCamel}Controller } from './${moduleName}.controller';
221
+ import { validate } from '@/common/middleware/validation.middleware';
222
+ import { create${ModuleName}Schema, update${ModuleName}Schema, idParamSchema } from './${moduleName}.validation';
223
+
224
+ /**
225
+ * ${ModuleName} Routes
226
+ */
227
+ const router = Router();
228
+
229
+ router.get('/', ${moduleNameCamel}Controller.getAll);
230
+ router.get('/:id', validate({ params: idParamSchema }), ${moduleNameCamel}Controller.getById);
231
+ router.post('/', validate({ body: create${ModuleName}Schema }), ${moduleNameCamel}Controller.create);
232
+ router.patch('/:id', validate({ params: idParamSchema, body: update${ModuleName}Schema }), ${moduleNameCamel}Controller.update);
233
+ router.delete('/:id', validate({ params: idParamSchema }), ${moduleNameCamel}Controller.delete);
234
+
235
+ export default router;
236
+ `,
237
+
238
+ // Validation
239
+ 'validation.ts': `import { z } from 'zod';
240
+
241
+ /**
242
+ * ${ModuleName} Validation Schemas
243
+ */
244
+
245
+ export const create${ModuleName}Schema = z.object({
246
+ name: z.string().min(1, 'Name is required'),
247
+ // Add more fields as needed
248
+ });
249
+
250
+ export const update${ModuleName}Schema = z.object({
251
+ name: z.string().min(1).optional(),
252
+ // Add more fields as needed
253
+ });
254
+
255
+ export const idParamSchema = z.object({
256
+ id: z.string().min(1, 'ID is required'),
257
+ });
258
+
259
+ export type Create${ModuleName}Input = z.infer<typeof create${ModuleName}Schema>;
260
+ export type Update${ModuleName}Input = z.infer<typeof update${ModuleName}Schema>;
261
+ `,
262
+
263
+ // Repository Interface
264
+ 'repository.interface.ts': `/**
265
+ * ${ModuleName} Repository Interface
266
+ */
267
+
268
+ export interface I${ModuleName}Repository {
269
+ findAll(): Promise<any[]>;
270
+ findById(id: string): Promise<any | null>;
271
+ create(data: any): Promise<any>;
272
+ update(id: string, data: any): Promise<any | null>;
273
+ delete(id: string): Promise<boolean>;
274
+ }
275
+ `,
276
+
277
+ // Repository Implementation
278
+ 'repository.ts': `import { I${ModuleName}Repository } from './${moduleName}.repository.interface';
279
+
280
+ /**
281
+ * ${ModuleName} Repository
282
+ *
283
+ * TODO: Implement based on your database choice:
284
+ * - For Mongoose: Import and use Mongoose model
285
+ * - For Prisma: Import and use Prisma client
286
+ * - For Drizzle: Import and use Drizzle db instance
287
+ */
288
+ class ${ModuleName}Repository implements I${ModuleName}Repository {
289
+ async findAll() {
290
+ // TODO: Implement database query
291
+ return [];
292
+ }
293
+
294
+ async findById(_id: string) {
295
+ // TODO: Implement database query
296
+ return null;
297
+ }
298
+
299
+ async create(data: any) {
300
+ // TODO: Implement database insertion
301
+ return data;
302
+ }
303
+
304
+ async update(_id: string, _data: any) {
305
+ // TODO: Implement database update
306
+ return null;
307
+ }
308
+
309
+ async delete(_id: string) {
310
+ // TODO: Implement database deletion
311
+ return false;
312
+ }
313
+ }
314
+
315
+ export const ${moduleNameCamel}Repository = new ${ModuleName}Repository();
316
+ `,
317
+ };
318
+
319
+ // Generate files
320
+ Object.entries(templates).forEach(([filename, content]) => {
321
+ const filePath = path.join(modulePath, `${moduleName}.${filename}`);
322
+ fs.writeFileSync(filePath, content);
323
+ console.log(`✓ Created ${moduleName}.${filename}`);
324
+ });
325
+
326
+ // ========== Auto-register routes ==========
327
+ console.log();
328
+ console.log('📝 Registering routes in server.ts...');
329
+
330
+ try {
331
+ const serverPath = path.join(process.cwd(), 'src', 'server.ts');
332
+ let serverContent = fs.readFileSync(serverPath, 'utf-8');
333
+
334
+ // 1. Add import statement at the top (after other imports)
335
+ const importStatement = `import ${moduleNameCamel}Routes from './modules/${moduleName}/${moduleName}.routes';`;
336
+
337
+ // Find the last import statement
338
+ const lastImportIndex = serverContent.lastIndexOf('import ');
339
+ const endOfLastImport = serverContent.indexOf('\n', lastImportIndex);
340
+
341
+ // Insert the new import after the last import
342
+ serverContent =
343
+ serverContent.slice(0, endOfLastImport + 1) +
344
+ importStatement +
345
+ '\n' +
346
+ serverContent.slice(endOfLastImport + 1);
347
+
348
+ // 2. Add route registration before error handlers
349
+ const routeRegistration = `\n/**\n * ${ModuleName} Routes\n */\napp.use('/api/${moduleName}', ${moduleNameCamel}Routes);\n`;
350
+
351
+ // Find the "Error Handling" comment (this should be before error handlers)
352
+ const errorHandlingComment = '/**\n * Error Handling (must be last)\n */';
353
+ const errorHandlingIndex = serverContent.indexOf(errorHandlingComment);
354
+
355
+ if (errorHandlingIndex === -1) {
356
+ // Fallback: insert before the first app.use with error handler
357
+ const notFoundHandlerIndex = serverContent.indexOf('app.use(notFoundHandler)');
358
+ if (notFoundHandlerIndex !== -1) {
359
+ serverContent =
360
+ serverContent.slice(0, notFoundHandlerIndex) +
361
+ routeRegistration +
362
+ serverContent.slice(notFoundHandlerIndex);
363
+ } else {
364
+ console.warn(
365
+ '⚠️ Could not find error handler location. You may need to manually register the routes.',
366
+ );
367
+ }
368
+ } else {
369
+ // Insert before the error handling comment
370
+ serverContent =
371
+ serverContent.slice(0, errorHandlingIndex) +
372
+ routeRegistration +
373
+ serverContent.slice(errorHandlingIndex);
374
+ }
375
+
376
+ // Write the updated content back
377
+ fs.writeFileSync(serverPath, serverContent);
378
+ console.log(`✓ Routes registered in server.ts`);
379
+ console.log(` - Import: ${importStatement}`);
380
+ console.log(` - Route: app.use('/api/${moduleName}', ${moduleNameCamel}Routes)`);
381
+ } catch (error) {
382
+ console.error('⚠️ Error auto-registering routes:', error.message);
383
+ console.log(' Please manually add the following to src/server.ts:');
384
+ console.log(
385
+ ` import ${moduleNameCamel}Routes from './modules/${moduleName}/${moduleName}.routes';`,
386
+ );
387
+ console.log(` app.use('/api/${moduleName}', ${moduleNameCamel}Routes);`);
388
+ }
389
+
390
+ console.log();
391
+ console.log(`✅ Module "${moduleName}" created successfully!`);
392
+ console.log();
393
+ console.log('📝 Next steps:');
394
+ console.log(`1. Implement database operations in ${moduleName}.repository.ts`);
395
+ console.log(`2. Update validation schemas in ${moduleName}.validation.ts`);
396
+ console.log(`3. Start coding! Routes are already registered at /api/${moduleName}`);
397
+ console.log();
@@ -0,0 +1,121 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { ZodError } from 'zod';
3
+ import { logger } from '@/common/utils/logger';
4
+ import { config } from '@/config';
5
+ import { ResponseHelper } from '@/common/utils/response-helper';
6
+ import { ErrorDetail } from '@/types/response.types';
7
+
8
+ /**
9
+ * Custom Application Error
10
+ *
11
+ * Throw this for expected errors:
12
+ * throw new AppError('User not found', 404);
13
+ */
14
+ export class AppError extends Error {
15
+ constructor(
16
+ public message: string,
17
+ public statusCode: number = 500,
18
+ public code: string = 'APP_ERROR',
19
+ public isOperational: boolean = true,
20
+ ) {
21
+ super(message);
22
+ this.name = this.constructor.name;
23
+ Error.captureStackTrace(this, this.constructor);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * MongoDB Duplicate Key Error Interface
29
+ */
30
+ interface MongoError extends Error {
31
+ code?: number;
32
+ keyPattern?: Record<string, number>;
33
+ keyValue?: Record<string, unknown>;
34
+ }
35
+
36
+ /**
37
+ * Global Error Handler
38
+ *
39
+ * Catches all errors and sends appropriate responses.
40
+ * MUST be registered LAST in middleware chain.
41
+ */
42
+ export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction) => {
43
+ // Log the error
44
+ logger.error({ err, req: { method: req.method, url: req.url } });
45
+
46
+ // Handle custom AppError
47
+ if (err instanceof AppError) {
48
+ // Client errors (4xx)
49
+ if (err.statusCode < 500) {
50
+ return ResponseHelper.fail(res, err.code, err.message, err.statusCode);
51
+ }
52
+ // Server errors (5xx)
53
+ return ResponseHelper.error(res, err.code, err.message, err.statusCode, err);
54
+ }
55
+
56
+ // Handle Zod validation errors
57
+ if (err instanceof ZodError) {
58
+ const details: ErrorDetail[] = err.errors.map((error) => ({
59
+ field: error.path.join('.'),
60
+ message: error.message,
61
+ code: error.code,
62
+ }));
63
+ return ResponseHelper.validationError(res, details);
64
+ }
65
+
66
+ // Handle JWT errors
67
+ if (err.name === 'JsonWebTokenError') {
68
+ return ResponseHelper.unauthorized(res, 'Invalid token');
69
+ }
70
+ if (err.name === 'TokenExpiredError') {
71
+ return ResponseHelper.unauthorized(res, 'Token expired');
72
+ }
73
+
74
+ // Handle Mongoose errors
75
+ if (err.name === 'ValidationError') {
76
+ return ResponseHelper.badRequest(res, 'Validation failed');
77
+ }
78
+ if (err.name === 'CastError') {
79
+ return ResponseHelper.badRequest(res, 'Invalid ID format');
80
+ }
81
+ const mongoError = err as MongoError;
82
+ if (mongoError.code === 11000) {
83
+ const field = mongoError.keyPattern ? Object.keys(mongoError.keyPattern)[0] : 'field';
84
+ return ResponseHelper.conflict(res, `Duplicate value for ${field ?? 'unknown field'}`);
85
+ }
86
+
87
+ // Default: Unknown error
88
+ return ResponseHelper.internalError(
89
+ res,
90
+ config.nodeEnv === 'development' ? err.message : 'An unexpected error occurred',
91
+ err,
92
+ );
93
+ };
94
+
95
+ /**
96
+ * Async Handler Wrapper
97
+ *
98
+ * Wraps async route handlers to catch errors automatically.
99
+ *
100
+ * Usage:
101
+ * router.get('/users', asyncHandler(async (req, res) => {
102
+ * const users = await userService.findAll();
103
+ * ResponseHelper.success(res, users);
104
+ * }));
105
+ */
106
+ export const asyncHandler = (
107
+ fn: (req: Request, res: Response, next: NextFunction) => Promise<void>,
108
+ ) => {
109
+ return (req: Request, res: Response, next: NextFunction) => {
110
+ Promise.resolve(fn(req, res, next)).catch(next);
111
+ };
112
+ };
113
+
114
+ /**
115
+ * 404 Not Found Handler
116
+ *
117
+ * Register BEFORE error handler.
118
+ */
119
+ export const notFoundHandler = (req: Request, res: Response) => {
120
+ return ResponseHelper.notFound(res, `Route ${req.method} ${req.url} not found`);
121
+ };
@@ -0,0 +1,50 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { AnyZodObject, ZodError } from 'zod';
3
+ import { ResponseHelper } from '@/common/utils/response-helper';
4
+ import { ErrorDetail } from '@/types/response.types';
5
+
6
+ /**
7
+ * Validation Middleware
8
+ *
9
+ * Validates request body, query, or params against Zod schema.
10
+ *
11
+ * Usage:
12
+ * router.post('/users', validate({ body: createUserSchema }), createUser);
13
+ */
14
+ interface ValidationSchemas {
15
+ body?: AnyZodObject;
16
+ query?: AnyZodObject;
17
+ params?: AnyZodObject;
18
+ }
19
+
20
+ export const validate = (schemas: ValidationSchemas) => {
21
+ return async (req: Request, res: Response, next: NextFunction) => {
22
+ try {
23
+ if (schemas.body) {
24
+ req.body = await schemas.body.parseAsync(req.body);
25
+ }
26
+
27
+ if (schemas.query) {
28
+ req.query = await schemas.query.parseAsync(req.query);
29
+ }
30
+
31
+ if (schemas.params) {
32
+ req.params = await schemas.params.parseAsync(req.params);
33
+ }
34
+
35
+ return next();
36
+ } catch (error) {
37
+ if (error instanceof ZodError) {
38
+ const details: ErrorDetail[] = error.errors.map((err) => ({
39
+ field: err.path.join('.'),
40
+ message: err.message,
41
+ code: err.code,
42
+ }));
43
+
44
+ return ResponseHelper.validationError(res, details);
45
+ }
46
+
47
+ return next(error);
48
+ }
49
+ };
50
+ };
@@ -0,0 +1,24 @@
1
+ import pinoHttp from 'pino-http';
2
+ import { Request, Response } from 'express';
3
+ import { logger } from './logger';
4
+
5
+ export const httpLogger = pinoHttp({
6
+ logger,
7
+
8
+ customLogLevel: (_req, res, err) => {
9
+ if (res.statusCode >= 500 || err) return 'error';
10
+ if (res.statusCode >= 400) return 'warn';
11
+ return 'info';
12
+ },
13
+
14
+ serializers: {
15
+ req: (req: Request) => ({
16
+ method: req.method,
17
+ url: req.url,
18
+ userId: req.user?.id,
19
+ }),
20
+ res: (res: Response) => ({
21
+ statusCode: res.statusCode,
22
+ }),
23
+ },
24
+ });
@@ -0,0 +1,34 @@
1
+ import pino from 'pino';
2
+ import { config } from '@/config';
3
+
4
+ /**
5
+ * Application Logger
6
+ *
7
+ * Usage:
8
+ * logger.info('User created', { userId: 123 });
9
+ * logger.error('Failed to connect', { error });
10
+ * logger.debug('Cache hit', { key: 'user:123' });
11
+ */
12
+ export const logger = pino({
13
+ level: config.logging.level,
14
+
15
+ // Pretty print in development, JSON in production
16
+ transport:
17
+ config.nodeEnv === 'development'
18
+ ? {
19
+ target: 'pino-pretty',
20
+ options: {
21
+ colorize: true,
22
+ translateTime: 'HH:MM:ss Z',
23
+ ignore: 'pid,hostname',
24
+ },
25
+ }
26
+ : undefined,
27
+
28
+ // Add environment to every log
29
+ // base: {
30
+ // env: config.nodeEnv,
31
+ // },
32
+
33
+ timestamp: pino.stdTimeFunctions.isoTime,
34
+ });