omgkit 2.2.0 → 2.3.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 (55) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  3. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  4. package/plugin/skills/databases/redis/SKILL.md +53 -860
  5. package/plugin/skills/devops/aws/SKILL.md +68 -672
  6. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  7. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  8. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  9. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  10. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  11. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  12. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  13. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  14. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  15. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  16. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  17. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  18. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  19. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  20. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  21. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  22. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  23. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  24. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  25. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  26. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  27. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  28. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  29. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  30. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  31. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  32. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  33. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  34. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  35. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  36. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  37. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  38. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  39. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  40. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  41. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  42. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  43. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  44. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  45. package/plugin/skills/security/oauth/SKILL.md +80 -934
  46. package/plugin/skills/security/owasp/SKILL.md +78 -862
  47. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  48. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  49. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  50. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  51. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  52. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  53. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  54. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  55. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,1381 +1,175 @@
1
1
  ---
2
- name: express
3
- description: Enterprise Express.js development with TypeScript, middleware patterns, and production-ready APIs
4
- category: frameworks
5
- triggers:
6
- - express
7
- - expressjs
8
- - express.js
9
- - node api
10
- - node rest api
11
- - express middleware
12
- - express routing
13
- - node backend
2
+ name: building-express-apis
3
+ description: Builds production Express.js APIs with TypeScript, middleware patterns, authentication, and error handling. Use when creating Node.js backends, REST APIs, or Express applications.
14
4
  ---
15
5
 
16
6
  # Express.js
17
7
 
18
- Enterprise-grade **Express.js development** following industry best practices. This skill covers TypeScript integration, middleware patterns, authentication, error handling, validation, testing, and production deployment configurations used by top engineering teams.
19
-
20
- ## Purpose
21
-
22
- Build scalable Node.js APIs with confidence:
23
-
24
- - Design clean API architectures with proper routing
25
- - Implement robust middleware patterns
26
- - Handle authentication and authorization securely
27
- - Validate requests with comprehensive schemas
28
- - Write comprehensive tests for reliability
29
- - Deploy production-ready applications
30
- - Optimize performance for high traffic
31
-
32
- ## Features
33
-
34
- ### 1. TypeScript Project Setup
8
+ ## Quick Start
35
9
 
36
10
  ```typescript
37
- // src/app.ts
38
- import express, { Application, Request, Response, NextFunction } from 'express';
11
+ import express from 'express';
39
12
  import cors from 'cors';
40
13
  import helmet from 'helmet';
41
- import compression from 'compression';
42
- import rateLimit from 'express-rate-limit';
43
- import { pinoHttp } from 'pino-http';
44
- import { errorHandler } from './middleware/errorHandler';
45
- import { notFoundHandler } from './middleware/notFoundHandler';
46
- import { apiRouter } from './routes';
47
- import { config } from './config';
48
- import { logger } from './utils/logger';
49
-
50
- export function createApp(): Application {
51
- const app = express();
52
-
53
- // Security middleware
54
- app.use(helmet());
55
- app.use(cors({
56
- origin: config.cors.origins,
57
- credentials: true,
58
- methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
59
- allowedHeaders: ['Content-Type', 'Authorization'],
60
- }));
61
-
62
- // Rate limiting
63
- app.use(rateLimit({
64
- windowMs: 15 * 60 * 1000, // 15 minutes
65
- max: 100,
66
- standardHeaders: true,
67
- legacyHeaders: false,
68
- message: { error: 'Too many requests, please try again later.' },
69
- }));
70
-
71
- // Request parsing
72
- app.use(express.json({ limit: '10mb' }));
73
- app.use(express.urlencoded({ extended: true, limit: '10mb' }));
74
- app.use(compression());
75
-
76
- // Logging
77
- app.use(pinoHttp({
78
- logger,
79
- customLogLevel: (req, res, err) => {
80
- if (res.statusCode >= 500 || err) return 'error';
81
- if (res.statusCode >= 400) return 'warn';
82
- return 'info';
83
- },
84
- }));
85
14
 
86
- // Health check
87
- app.get('/health', (req, res) => {
88
- res.json({ status: 'healthy', timestamp: new Date().toISOString() });
89
- });
15
+ const app = express();
90
16
 
91
- // API routes
92
- app.use('/api/v1', apiRouter);
17
+ app.use(helmet());
18
+ app.use(cors());
19
+ app.use(express.json());
93
20
 
94
- // Error handling
95
- app.use(notFoundHandler);
96
- app.use(errorHandler);
97
-
98
- return app;
99
- }
100
-
101
- // src/server.ts
102
- import { createApp } from './app';
103
- import { config } from './config';
104
- import { logger } from './utils/logger';
105
- import { connectDatabase } from './database';
21
+ app.get('/api/health', (req, res) => {
22
+ res.json({ status: 'ok' });
23
+ });
106
24
 
107
- async function bootstrap() {
108
- try {
109
- await connectDatabase();
110
- logger.info('Database connected');
25
+ app.listen(3000);
26
+ ```
111
27
 
112
- const app = createApp();
113
- const server = app.listen(config.port, () => {
114
- logger.info(`Server running on port ${config.port}`);
115
- });
28
+ ## Features
116
29
 
117
- // Graceful shutdown
118
- const shutdown = async (signal: string) => {
119
- logger.info(`${signal} received, shutting down gracefully`);
120
- server.close(async () => {
121
- logger.info('HTTP server closed');
122
- process.exit(0);
123
- });
30
+ | Feature | Description | Guide |
31
+ |---------|-------------|-------|
32
+ | Project Setup | TypeScript config, middleware stack | [SETUP.md](SETUP.md) |
33
+ | Routing | Controllers, validation, async handlers | [ROUTING.md](ROUTING.md) |
34
+ | Middleware | Auth, validation, error handling | [MIDDLEWARE.md](MIDDLEWARE.md) |
35
+ | Database | Prisma/TypeORM integration | [DATABASE.md](DATABASE.md) |
36
+ | Testing | Jest, supertest patterns | [TESTING.md](TESTING.md) |
37
+ | Deployment | Docker, PM2, production config | [DEPLOYMENT.md](DEPLOYMENT.md) |
124
38
 
125
- setTimeout(() => {
126
- logger.error('Forced shutdown after timeout');
127
- process.exit(1);
128
- }, 10000);
129
- };
39
+ ## Common Patterns
130
40
 
