omgkit 2.1.0 → 2.2.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 (56) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  5. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  6. package/plugin/skills/databases/redis/SKILL.md +885 -25
  7. package/plugin/skills/devops/aws/SKILL.md +686 -28
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  10. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  11. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  12. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  13. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  14. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  15. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  16. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  17. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  18. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  19. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  20. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  21. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  23. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  26. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  27. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  28. package/plugin/skills/languages/python/SKILL.md +489 -25
  29. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  30. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  31. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  32. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  33. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  34. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  35. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  36. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  37. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  38. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  39. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  40. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  41. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  42. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  43. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  44. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  45. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  46. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  47. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  48. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  49. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  50. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  51. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  52. package/plugin/skills/security/oauth/SKILL.md +968 -31
  53. package/plugin/skills/security/owasp/SKILL.md +894 -33
  54. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  55. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  56. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,55 +1,1381 @@
1
1
  ---
2
2
  name: express
3
- description: Express.js development. Use for Express APIs, middleware, routing.
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
4
14
  ---
5
15
 
6
- # Express.js Skill
16
+ # Express.js
7
17
 
8
- ## Patterns
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.
9
19
 
10
- ### Basic Setup
11
- ```javascript
12
- import express from 'express';
20
+ ## Purpose
13
21
 
14
- const app = express();
15
- app.use(express.json());
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
35
+
36
+ ```typescript
37
+ // src/app.ts
38
+ import express, { Application, Request, Response, NextFunction } from 'express';
39
+ import cors from 'cors';
40
+ 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
+
86
+ // Health check
87
+ app.get('/health', (req, res) => {
88
+ res.json({ status: 'healthy', timestamp: new Date().toISOString() });
89
+ });
90
+
91
+ // API routes
92
+ app.use('/api/v1', apiRouter);
93
+
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';
106
+
107
+ async function bootstrap() {
108
+ try {
109
+ await connectDatabase();
110
+ logger.info('Database connected');
111
+
112
+ const app = createApp();
113
+ const server = app.listen(config.port, () => {
114
+ logger.info(`Server running on port ${config.port}`);
115
+ });
116
+
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
+ });
124
+
125
+ setTimeout(() => {
126
+ logger.error('Forced shutdown after timeout');
127
+ process.exit(1);
128
+ }, 10000);
129
+ };
130
+
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();
16
140
  ```
17
141
 
18
- ### Routes
19
- ```javascript
20
- app.get('/users', async (req, res) => {
21
- const users = await db.users.findMany();
22
- res.json(users);
23
- });
142
+ ### 2. Middleware Patterns
24
143
 
25
- app.post('/users', async (req, res) => {
26
- const user = await db.users.create(req.body);
27
- res.status(201).json(user);
28
- });
144
+ ```typescript
145
+ // src/middleware/auth.ts
146
+ import { Request, Response, NextFunction } from 'express';
147
+ import jwt from 'jsonwebtoken';
148
+ import { config } from '../config';
149
+ import { AppError } from '../errors/AppError';
150
+ import { UserService } from '../services/UserService';
151
+
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
+ }
224
+
225
+ export function validate(schemas: ValidationSchemas) {
226
+ return async (req: Request, res: Response, next: NextFunction) => {
227
+ 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();
238
+ } 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
+ }
248
+ }
249
+ };
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
+
287
+ 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();
303
+ } catch (error) {
304
+ // If cache fails, continue without caching
305
+ next();
306
+ }
307
+ };
308
+ }
29
309
  ```
30
310
 
31
- ### Middleware
32
- ```javascript
33
- function authMiddleware(req, res, next) {
34
- const token = req.headers.authorization;
35
- if (!token) return res.status(401).json({ error: 'Unauthorized' });
36
- req.user = verifyToken(token);
37
- next();
311
+ ### 3. Error Handling
312
+
313
+ ```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;
320
+
321
+ constructor(
322
+ message: string,
323
+ statusCode: number = 500,
324
+ code: string = 'INTERNAL_ERROR',
325
+ details?: unknown,
326
+ isOperational: boolean = true
327
+ ) {
328
+ 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
+ }
346
+ }
347
+
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
+ }
38
358
  }
39
359
 
40
- app.use('/api', authMiddleware);
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
+ export function errorHandler(
379
+ err: Error,
380
+ req: Request,
381
+ res: Response,
382
+ next: NextFunction
383
+ ) {
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
+ 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
+ }
424
+ }
425
+
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
+ });
450
+ }
451
+ ```
452
+
453
+ ### 4. Router Organization
454
+
455
+ ```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
+ );
41
589
  ```
42
590
 
43
- ### Error Handler
44
- ```javascript
45
- app.use((err, req, res, next) => {
46
- console.error(err);
47
- res.status(500).json({ error: 'Internal Server Error' });
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
+ }
852
+
853
+ async verifyCredentials(email: string, password: string) {
854
+ const user = await prisma.user.findFirst({
855
+ where: { email, deletedAt: null },
856
+ });
857
+
858
+ if (!user) {
859
+ return null;
860
+ }
861
+
862
+ const isValid = await verifyPassword(password, user.password);
863
+ if (!isValid) {
864
+ return null;
865
+ }
866
+
867
+ return {
868
+ id: user.id,
869
+ email: user.email,
870
+ name: user.name,
871
+ role: user.role,
872
+ };
873
+ }
874
+ }
875
+ ```
876
+
877
+ ### 6. Validation Schemas
878
+
879
+ ```typescript
880
+ // src/schemas/userSchemas.ts
881
+ import { z } from 'zod';
882
+
883
+ export const userSchemas = {
884
+ params: z.object({
885
+ id: z.string().uuid('Invalid user ID'),
886
+ }),
887
+
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
+ }),
894
+
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
+ ```
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
+ });
48
1203
  });
49
1204
  ```
50
1205
 
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
+ );
1257
+
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
+ );
1267
+
1268
+ router.patch(
1269
+ '/:id',
1270
+ authenticate(),
1271
+ authorize('admin'),
1272
+ controller.update
1273
+ );
1274
+
1275
+ export { router as productRouter };
1276
+ ```
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
+ }
1345
+ ```
1346
+
51
1347
  ## Best Practices
52
- - Use async/await with try/catch
53
- - Use middleware for cross-cutting concerns
54
- - Validate input
55
- - Use proper status codes
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)