131
- process.on('SIGTERM', () => shutdown('SIGTERM'));
132
- process.on('SIGINT', () => shutdown('SIGINT'));
133
- } catch (error) {
134
- logger.error('Failed to start server', error);
135
- process.exit(1);
136
- }
137
- }
138
-
139
- bootstrap();
140
- ```
141
-
142
- ### 2. Middleware Patterns
41
+ ### Controller Pattern
143
42
 
144
43
  ```typescript
145
- // src/middleware/auth.ts
44
+ // controllers/users.ts
146
45
  import { Request, Response, NextFunction } from 'express';
147
- import jwt from 'jsonwebtoken';
148
- import { config } from '../config';
149
- import { AppError } from '../errors/AppError';
150
46
  import { UserService } from '../services/UserService';
151
47
 
152
- export interface AuthRequest extends Request {
153
- user?: {
154
- id: string;
155
- email: string;
156
- role: string;
157
- };
158
- }
159
-
160
- export function authenticate() {
161
- return async (req: AuthRequest, res: Response, next: NextFunction) => {
162
- try {
163
- const authHeader = req.headers.authorization;
164
- if (!authHeader?.startsWith('Bearer ')) {
165
- throw new AppError('No token provided', 401, 'UNAUTHORIZED');
166
- }
167
-
168
- const token = authHeader.substring(7);
169
- const decoded = jwt.verify(token, config.jwt.secret) as {
170
- userId: string;
171
- email: string;
172
- role: string;
173
- };
174
-
175
- // Optionally verify user still exists
176
- const user = await UserService.findById(decoded.userId);
177
- if (!user) {
178
- throw new AppError('User not found', 401, 'UNAUTHORIZED');
179
- }
180
-
181
- req.user = {
182
- id: decoded.userId,
183
- email: decoded.email,
184
- role: decoded.role,
185
- };
186
-
187
- next();
188
- } catch (error) {
189
- if (error instanceof jwt.JsonWebTokenError) {
190
- next(new AppError('Invalid token', 401, 'INVALID_TOKEN'));
191
- } else if (error instanceof jwt.TokenExpiredError) {
192
- next(new AppError('Token expired', 401, 'TOKEN_EXPIRED'));
193
- } else {
194
- next(error);
195
- }
196
- }
197
- };
198
- }
199
-
200
- export function authorize(...roles: string[]) {
201
- return (req: AuthRequest, res: Response, next: NextFunction) => {
202
- if (!req.user) {
203
- return next(new AppError('Authentication required', 401, 'UNAUTHORIZED'));
204
- }
205
-
206
- if (!roles.includes(req.user.role)) {
207
- return next(new AppError('Insufficient permissions', 403, 'FORBIDDEN'));
208
- }
209
-
210
- next();
211
- };
212
- }
213
-
214
- // src/middleware/validate.ts
215
- import { Request, Response, NextFunction } from 'express';
216
- import { ZodSchema, ZodError } from 'zod';
217
- import { AppError } from '../errors/AppError';
218
-
219
- interface ValidationSchemas {
220
- body?: ZodSchema;
221
- query?: ZodSchema;
222
- params?: ZodSchema;
223
- }
48
+ export class UserController {
49
+ constructor(private userService: UserService) {}
224
50
 
225
- export function validate(schemas: ValidationSchemas) {
226
- return async (req: Request, res: Response, next: NextFunction) => {
51
+ getAll = async (req: Request, res: Response, next: NextFunction) => {
227
52
  try {
228
- if (schemas.body) {
229
- req.body = await schemas.body.parseAsync(req.body);
230
- }
231
- if (schemas.query) {
232
- req.query = await schemas.query.parseAsync(req.query);
233
- }
234
- if (schemas.params) {
235
- req.params = await schemas.params.parseAsync(req.params);
236
- }
237
- next();
53
+ const users = await this.userService.findAll();
54
+ res.json(users);
238
55
  } catch (error) {
239
- if (error instanceof ZodError) {
240
- const details = error.errors.map(err => ({
241
- path: err.path.join('.'),
242
- message: err.message,
243
- }));
244
- next(new AppError('Validation failed', 400, 'VALIDATION_ERROR', details));
245
- } else {
246
- next(error);
247
- }
56
+ next(error);
248
57
  }
249
58
  };
250
- }
251
-
252
- // src/middleware/requestId.ts
253
- import { Request, Response, NextFunction } from 'express';
254
- import { v4 as uuidv4 } from 'uuid';
255
-
256
- export interface RequestWithId extends Request {
257
- id: string;
258
- }
259
-
260
- export function requestId() {
261
- return (req: RequestWithId, res: Response, next: NextFunction) => {
262
- req.id = (req.headers['x-request-id'] as string) || uuidv4();
263
- res.setHeader('X-Request-ID', req.id);
264
- next();
265
- };
266
- }
267
-
268
- // src/middleware/cache.ts
269
- import { Request, Response, NextFunction } from 'express';
270
- import { redis } from '../database/redis';
271
-
272
- interface CacheOptions {
273
- ttl?: number;
274
- keyGenerator?: (req: Request) => string;
275
- }
276
-
277
- export function cache(options: CacheOptions = {}) {
278
- const { ttl = 300, keyGenerator } = options;
279
-
280
- return async (req: Request, res: Response, next: NextFunction) => {
281
- if (req.method !== 'GET') {
282
- return next();
283
- }
284
-
285
- const key = keyGenerator?.(req) || `cache:${req.originalUrl}`;
286
59
 
60
+ getById = async (req: Request, res: Response, next: NextFunction) => {
287
61
  try {
288
- const cached = await redis.get(key);
289
- if (cached) {
290
- res.setHeader('X-Cache', 'HIT');
291
- return res.json(JSON.parse(cached));
292
- }
293
-
294
- // Override res.json to cache the response
295
- const originalJson = res.json.bind(res);
296
- res.json = (body: unknown) => {
297
- redis.setex(key, ttl, JSON.stringify(body));
298
- res.setHeader('X-Cache', 'MISS');
299
- return originalJson(body);
300
- };
301
-
302
- next();
62
+ const user = await this.userService.findById(req.params.id);
63
+ if (!user) return res.status(404).json({ error: 'Not found' });
64
+ res.json(user);
303
65
  } catch (error) {
304
- // If cache fails, continue without caching
305
- next();
66
+ next(error);
306
67
  }
307
68
  };
308
69
  }
309
70
  ```
310
71
 
311
- ### 3. Error Handling
72
+ ### Error Handler
312
73
 
313
74
  ```typescript
314
- // src/errors/AppError.ts
315
- export class AppError extends Error {
316
- public readonly statusCode: number;
317
- public readonly code: string;
318
- public readonly isOperational: boolean;
319
- public readonly details?: unknown;
75
+ // middleware/errorHandler.ts
76
+ import { Request, Response, NextFunction } from 'express';
320
77
 
78
+ export class AppError extends Error {
321
79
  constructor(
80
+ public statusCode: number,
322
81
  message: string,
323
- statusCode: number = 500,
324
- code: string = 'INTERNAL_ERROR',
325
- details?: unknown,
326
- isOperational: boolean = true
82
+ public isOperational = true
327
83
  ) {
328
84
  super(message);
329
- this.statusCode = statusCode;
330
- this.code = code;
331
- this.isOperational = isOperational;
332
- this.details = details;
333
-
334
- Error.captureStackTrace(this, this.constructor);
335
- }
336
- }
337
-
338
- export class NotFoundError extends AppError {
339
- constructor(resource: string, id?: string) {
340
- super(
341
- id ? `${resource} with id ${id} not found` : `${resource} not found`,
342
- 404,
343
- 'NOT_FOUND'
344
- );
345
85
  }
346
86
  }
347
87
 
348
- export class ValidationError extends AppError {
349
- constructor(message: string, details?: unknown) {
350
- super(message, 400, 'VALIDATION_ERROR', details);
351
- }
352
- }
353
-
354
- export class UnauthorizedError extends AppError {
355
- constructor(message: string = 'Unauthorized') {
356
- super(message, 401, 'UNAUTHORIZED');
357
- }
358
- }
359
-
360
- export class ForbiddenError extends AppError {
361
- constructor(message: string = 'Forbidden') {
362
- super(message, 403, 'FORBIDDEN');
363
- }
364
- }
365
-
366
- export class ConflictError extends AppError {
367
- constructor(message: string) {
368
- super(message, 409, 'CONFLICT');
369
- }
370
- }
371
-
372
- // src/middleware/errorHandler.ts
373
- import { Request, Response, NextFunction } from 'express';
374
- import { AppError } from '../errors/AppError';
375
- import { logger } from '../utils/logger';
376
- import { config } from '../config';
377
-
378
88
  export function errorHandler(
379
89
  err: Error,
380
90
  req: Request,
381
91
  res: Response,
382
92
  next: NextFunction
383
93
  ) {
384
- // Log error
385
- logger.error({
386
- error: err.message,
387
- stack: err.stack,
388
- path: req.path,
389
- method: req.method,
390
- body: req.body,
391
- });
392
-
393
- // Handle known operational errors
394
94
  if (err instanceof AppError) {
395
- return res.status(err.statusCode).json({
396
- error: {
397
- code: err.code,
398
- message: err.message,
399
- details: err.details,
400
- },
401
- });
402
- }
403
-
404
- // Handle Prisma errors
405
- if (err.constructor.name === 'PrismaClientKnownRequestError') {
406
- const prismaError = err as { code: string; meta?: { target?: string[] } };
407
- if (prismaError.code === 'P2002') {
408
- const field = prismaError.meta?.target?.[0] || 'field';
409
- return res.status(409).json({
410
- error: {
411
- code: 'CONFLICT',
412
- message: `A record with this ${field} already exists`,
413
- },
414
- });
415
- }
416
- if (prismaError.code === 'P2025') {
417
- return res.status(404).json({
418
- error: {
419
- code: 'NOT_FOUND',
420
- message: 'Record not found',
421
- },
422
- });
423
- }
95
+ return res.status(err.statusCode).json({ error: err.message });
424
96
  }
425
97
 
426
- // Handle unknown errors
427
- const message = config.isProduction
428
- ? 'An unexpected error occurred'
429
- : err.message;
430
-
431
- res.status(500).json({
432
- error: {
433
- code: 'INTERNAL_ERROR',
434
- message,
435
- ...(config.isDevelopment && { stack: err.stack }),
436
- },
437
- });
438
- }
439
-
440
- // src/middleware/notFoundHandler.ts
441
- import { Request, Response } from 'express';
442
-
443
- export function notFoundHandler(req: Request, res: Response) {
444
- res.status(404).json({
445
- error: {
446
- code: 'NOT_FOUND',
447
- message: `Route ${req.method} ${req.path} not found`,
448
- },
449
- });
98
+ console.error(err);
99
+ res.status(500).json({ error: 'Internal server error' });
450
100
  }
451
101
  ```
452
102
 
453
- ### 4. Router Organization
103
+ ### Validation Middleware
454
104
 
455
105
  ```typescript
456
- // src/routes/index.ts
457
- import { Router } from 'express';
458
- import { userRouter } from './users';
459
- import { authRouter } from './auth';
460
- import { projectRouter } from './projects';
461
- import { organizationRouter } from './organizations';
462
-
463
- export const apiRouter = Router();
464
-
465
- apiRouter.use('/auth', authRouter);
466
- apiRouter.use('/users', userRouter);
467
- apiRouter.use('/organizations', organizationRouter);
468
- apiRouter.use('/projects', projectRouter);
469
-
470
- // src/routes/users.ts
471
- import { Router } from 'express';
472
- import { UserController } from '../controllers/UserController';
473
- import { authenticate, authorize } from '../middleware/auth';
474
- import { validate } from '../middleware/validate';
475
- import { userSchemas } from '../schemas/userSchemas';
476
-
477
- export const userRouter = Router();
478
- const controller = new UserController();
479
-
480
- userRouter.get(
481
- '/',
482
- authenticate(),
483
- authorize('admin'),
484
- validate({ query: userSchemas.list }),
485
- controller.list
486
- );
487
-
488
- userRouter.get(
489
- '/me',
490
- authenticate(),
491
- controller.getCurrentUser
492
- );
493
-
494
- userRouter.patch(
495
- '/me',
496
- authenticate(),
497
- validate({ body: userSchemas.updateProfile }),
498
- controller.updateProfile
499
- );
500
-
501
- userRouter.get(
502
- '/:id',
503
- authenticate(),
504
- validate({ params: userSchemas.params }),
505
- controller.getById
506
- );
507
-
508
- userRouter.post(
509
- '/',
510
- authenticate(),
511
- authorize('admin'),
512
- validate({ body: userSchemas.create }),
513
- controller.create
514
- );
515
-
516
- userRouter.patch(
517
- '/:id',
518
- authenticate(),
519
- authorize('admin'),
520
- validate({ params: userSchemas.params, body: userSchemas.update }),
521
- controller.update
522
- );
523
-
524
- userRouter.delete(
525
- '/:id',
526
- authenticate(),
527
- authorize('admin'),
528
- validate({ params: userSchemas.params }),
529
- controller.delete
530
- );
531
-
532
- // src/routes/projects.ts
533
- import { Router } from 'express';
534
- import { ProjectController } from '../controllers/ProjectController';
535
- import { authenticate } from '../middleware/auth';
536
- import { validate } from '../middleware/validate';
537
- import { projectSchemas } from '../schemas/projectSchemas';
538
- import { cache } from '../middleware/cache';
539
-
540
- export const projectRouter = Router();
541
- const controller = new ProjectController();
542
-
543
- projectRouter.use(authenticate());
544
-
545
- projectRouter.get(
546
- '/',
547
- validate({ query: projectSchemas.list }),
548
- cache({ ttl: 60 }),
549
- controller.list
550
- );
551
-
552
- projectRouter.get(
553
- '/:id',
554
- validate({ params: projectSchemas.params }),
555
- cache({ ttl: 300 }),
556
- controller.getById
557
- );
558
-
559
- projectRouter.post(
560
- '/',
561
- validate({ body: projectSchemas.create }),
562
- controller.create
563
- );
564
-
565
- projectRouter.patch(
566
- '/:id',
567
- validate({ params: projectSchemas.params, body: projectSchemas.update }),
568
- controller.update
569
- );
570
-
571
- projectRouter.delete(
572
- '/:id',
573
- validate({ params: projectSchemas.params }),
574
- controller.delete
575
- );
576
-
577
- // Nested routes for project tasks
578
- projectRouter.get(
579
- '/:id/tasks',
580
- validate({ params: projectSchemas.params, query: projectSchemas.taskList }),
581
- controller.getTasks
582
- );
583
-
584
- projectRouter.post(
585
- '/:id/tasks',
586
- validate({ params: projectSchemas.params, body: projectSchemas.createTask }),
587
- controller.createTask
588
- );
589
- ```
590
-
591
- ### 5. Controllers and Services
592
-
593
- ```typescript
594
- // src/controllers/UserController.ts
595
- import { Response, NextFunction } from 'express';
596
- import { AuthRequest } from '../middleware/auth';
597
- import { UserService } from '../services/UserService';
598
- import { NotFoundError } from '../errors/AppError';
599
-
600
- export class UserController {
601
- private userService = new UserService();
602
-
603
- list = async (req: AuthRequest, res: Response, next: NextFunction) => {
604
- try {
605
- const { page = 1, limit = 20, search, role } = req.query as {
606
- page?: number;
607
- limit?: number;
608
- search?: string;
609
- role?: string;
610
- };
611
-
612
- const result = await this.userService.findAll({
613
- page: Number(page),
614
- limit: Math.min(Number(limit), 100),
615
- search,
616
- role,
617
- });
618
-
619
- res.json(result);
620
- } catch (error) {
621
- next(error);
622
- }
623
- };
624
-
625
- getCurrentUser = async (req: AuthRequest, res: Response, next: NextFunction) => {
626
- try {
627
- const user = await this.userService.findById(req.user!.id);
628
- if (!user) {
629
- throw new NotFoundError('User');
630
- }
631
- res.json(user);
632
- } catch (error) {
633
- next(error);
634
- }
635
- };
636
-
637
- getById = async (req: AuthRequest, res: Response, next: NextFunction) => {
638
- try {
639
- const user = await this.userService.findById(req.params.id);
640
- if (!user) {
641
- throw new NotFoundError('User', req.params.id);
642
- }
643
- res.json(user);
644
- } catch (error) {
645
- next(error);
646
- }
647
- };
648
-
649
- create = async (req: AuthRequest, res: Response, next: NextFunction) => {
650
- try {
651
- const user = await this.userService.create(req.body);
652
- res.status(201).json(user);
653
- } catch (error) {
654
- next(error);
655
- }
656
- };
657
-
658
- updateProfile = async (req: AuthRequest, res: Response, next: NextFunction) => {
659
- try {
660
- const user = await this.userService.update(req.user!.id, req.body);
661
- res.json(user);
662
- } catch (error) {
663
- next(error);
664
- }
665
- };
666
-
667
- update = async (req: AuthRequest, res: Response, next: NextFunction) => {
668
- try {
669
- const user = await this.userService.update(req.params.id, req.body);
670
- res.json(user);
671
- } catch (error) {
672
- next(error);
673
- }
674
- };
675
-
676
- delete = async (req: AuthRequest, res: Response, next: NextFunction) => {
677
- try {
678
- await this.userService.delete(req.params.id);
679
- res.status(204).send();
680
- } catch (error) {
681
- next(error);
682
- }
683
- };
684
- }
685
-
686
- // src/services/UserService.ts
687
- import { Prisma } from '@prisma/client';
688
- import { prisma } from '../database/prisma';
689
- import { ConflictError, NotFoundError } from '../errors/AppError';
690
- import { hashPassword, verifyPassword } from '../utils/password';
691
-
692
- interface FindAllOptions {
693
- page: number;
694
- limit: number;
695
- search?: string;
696
- role?: string;
697
- }
698
-
699
- interface CreateUserData {
700
- email: string;
701
- password: string;
702
- name: string;
703
- role?: string;
704
- }
705
-
706
- interface UpdateUserData {
707
- email?: string;
708
- name?: string;
709
- role?: string;
710
- password?: string;
711
- }
712
-
713
- export class UserService {
714
- async findAll(options: FindAllOptions) {
715
- const { page, limit, search, role } = options;
716
- const skip = (page - 1) * limit;
717
-
718
- const where: Prisma.UserWhereInput = {
719
- deletedAt: null,
720
- ...(search && {
721
- OR: [
722
- { name: { contains: search, mode: 'insensitive' } },
723
- { email: { contains: search, mode: 'insensitive' } },
724
- ],
725
- }),
726
- ...(role && { role }),
727
- };
728
-
729
- const [users, total] = await Promise.all([
730
- prisma.user.findMany({
731
- where,
732
- skip,
733
- take: limit,
734
- select: {
735
- id: true,
736
- email: true,
737
- name: true,
738
- role: true,
739
- createdAt: true,
740
- },
741
- orderBy: { createdAt: 'desc' },
742
- }),
743
- prisma.user.count({ where }),
744
- ]);
745
-
746
- return {
747
- data: users,
748
- pagination: {
749
- page,
750
- limit,
751
- total,
752
- totalPages: Math.ceil(total / limit),
753
- hasMore: skip + users.length < total,
754
- },
755
- };
756
- }
757
-
758
- async findById(id: string) {
759
- return prisma.user.findFirst({
760
- where: { id, deletedAt: null },
761
- select: {
762
- id: true,
763
- email: true,
764
- name: true,
765
- role: true,
766
- createdAt: true,
767
- updatedAt: true,
768
- organizations: {
769
- select: {
770
- organization: {
771
- select: { id: true, name: true, slug: true },
772
- },
773
- role: true,
774
- },
775
- },
776
- },
777
- });
778
- }
779
-
780
- async findByEmail(email: string) {
781
- return prisma.user.findFirst({
782
- where: { email, deletedAt: null },
783
- });
784
- }
785
-
786
- async create(data: CreateUserData) {
787
- const existing = await this.findByEmail(data.email);
788
- if (existing) {
789
- throw new ConflictError('Email already in use');
790
- }
791
-
792
- const hashedPassword = await hashPassword(data.password);
793
-
794
- return prisma.user.create({
795
- data: {
796
- ...data,
797
- password: hashedPassword,
798
- },
799
- select: {
800
- id: true,
801
- email: true,
802
- name: true,
803
- role: true,
804
- createdAt: true,
805
- },
806
- });
807
- }
808
-
809
- async update(id: string, data: UpdateUserData) {
810
- const user = await this.findById(id);
811
- if (!user) {
812
- throw new NotFoundError('User', id);
813
- }
814
-
815
- if (data.email && data.email !== user.email) {
816
- const existing = await this.findByEmail(data.email);
817
- if (existing) {
818
- throw new ConflictError('Email already in use');
819
- }
820
- }
821
-
822
- const updateData: Prisma.UserUpdateInput = { ...data };
823
- if (data.password) {
824
- updateData.password = await hashPassword(data.password);
825
- }
826
-
827
- return prisma.user.update({
828
- where: { id },
829
- data: updateData,
830
- select: {
831
- id: true,
832
- email: true,
833
- name: true,
834
- role: true,
835
- updatedAt: true,
836
- },
837
- });
838
- }
839
-
840
- async delete(id: string) {
841
- const user = await this.findById(id);
842
- if (!user) {
843
- throw new NotFoundError('User', id);
844
- }
845
-
846
- // Soft delete
847
- await prisma.user.update({
848
- where: { id },
849
- data: { deletedAt: new Date() },
850
- });
851
- }
106
+ // middleware/validate.ts
107
+ import { Request, Response, NextFunction } from 'express';
108
+ import { ZodSchema } from 'zod';
852
109
 
853
- async verifyCredentials(email: string, password: string) {
854
- const user = await prisma.user.findFirst({
855
- where: { email, deletedAt: null },
110
+ export function validate(schema: ZodSchema) {
111
+ return (req: Request, res: Response, next: NextFunction) => {
112
+ const result = schema.safeParse({
113
+ body: req.body,
114
+ query: req.query,
115
+ params: req.params,
856
116
  });
857
117
 
858
- if (!user) {
859
- return null;
860
- }
861
-
862
- const isValid = await verifyPassword(password, user.password);
863
- if (!isValid) {
864
- return null;
118
+ if (!result.success) {
119
+ return res.status(400).json({ errors: result.error.issues });
865
120
  }
866
121
 
867
- return {
868
- id: user.id,
869
- email: user.email,
870
- name: user.name,
871
- role: user.role,
872
- };
873
- }
122
+ next();
123
+ };
874
124
  }
875
125
  ```
876
126
 
877
- ### 6. Validation Schemas
127
+ ## Workflows
878
128
 
879
- ```typescript
880
- // src/schemas/userSchemas.ts
881
- import { z } from 'zod';
129
+ ### API Development Workflow
882
130
 
883
- export const userSchemas = {
884
- params: z.object({
885
- id: z.string().uuid('Invalid user ID'),
886
- }),
131
+ 1. Define routes in `routes/index.ts`
132
+ 2. Create controller with business logic
133
+ 3. Add validation schemas with Zod
134
+ 4. Write tests with supertest
135
+ 5. Document with OpenAPI/Swagger
887
136
 
888
- list: z.object({
889
- page: z.coerce.number().int().positive().default(1),
890
- limit: z.coerce.number().int().positive().max(100).default(20),
891
- search: z.string().optional(),
892
- role: z.enum(['admin', 'user', 'guest']).optional(),
893
- }),
137
+ ### Middleware Order
894
138
 
895
- create: z.object({
896
- email: z.string().email('Invalid email address'),
897
- password: z
898
- .string()
899
- .min(8, 'Password must be at least 8 characters')
900
- .regex(/[A-Z]/, 'Password must contain an uppercase letter')
901
- .regex(/[0-9]/, 'Password must contain a number'),
902
- name: z.string().min(2).max(100),
903
- role: z.enum(['admin', 'user', 'guest']).default('user'),
904
- }),
905
-
906
- update: z.object({
907
- email: z.string().email('Invalid email address').optional(),
908
- name: z.string().min(2).max(100).optional(),
909
- role: z.enum(['admin', 'user', 'guest']).optional(),
910
- password: z
911
- .string()
912
- .min(8, 'Password must be at least 8 characters')
913
- .regex(/[A-Z]/, 'Password must contain an uppercase letter')
914
- .regex(/[0-9]/, 'Password must contain a number')
915
- .optional(),
916
- }).refine(data => Object.keys(data).length > 0, {
917
- message: 'At least one field must be provided',
918
- }),
919
-
920
- updateProfile: z.object({
921
- name: z.string().min(2).max(100).optional(),
922
- avatar: z.string().url().optional(),
923
- }),
924
- };
925
-
926
- // src/schemas/projectSchemas.ts
927
- import { z } from 'zod';
928
-
929
- export const projectSchemas = {
930
- params: z.object({
931
- id: z.string().uuid('Invalid project ID'),
932
- }),
933
-
934
- list: z.object({
935
- page: z.coerce.number().int().positive().default(1),
936
- limit: z.coerce.number().int().positive().max(100).default(20),
937
- status: z.enum(['draft', 'active', 'completed', 'archived']).optional(),
938
- search: z.string().optional(),
939
- sortBy: z.enum(['createdAt', 'name', 'updatedAt']).default('createdAt'),
940
- sortOrder: z.enum(['asc', 'desc']).default('desc'),
941
- }),
942
-
943
- create: z.object({
944
- name: z.string().min(1).max(255),
945
- description: z.string().max(5000).optional(),
946
- organizationId: z.string().uuid(),
947
- }),
948
-
949
- update: z.object({
950
- name: z.string().min(1).max(255).optional(),
951
- description: z.string().max(5000).optional(),
952
- status: z.enum(['draft', 'active', 'completed', 'archived']).optional(),
953
- }),
954
-
955
- taskList: z.object({
956
- page: z.coerce.number().int().positive().default(1),
957
- limit: z.coerce.number().int().positive().max(100).default(20),
958
- status: z.enum(['todo', 'in_progress', 'done']).optional(),
959
- }),
960
-
961
- createTask: z.object({
962
- title: z.string().min(1).max(255),
963
- description: z.string().max(5000).optional(),
964
- priority: z.enum(['low', 'medium', 'high']).default('medium'),
965
- assigneeId: z.string().uuid().optional(),
966
- dueDate: z.coerce.date().optional(),
967
- }),
968
- };
969
139
  ```
970
-
971
- ### 7. Testing Patterns
972
-
973
- ```typescript
974
- // tests/setup.ts
975
- import { PrismaClient } from '@prisma/client';
976
- import { mockDeep, DeepMockProxy } from 'jest-mock-extended';
977
-
978
- export const prismaMock = mockDeep<PrismaClient>();
979
-
980
- jest.mock('../src/database/prisma', () => ({
981
- prisma: prismaMock,
982
- }));
983
-
984
- beforeEach(() => {
985
- jest.clearAllMocks();
986
- });
987
-
988
- // tests/integration/users.test.ts
989
- import request from 'supertest';
990
- import { createApp } from '../../src/app';
991
- import { prisma } from '../../src/database/prisma';
992
- import { generateToken } from '../../src/utils/jwt';
993
-
994
- const app = createApp();
995
-
996
- describe('Users API', () => {
997
- let adminToken: string;
998
- let userToken: string;
999
-
1000
- beforeAll(async () => {
1001
- // Create test users
1002
- const admin = await prisma.user.create({
1003
- data: {
1004
- email: 'admin@test.com',
1005
- password: await hashPassword('Admin123!'),
1006
- name: 'Admin User',
1007
- role: 'admin',
1008
- },
1009
- });
1010
-
1011
- const user = await prisma.user.create({
1012
- data: {
1013
- email: 'user@test.com',
1014
- password: await hashPassword('User123!'),
1015
- name: 'Regular User',
1016
- role: 'user',
1017
- },
1018
- });
1019
-
1020
- adminToken = generateToken(admin);
1021
- userToken = generateToken(user);
1022
- });
1023
-
1024
- afterAll(async () => {
1025
- await prisma.user.deleteMany();
1026
- });
1027
-
1028
- describe('GET /api/v1/users', () => {
1029
- it('should return paginated users for admin', async () => {
1030
- const response = await request(app)
1031
- .get('/api/v1/users')
1032
- .set('Authorization', `Bearer ${adminToken}`)
1033
- .expect(200);
1034
-
1035
- expect(response.body).toHaveProperty('data');
1036
- expect(response.body).toHaveProperty('pagination');
1037
- expect(Array.isArray(response.body.data)).toBe(true);
1038
- });
1039
-
1040
- it('should return 403 for non-admin users', async () => {
1041
- await request(app)
1042
- .get('/api/v1/users')
1043
- .set('Authorization', `Bearer ${userToken}`)
1044
- .expect(403);
1045
- });
1046
-
1047
- it('should return 401 without token', async () => {
1048
- await request(app)
1049
- .get('/api/v1/users')
1050
- .expect(401);
1051
- });
1052
-
1053
- it('should filter users by search term', async () => {
1054
- const response = await request(app)
1055
- .get('/api/v1/users?search=admin')
1056
- .set('Authorization', `Bearer ${adminToken}`)
1057
- .expect(200);
1058
-
1059
- expect(response.body.data.every((u: any) =>
1060
- u.name.toLowerCase().includes('admin') ||
1061
- u.email.toLowerCase().includes('admin')
1062
- )).toBe(true);
1063
- });
1064
- });
1065
-
1066
- describe('GET /api/v1/users/me', () => {
1067
- it('should return current user profile', async () => {
1068
- const response = await request(app)
1069
- .get('/api/v1/users/me')
1070
- .set('Authorization', `Bearer ${userToken}`)
1071
- .expect(200);
1072
-
1073
- expect(response.body.email).toBe('user@test.com');
1074
- expect(response.body).not.toHaveProperty('password');
1075
- });
1076
- });
1077
-
1078
- describe('POST /api/v1/users', () => {
1079
- it('should create a new user', async () => {
1080
- const newUser = {
1081
- email: 'new@test.com',
1082
- password: 'NewUser123!',
1083
- name: 'New User',
1084
- role: 'user',
1085
- };
1086
-
1087
- const response = await request(app)
1088
- .post('/api/v1/users')
1089
- .set('Authorization', `Bearer ${adminToken}`)
1090
- .send(newUser)
1091
- .expect(201);
1092
-
1093
- expect(response.body.email).toBe(newUser.email);
1094
- expect(response.body).toHaveProperty('id');
1095
- expect(response.body).not.toHaveProperty('password');
1096
- });
1097
-
1098
- it('should return 400 for invalid email', async () => {
1099
- const response = await request(app)
1100
- .post('/api/v1/users')
1101
- .set('Authorization', `Bearer ${adminToken}`)
1102
- .send({
1103
- email: 'invalid-email',
1104
- password: 'Valid123!',
1105
- name: 'Test User',
1106
- })
1107
- .expect(400);
1108
-
1109
- expect(response.body.error.code).toBe('VALIDATION_ERROR');
1110
- });
1111
-
1112
- it('should return 409 for duplicate email', async () => {
1113
- await request(app)
1114
- .post('/api/v1/users')
1115
- .set('Authorization', `Bearer ${adminToken}`)
1116
- .send({
1117
- email: 'user@test.com',
1118
- password: 'Duplicate123!',
1119
- name: 'Duplicate User',
1120
- })
1121
- .expect(409);
1122
- });
1123
- });
1124
- });
1125
-
1126
- // tests/unit/UserService.test.ts
1127
- import { UserService } from '../../src/services/UserService';
1128
- import { prismaMock } from '../setup';
1129
-
1130
- describe('UserService', () => {
1131
- const service = new UserService();
1132
-
1133
- describe('findAll', () => {
1134
- it('should return paginated users', async () => {
1135
- const mockUsers = [
1136
- { id: '1', email: 'user1@test.com', name: 'User 1', role: 'user', createdAt: new Date() },
1137
- { id: '2', email: 'user2@test.com', name: 'User 2', role: 'user', createdAt: new Date() },
1138
- ];
1139
-
1140
- prismaMock.user.findMany.mockResolvedValue(mockUsers);
1141
- prismaMock.user.count.mockResolvedValue(2);
1142
-
1143
- const result = await service.findAll({ page: 1, limit: 10 });
1144
-
1145
- expect(result.data).toHaveLength(2);
1146
- expect(result.pagination.total).toBe(2);
1147
- expect(prismaMock.user.findMany).toHaveBeenCalledWith(
1148
- expect.objectContaining({
1149
- skip: 0,
1150
- take: 10,
1151
- })
1152
- );
1153
- });
1154
-
1155
- it('should apply search filter', async () => {
1156
- prismaMock.user.findMany.mockResolvedValue([]);
1157
- prismaMock.user.count.mockResolvedValue(0);
1158
-
1159
- await service.findAll({ page: 1, limit: 10, search: 'john' });
1160
-
1161
- expect(prismaMock.user.findMany).toHaveBeenCalledWith(
1162
- expect.objectContaining({
1163
- where: expect.objectContaining({
1164
- OR: expect.arrayContaining([
1165
- { name: { contains: 'john', mode: 'insensitive' } },
1166
- ]),
1167
- }),
1168
- })
1169
- );
1170
- });
1171
- });
1172
-
1173
- describe('create', () => {
1174
- it('should create a new user with hashed password', async () => {
1175
- const userData = {
1176
- email: 'new@test.com',
1177
- password: 'Password123!',
1178
- name: 'New User',
1179
- };
1180
-
1181
- prismaMock.user.findFirst.mockResolvedValue(null);
1182
- prismaMock.user.create.mockResolvedValue({
1183
- id: 'new-id',
1184
- ...userData,
1185
- password: 'hashed',
1186
- role: 'user',
1187
- createdAt: new Date(),
1188
- });
1189
-
1190
- const result = await service.create(userData);
1191
-
1192
- expect(result.email).toBe(userData.email);
1193
- expect(prismaMock.user.create).toHaveBeenCalledWith(
1194
- expect.objectContaining({
1195
- data: expect.objectContaining({
1196
- email: userData.email,
1197
- password: expect.not.stringContaining(userData.password),
1198
- }),
1199
- })
1200
- );
1201
- });
1202
- });
1203
- });
140
+ 1. Security (helmet, cors)
141
+ 2. Rate limiting
142
+ 3. Body parsing
143
+ 4. Logging
144
+ 5. Authentication
145
+ 6. Routes
146
+ 7. 404 handler
147
+ 8. Error handler
1204
148
  ```
1205
149
 
1206
- ## Use Cases
1207
-
1208
- ### REST API for E-commerce Platform
1209
-
1210
- ```typescript
1211
- // src/routes/products.ts
1212
- import { Router } from 'express';
1213
- import { ProductController } from '../controllers/ProductController';
1214
- import { authenticate, authorize } from '../middleware/auth';
1215
- import { validate } from '../middleware/validate';
1216
- import { cache } from '../middleware/cache';
1217
- import { upload } from '../middleware/upload';
1218
- import { z } from 'zod';
1219
-
1220
- const router = Router();
1221
- const controller = new ProductController();
1222
-
1223
- const productSchemas = {
1224
- list: z.object({
1225
- category: z.string().optional(),
1226
- minPrice: z.coerce.number().positive().optional(),
1227
- maxPrice: z.coerce.number().positive().optional(),
1228
- search: z.string().optional(),
1229
- sortBy: z.enum(['price', 'name', 'createdAt', 'popularity']).default('createdAt'),
1230
- sortOrder: z.enum(['asc', 'desc']).default('desc'),
1231
- page: z.coerce.number().positive().default(1),
1232
- limit: z.coerce.number().positive().max(50).default(20),
1233
- }),
1234
- create: z.object({
1235
- name: z.string().min(1).max(255),
1236
- description: z.string().max(5000),
1237
- price: z.number().positive(),
1238
- categoryId: z.string().uuid(),
1239
- inventory: z.number().int().nonnegative().default(0),
1240
- sku: z.string().optional(),
1241
- }),
1242
- };
1243
-
1244
- // Public routes
1245
- router.get(
1246
- '/',
1247
- validate({ query: productSchemas.list }),
1248
- cache({ ttl: 60 }),
1249
- controller.list
1250
- );
1251
-
1252
- router.get(
1253
- '/:id',
1254
- cache({ ttl: 300 }),
1255
- controller.getById
1256
- );
150
+ ## Best Practices
1257
151
 
1258
- // Admin routes
1259
- router.post(
1260
- '/',
1261
- authenticate(),
1262
- authorize('admin'),
1263
- upload.array('images', 5),
1264
- validate({ body: productSchemas.create }),
1265
- controller.create
1266
- );
152
+ | Do | Avoid |
153
+ |----|-------|
154
+ | Use async/await with try-catch | Callback patterns |
155
+ | Validate all inputs | Trusting client data |
156
+ | Use typed request/response | `any` types |
157
+ | Centralize error handling | Scattered try-catch |
158
+ | Use dependency injection | Direct imports in controllers |
1267
159
 
1268
- router.patch(
1269
- '/:id',
1270
- authenticate(),
1271
- authorize('admin'),
1272
- controller.update
1273
- );
160
+ ## Project Structure
1274
161
 
1275
- export { router as productRouter };
1276
162
  ```
1277
-
1278
- ### WebSocket Integration for Real-time Updates
1279
-
1280
- ```typescript
1281
- // src/websocket/index.ts
1282
- import { Server as SocketIOServer } from 'socket.io';
1283
- import { Server } from 'http';
1284
- import { verifyToken } from '../utils/jwt';
1285
- import { logger } from '../utils/logger';
1286
-
1287
- export function setupWebSocket(server: Server) {
1288
- const io = new SocketIOServer(server, {
1289
- cors: {
1290
- origin: process.env.CORS_ORIGINS?.split(',') || '*',
1291
- credentials: true,
1292
- },
1293
- });
1294
-
1295
- // Authentication middleware
1296
- io.use(async (socket, next) => {
1297
- try {
1298
- const token = socket.handshake.auth.token;
1299
- if (!token) {
1300
- return next(new Error('Authentication required'));
1301
- }
1302
-
1303
- const user = await verifyToken(token);
1304
- socket.data.user = user;
1305
- next();
1306
- } catch (error) {
1307
- next(new Error('Invalid token'));
1308
- }
1309
- });
1310
-
1311
- io.on('connection', (socket) => {
1312
- const user = socket.data.user;
1313
- logger.info(`User connected: ${user.id}`);
1314
-
1315
- // Join user's personal room
1316
- socket.join(`user:${user.id}`);
1317
-
1318
- // Join organization rooms
1319
- socket.on('join:organization', async (orgId: string) => {
1320
- // Verify user belongs to organization
1321
- const membership = await checkMembership(user.id, orgId);
1322
- if (membership) {
1323
- socket.join(`org:${orgId}`);
1324
- socket.emit('joined:organization', { orgId });
1325
- }
1326
- });
1327
-
1328
- // Handle real-time notifications
1329
- socket.on('disconnect', () => {
1330
- logger.info(`User disconnected: ${user.id}`);
1331
- });
1332
- });
1333
-
1334
- return io;
1335
- }
1336
-
1337
- // Emit events from services
1338
- export function emitToUser(io: SocketIOServer, userId: string, event: string, data: unknown) {
1339
- io.to(`user:${userId}`).emit(event, data);
1340
- }
1341
-
1342
- export function emitToOrganization(io: SocketIOServer, orgId: string, event: string, data: unknown) {
1343
- io.to(`org:${orgId}`).emit(event, data);
1344
- }
163
+ src/
164
+ ├── app.ts # Express setup
165
+ ├── server.ts # Server entry
166
+ ├── config/ # Environment config
167
+ ├── controllers/ # Route handlers
168
+ ├── middleware/ # Custom middleware
169
+ ├── routes/ # Route definitions
170
+ ├── services/ # Business logic
171
+ ├── utils/ # Helpers
172
+ └── types/ # TypeScript types
1345
173
  ```
1346
174
 
1347
- ## Best Practices
1348
-
1349
- ### Do's
1350
-
1351
- - Use TypeScript for type safety
1352
- - Implement proper error handling with custom error classes
1353
- - Use validation middleware for all inputs
1354
- - Use dependency injection for testability
1355
- - Implement proper logging with correlation IDs
1356
- - Use environment variables for configuration
1357
- - Implement graceful shutdown handling
1358
- - Use compression and rate limiting
1359
- - Write integration and unit tests
1360
- - Use async/await consistently
1361
-
1362
- ### Don'ts
1363
-
1364
- - Don't use callbacks when async/await is available
1365
- - Don't catch errors without proper handling
1366
- - Don't expose stack traces in production
1367
- - Don't store secrets in code
1368
- - Don't skip input validation
1369
- - Don't use synchronous file operations
1370
- - Don't ignore error handling in middleware
1371
- - Don't use `any` type unnecessarily
1372
- - Don't forget to handle promise rejections
1373
- - Don't skip security headers
1374
-
1375
- ## References
1376
-
1377
- - [Express.js Documentation](https://expressjs.com/)
1378
- - [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices)
1379
- - [Prisma Documentation](https://www.prisma.io/docs)
1380
- - [Zod Documentation](https://zod.dev/)
1381
- - [TypeScript Express Tutorial](https://developer.okta.com/blog/2018/11/15/node-express-typescript)
175
+ For detailed examples and patterns, see reference files above.