@devmunna/agent-skillkit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/bin/ai-skills.js +5 -0
  4. package/dist/cli/commands/add.d.ts +2 -0
  5. package/dist/cli/commands/add.d.ts.map +1 -0
  6. package/dist/cli/commands/add.js +66 -0
  7. package/dist/cli/commands/add.js.map +1 -0
  8. package/dist/cli/commands/doctor.d.ts +2 -0
  9. package/dist/cli/commands/doctor.d.ts.map +1 -0
  10. package/dist/cli/commands/doctor.js +33 -0
  11. package/dist/cli/commands/doctor.js.map +1 -0
  12. package/dist/cli/commands/init.d.ts +10 -0
  13. package/dist/cli/commands/init.d.ts.map +1 -0
  14. package/dist/cli/commands/init.js +145 -0
  15. package/dist/cli/commands/init.js.map +1 -0
  16. package/dist/cli/commands/list.d.ts +5 -0
  17. package/dist/cli/commands/list.d.ts.map +1 -0
  18. package/dist/cli/commands/list.js +55 -0
  19. package/dist/cli/commands/list.js.map +1 -0
  20. package/dist/cli/commands/update.d.ts +2 -0
  21. package/dist/cli/commands/update.d.ts.map +1 -0
  22. package/dist/cli/commands/update.js +49 -0
  23. package/dist/cli/commands/update.js.map +1 -0
  24. package/dist/cli/commands/validate.d.ts +2 -0
  25. package/dist/cli/commands/validate.d.ts.map +1 -0
  26. package/dist/cli/commands/validate.js +22 -0
  27. package/dist/cli/commands/validate.js.map +1 -0
  28. package/dist/cli/index.d.ts +2 -0
  29. package/dist/cli/index.d.ts.map +1 -0
  30. package/dist/cli/index.js +49 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/prompts/agent-selector.d.ts +3 -0
  33. package/dist/cli/prompts/agent-selector.d.ts.map +1 -0
  34. package/dist/cli/prompts/agent-selector.js +23 -0
  35. package/dist/cli/prompts/agent-selector.js.map +1 -0
  36. package/dist/cli/prompts/stack-selector.d.ts +3 -0
  37. package/dist/cli/prompts/stack-selector.d.ts.map +1 -0
  38. package/dist/cli/prompts/stack-selector.js +60 -0
  39. package/dist/cli/prompts/stack-selector.js.map +1 -0
  40. package/dist/core/config-manager.d.ts +20 -0
  41. package/dist/core/config-manager.d.ts.map +1 -0
  42. package/dist/core/config-manager.js +107 -0
  43. package/dist/core/config-manager.js.map +1 -0
  44. package/dist/core/detector.d.ts +3 -0
  45. package/dist/core/detector.d.ts.map +1 -0
  46. package/dist/core/detector.js +50 -0
  47. package/dist/core/detector.js.map +1 -0
  48. package/dist/core/doctor.d.ts +12 -0
  49. package/dist/core/doctor.d.ts.map +1 -0
  50. package/dist/core/doctor.js +102 -0
  51. package/dist/core/doctor.js.map +1 -0
  52. package/dist/core/skill-registry.d.ts +11 -0
  53. package/dist/core/skill-registry.d.ts.map +1 -0
  54. package/dist/core/skill-registry.js +174 -0
  55. package/dist/core/skill-registry.js.map +1 -0
  56. package/dist/core/skill-resolver.d.ts +3 -0
  57. package/dist/core/skill-resolver.d.ts.map +1 -0
  58. package/dist/core/skill-resolver.js +36 -0
  59. package/dist/core/skill-resolver.js.map +1 -0
  60. package/dist/core/validator.d.ts +13 -0
  61. package/dist/core/validator.d.ts.map +1 -0
  62. package/dist/core/validator.js +99 -0
  63. package/dist/core/validator.js.map +1 -0
  64. package/dist/generators/agent-installer.d.ts +5 -0
  65. package/dist/generators/agent-installer.d.ts.map +1 -0
  66. package/dist/generators/agent-installer.js +20 -0
  67. package/dist/generators/agent-installer.js.map +1 -0
  68. package/dist/generators/agents-md.d.ts +3 -0
  69. package/dist/generators/agents-md.d.ts.map +1 -0
  70. package/dist/generators/agents-md.js +70 -0
  71. package/dist/generators/agents-md.js.map +1 -0
  72. package/dist/generators/claude-md.d.ts +3 -0
  73. package/dist/generators/claude-md.d.ts.map +1 -0
  74. package/dist/generators/claude-md.js +47 -0
  75. package/dist/generators/claude-md.js.map +1 -0
  76. package/dist/generators/skill-generator.d.ts +5 -0
  77. package/dist/generators/skill-generator.d.ts.map +1 -0
  78. package/dist/generators/skill-generator.js +34 -0
  79. package/dist/generators/skill-generator.js.map +1 -0
  80. package/dist/generators/workflows.d.ts +3 -0
  81. package/dist/generators/workflows.d.ts.map +1 -0
  82. package/dist/generators/workflows.js +57 -0
  83. package/dist/generators/workflows.js.map +1 -0
  84. package/dist/index.d.ts +13 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +13 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/types/index.d.ts +55 -0
  89. package/dist/types/index.d.ts.map +1 -0
  90. package/dist/types/index.js +2 -0
  91. package/dist/types/index.js.map +1 -0
  92. package/dist/utils/file-utils.d.ts +12 -0
  93. package/dist/utils/file-utils.d.ts.map +1 -0
  94. package/dist/utils/file-utils.js +39 -0
  95. package/dist/utils/file-utils.js.map +1 -0
  96. package/dist/utils/logger.d.ts +10 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +11 -0
  99. package/dist/utils/logger.js.map +1 -0
  100. package/package.json +73 -0
  101. package/skills/clean-architecture/SKILL.md +324 -0
  102. package/skills/express-mvc-prisma/SKILL.md +168 -0
  103. package/skills/express-mvc-prisma/references/auth.md +190 -0
  104. package/skills/express-mvc-prisma/references/boilerplate.md +196 -0
  105. package/skills/express-mvc-prisma/references/error-handling.md +121 -0
  106. package/skills/express-mvc-prisma/references/module-scaffold.md +253 -0
  107. package/skills/express-mvc-prisma/references/prisma-setup.md +97 -0
  108. package/skills/express-mvc-prisma/references/response-helpers.md +157 -0
  109. package/skills/express-mvc-prisma/references/zod-validation.md +157 -0
  110. package/skills/fastify-rest/SKILL.md +287 -0
  111. package/skills/mongoose-odm/SKILL.md +281 -0
  112. package/skills/nextjs-fullstack/SKILL.md +328 -0
  113. package/skills/nextjs-fullstack/references/auth.md +270 -0
  114. package/skills/nextjs-fullstack/references/caching.md +157 -0
  115. package/skills/nextjs-fullstack/references/route-handlers.md +194 -0
  116. package/skills/nextjs-fullstack/references/server-actions.md +214 -0
  117. package/skills/nextjs-fullstack/references/server-components.md +190 -0
  118. package/skills/node-base/SKILL.md +139 -0
  119. package/skills/prisma-orm/SKILL.md +334 -0
  120. package/skills/react-feature-arch/SKILL.md +208 -0
  121. package/skills/react-feature-arch/references/api-layer.md +110 -0
  122. package/skills/react-feature-arch/references/components.md +192 -0
  123. package/skills/react-feature-arch/references/data-fetching.md +198 -0
  124. package/skills/react-feature-arch/references/forms.md +194 -0
  125. package/skills/react-feature-arch/references/routing.md +148 -0
  126. package/skills/react-feature-arch/references/state-management.md +107 -0
  127. package/skills/tailwind-css/SKILL.md +236 -0
  128. package/skills/tailwind-css/references/components.md +340 -0
  129. package/skills/tailwind-css/references/design-tokens.md +230 -0
  130. package/skills/tailwind-css/references/patterns.md +375 -0
  131. package/skills/tailwind-css/references/setup.md +165 -0
  132. package/skills/zod-validation/SKILL.md +267 -0
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: express-mvc-prisma
3
+ version: 2.0.0
4
+ description: >
5
+ Use this skill for any Express.js backend task: project setup, new module, route/controller/service/validation, Prisma queries, error handling, auth, middleware, or API design. Triggers: "Express API", "add a module", "create CRUD", "validate request", "set up Prisma", "JWT auth", "error handling", "Express folder structure". Always enforce the 4-file module structure and all patterns below — even when the user only asks for a single file.
6
+ stack: [express, prisma, postgresql, zod, jwt]
7
+ depends: [node-base]
8
+ ---
9
+
10
+ # Express + Prisma + Zod — Production Skill
11
+
12
+ **Version target:** Express v5 · Prisma v5 · Zod v4 · Node.js 20+ (ESM)
13
+
14
+ ---
15
+
16
+ ## Tech Stack
17
+
18
+ | Layer | Package |
19
+ |---|---|
20
+ | Framework | express v5 |
21
+ | ORM | @prisma/client v5 |
22
+ | Database | PostgreSQL |
23
+ | Validation | zod v4 |
24
+ | Auth | jsonwebtoken + bcrypt |
25
+ | Security | helmet + cors + express-rate-limit |
26
+ | Logging | morgan (dev) |
27
+ | Config | dotenv + Zod env validation |
28
+
29
+ ---
30
+
31
+ ## Project Structure
32
+
33
+ ```
34
+ src/
35
+ ├── config/
36
+ │ ├── db.js # Prisma singleton
37
+ │ └── env.js # Zod-validated env vars
38
+ ├── middlewares/
39
+ │ ├── auth.middleware.js # JWT + role guard
40
+ │ ├── validate.middleware.js
41
+ │ └── errorHandler.js # Global error + 404
42
+ ├── modules/ # Every feature = 4 files
43
+ │ └── {module}/
44
+ │ ├── {module}.routes.js
45
+ │ ├── {module}.controller.js
46
+ │ ├── {module}.service.js
47
+ │ └── {module}.validation.js
48
+ ├── utils/
49
+ │ ├── AppError.js # Operational error class
50
+ │ └── response.js # sendSuccess / sendError
51
+ ├── routes/
52
+ │ └── index.js # Mounts all module routers
53
+ └── app.js # Express app (no listen)
54
+ server.js # Entry: connect DB → listen
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Module Pattern — 4 Files (strict)
60
+
61
+ Every feature module has exactly 4 files. No more, no less.
62
+
63
+ ```
64
+ modules/{module}/
65
+ ├── {module}.routes.js # Router + middleware chain
66
+ ├── {module}.controller.js # HTTP only — call service, send response
67
+ ├── {module}.service.js # Business logic + Prisma calls
68
+ └── {module}.validation.js # Zod schemas for body / params / query
69
+ ```
70
+
71
+ **Rules:**
72
+ - Controllers are HTTP adapters only — no business logic, no Prisma imports
73
+ - Services contain all business logic and all Prisma calls
74
+ - Services throw `new AppError(message, statusCode)` — never `res.json` from service
75
+ - **Express v5**: async errors in route handlers are forwarded automatically — no `catchAsync` or `try/catch` needed in controllers
76
+ - Validation schemas live in `*.validation.js` — imported by routes
77
+
78
+ See `references/module-scaffold.md` for the complete 4-file template.
79
+
80
+ ---
81
+
82
+ ## Express v5 — Key Change
83
+
84
+ > Express 5 natively handles rejected async route handlers. Do not wrap controllers in `catchAsync` or `try/catch`. Throw errors from services; the global error handler catches them.
85
+
86
+ ```js
87
+ // Express v5 — async errors auto-forwarded to errorHandler
88
+ export const getById = async (req, res) => {
89
+ const user = await userService.getById(req.params.id); // throws AppError if not found
90
+ sendSuccess(res, 'User fetched', user);
91
+ };
92
+ // No try/catch. No catchAsync. Express v5 handles it.
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Error Flow
98
+
99
+ ```
100
+ Service throws AppError → Controller throws (auto, Express v5) → errorHandler middleware
101
+ Prisma error → errorHandler maps P2002 / P2025
102
+ JWT error → errorHandler maps JsonWebTokenError / TokenExpiredError
103
+ Zod error (fallback) → errorHandler maps ZodError
104
+ ```
105
+
106
+ See `references/error-handling.md`.
107
+
108
+ ---
109
+
110
+ ## Response Helpers — `src/utils/response.js`
111
+
112
+ ```js
113
+ sendSuccess(res, message, data?, statusCode = 200)
114
+ sendError(res, message, statusCode = 500) // used by errorHandler only
115
+ ```
116
+
117
+ Pagination is auto-detected: if `data` has `{ data, total, page, limit }` shape, `meta` block is added.
118
+
119
+ See `references/response-helpers.md`.
120
+
121
+ ---
122
+
123
+ ## Validation — Zod v4
124
+
125
+ Schemas in `{module}.validation.js`. Validate body + params + query in one call.
126
+ Error accessor in v4: `result.error.issues` (not `.flatten()`).
127
+
128
+ ```js
129
+ // validate middleware wraps schema per request segment
130
+ validate(createUserSchema) // validates body, params, query from schema shape
131
+ ```
132
+
133
+ See `references/zod-validation.md`.
134
+
135
+ ---
136
+
137
+ ## Authentication
138
+
139
+ JWT Bearer token. Auth middleware sets `req.user`.
140
+ Role guard: `requireRole('ADMIN')` middleware.
141
+
142
+ See `references/auth.md`.
143
+
144
+ ---
145
+
146
+ ## Prisma Conventions
147
+
148
+ - UUID primary keys (`@default(uuid())`)
149
+ - `createdAt` + `updatedAt` on every model
150
+ - `@@map("snake_case_table")` on every model
151
+ - `@@index([foreignKey])` on every FK
152
+ - Catch `PrismaClientKnownRequestError` in **service layer** (not controller, not global handler)
153
+
154
+ See `references/prisma-setup.md`.
155
+
156
+ ---
157
+
158
+ ## Core References
159
+
160
+ | Topic | File |
161
+ |---|---|
162
+ | Module scaffold (4-file template) | `references/module-scaffold.md` |
163
+ | Boilerplate (app.js, server.js, env, db) | `references/boilerplate.md` |
164
+ | Error handling (AppError, global handler) | `references/error-handling.md` |
165
+ | Auth (JWT middleware, auth module) | `references/auth.md` |
166
+ | Zod v4 validation patterns | `references/zod-validation.md` |
167
+ | Response helpers + pagination | `references/response-helpers.md` |
168
+ | Prisma schema, migrations, queries | `references/prisma-setup.md` |
@@ -0,0 +1,190 @@
1
+ # Auth Reference
2
+
3
+ JWT Bearer token auth. Authenticate middleware sets `req.user`. Role guard protects routes.
4
+
5
+ ---
6
+
7
+ ## src/middlewares/auth.middleware.js
8
+
9
+ ```js
10
+ import jwt from 'jsonwebtoken';
11
+ import { env } from '../config/env.js';
12
+ import prisma from '../config/db.js';
13
+ import { AppError } from '../utils/AppError.js';
14
+
15
+ export const authenticate = async (req, res, next) => {
16
+ const header = req.headers.authorization;
17
+ const token = header?.startsWith('Bearer ') ? header.split(' ')[1] : null;
18
+
19
+ if (!token) return next(new AppError('No token provided', 401));
20
+
21
+ const payload = jwt.verify(token, env.JWT_SECRET); // throws → caught by errorHandler
22
+
23
+ const user = await prisma.user.findUnique({
24
+ where: { id: payload.sub },
25
+ select: { id: true, name: true, email: true, role: true, status: true },
26
+ });
27
+
28
+ if (!user || user.status !== 'ACTIVE') {
29
+ return next(new AppError('Account not found or inactive', 401));
30
+ }
31
+
32
+ req.user = user;
33
+ next();
34
+ };
35
+
36
+ // Role-based guard — use after authenticate
37
+ export const requireRole = (...roles) => (req, res, next) => {
38
+ if (!roles.includes(req.user.role)) {
39
+ return next(new AppError('Insufficient permissions', 403));
40
+ }
41
+ next();
42
+ };
43
+ ```
44
+
45
+ Usage in routes:
46
+ ```js
47
+ router.delete('/:id', authenticate, requireRole('ADMIN'), controller.remove);
48
+ router.patch('/:id', authenticate, requireRole('ADMIN', 'MANAGER'), controller.update);
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Auth Module — 4 Files
54
+
55
+ ### auth.validation.js
56
+
57
+ ```js
58
+ import { z } from 'zod';
59
+
60
+ export const registerSchema = z.object({
61
+ body: z.object({
62
+ name: z.string().min(2).max(100),
63
+ email: z.string().email(),
64
+ password: z.string().min(8, 'Password must be ≥8 characters'),
65
+ }).strict(),
66
+ });
67
+
68
+ export const loginSchema = z.object({
69
+ body: z.object({
70
+ email: z.string().email(),
71
+ password: z.string().min(1),
72
+ }).strict(),
73
+ });
74
+ ```
75
+
76
+ ### auth.service.js
77
+
78
+ ```js
79
+ import bcrypt from 'bcrypt';
80
+ import jwt from 'jsonwebtoken';
81
+ import prisma from '../../config/db.js';
82
+ import { env } from '../../config/env.js';
83
+ import { AppError } from '../../utils/AppError.js';
84
+
85
+ const signToken = (userId) =>
86
+ jwt.sign({ sub: userId }, env.JWT_SECRET, { expiresIn: env.JWT_EXPIRES_IN });
87
+
88
+ export const authService = {
89
+
90
+ async register({ name, email, password }) {
91
+ const exists = await prisma.user.findUnique({ where: { email } });
92
+ if (exists) throw new AppError('Email already registered', 409);
93
+
94
+ const passwordHash = await bcrypt.hash(password, 12);
95
+
96
+ const user = await prisma.user.create({
97
+ data: { name, email, password: passwordHash },
98
+ select: { id: true, name: true, email: true, role: true, createdAt: true },
99
+ });
100
+
101
+ const token = signToken(user.id);
102
+ return { user, token };
103
+ },
104
+
105
+ async login({ email, password }) {
106
+ const user = await prisma.user.findUnique({ where: { email } });
107
+ if (!user) throw new AppError('Invalid credentials', 401);
108
+
109
+ const valid = await bcrypt.compare(password, user.password);
110
+ if (!valid) throw new AppError('Invalid credentials', 401);
111
+
112
+ const { password: _, ...safeUser } = user;
113
+ const token = signToken(safeUser.id);
114
+ return { user: safeUser, token };
115
+ },
116
+
117
+ async me(userId) {
118
+ const user = await prisma.user.findUnique({
119
+ where: { id: userId },
120
+ select: { id: true, name: true, email: true, role: true, createdAt: true },
121
+ });
122
+ if (!user) throw new AppError('User not found', 404);
123
+ return user;
124
+ },
125
+ };
126
+ ```
127
+
128
+ ### auth.controller.js
129
+
130
+ ```js
131
+ import { authService } from './auth.service.js';
132
+ import { sendSuccess } from '../../utils/response.js';
133
+
134
+ export const authController = {
135
+ register: async (req, res) => {
136
+ const result = await authService.register(req.body);
137
+ sendSuccess(res, 'Registered successfully', result, 201);
138
+ },
139
+
140
+ login: async (req, res) => {
141
+ const result = await authService.login(req.body);
142
+ sendSuccess(res, 'Login successful', result);
143
+ },
144
+
145
+ me: async (req, res) => {
146
+ const user = await authService.me(req.user.id);
147
+ sendSuccess(res, 'Profile fetched', user);
148
+ },
149
+ };
150
+ ```
151
+
152
+ ### auth.routes.js
153
+
154
+ ```js
155
+ import { Router } from 'express';
156
+ import { validate } from '../../middlewares/validate.middleware.js';
157
+ import { authenticate } from '../../middlewares/auth.middleware.js';
158
+ import { registerSchema, loginSchema } from './auth.validation.js';
159
+ import { authController } from './auth.controller.js';
160
+
161
+ const router = Router();
162
+
163
+ router.post('/register', validate(registerSchema), authController.register);
164
+ router.post('/login', validate(loginSchema), authController.login);
165
+ router.get('/me', authenticate, authController.me);
166
+
167
+ export default router;
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Prisma User Model (minimum)
173
+
174
+ ```prisma
175
+ model User {
176
+ id String @id @default(uuid())
177
+ name String
178
+ email String @unique
179
+ password String
180
+ role Role @default(USER)
181
+ status Status @default(ACTIVE)
182
+ createdAt DateTime @default(now())
183
+ updatedAt DateTime @updatedAt
184
+
185
+ @@map("users")
186
+ }
187
+
188
+ enum Role { USER ADMIN MANAGER }
189
+ enum Status { ACTIVE SUSPENDED DELETED }
190
+ ```
@@ -0,0 +1,196 @@
1
+ # Boilerplate Reference
2
+
3
+ Express v5 project setup — app.js, server.js, env validation, Prisma singleton.
4
+
5
+ ---
6
+
7
+ ## package.json
8
+
9
+ ```json
10
+ {
11
+ "type": "module",
12
+ "scripts": {
13
+ "dev": "nodemon src/server.js",
14
+ "start": "node src/server.js",
15
+ "migrate": "prisma migrate dev",
16
+ "studio": "prisma studio",
17
+ "seed": "node prisma/seed.js"
18
+ },
19
+ "dependencies": {
20
+ "@prisma/client": "^5.0.0",
21
+ "bcrypt": "^6.0.0",
22
+ "cors": "^2.8.5",
23
+ "dotenv": "^16.0.0",
24
+ "express": "^5.0.0",
25
+ "express-rate-limit":"^7.0.0",
26
+ "helmet": "^8.0.0",
27
+ "jsonwebtoken": "^9.0.0",
28
+ "morgan": "^1.10.0",
29
+ "zod": "^4.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "nodemon": "^3.0.0",
33
+ "prisma": "^5.0.0"
34
+ }
35
+ }
36
+ ```
37
+
38
+ ---
39
+
40
+ ## src/config/env.js
41
+
42
+ Crash at startup if any required variable is missing.
43
+
44
+ ```js
45
+ import { z } from 'zod';
46
+ import 'dotenv/config';
47
+
48
+ const schema = z.object({
49
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
50
+ PORT: z.coerce.number().default(3000),
51
+ DATABASE_URL: z.string().url('DATABASE_URL must be a valid URL'),
52
+ JWT_SECRET: z.string().min(32, 'JWT_SECRET must be ≥32 chars'),
53
+ JWT_EXPIRES_IN: z.string().default('7d'),
54
+ CORS_ORIGIN: z.string().default('*'),
55
+ });
56
+
57
+ const result = schema.safeParse(process.env);
58
+
59
+ if (!result.success) {
60
+ console.error('❌ Invalid environment variables:');
61
+ result.error.issues.forEach((i) => console.error(` • ${i.path.join('.')}: ${i.message}`));
62
+ process.exit(1);
63
+ }
64
+
65
+ export const env = result.data;
66
+ export const isDev = env.NODE_ENV === 'development';
67
+ ```
68
+
69
+ ---
70
+
71
+ ## src/config/db.js — Prisma singleton
72
+
73
+ ```js
74
+ import { PrismaClient } from '@prisma/client';
75
+ import { isDev } from './env.js';
76
+
77
+ const globalForPrisma = globalThis;
78
+
79
+ export const prisma =
80
+ globalForPrisma.prisma ??
81
+ new PrismaClient({ log: isDev ? ['query', 'warn', 'error'] : ['error'] });
82
+
83
+ if (!isDev) globalForPrisma.prisma = prisma;
84
+
85
+ export default prisma;
86
+ ```
87
+
88
+ ---
89
+
90
+ ## src/app.js
91
+
92
+ ```js
93
+ import express from 'express';
94
+ import helmet from 'helmet';
95
+ import cors from 'cors';
96
+ import morgan from 'morgan';
97
+ import rateLimit from 'express-rate-limit';
98
+ import { router } from './routes/index.js';
99
+ import { errorHandler, notFound } from './middlewares/errorHandler.js';
100
+ import { env, isDev } from './config/env.js';
101
+
102
+ const app = express();
103
+
104
+ // Security
105
+ app.use(helmet());
106
+ app.use(cors({ origin: env.CORS_ORIGIN, credentials: true }));
107
+ app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100, standardHeaders: true }));
108
+
109
+ // Parsing
110
+ app.use(express.json({ limit: '10mb' }));
111
+ app.use(express.urlencoded({ extended: true }));
112
+
113
+ // Logging
114
+ if (isDev) app.use(morgan('dev'));
115
+
116
+ // Health check (no auth required)
117
+ app.get('/health', (req, res) => res.json({ status: 'ok', timestamp: new Date().toISOString() }));
118
+
119
+ // API routes
120
+ app.use('/api/v1', router);
121
+
122
+ // Error handling — must be last
123
+ app.use(notFound);
124
+ app.use(errorHandler);
125
+
126
+ export { app };
127
+ ```
128
+
129
+ ---
130
+
131
+ ## server.js
132
+
133
+ ```js
134
+ import { app } from './src/app.js';
135
+ import { prisma } from './src/config/db.js';
136
+ import { env } from './src/config/env.js';
137
+
138
+ const start = async () => {
139
+ try {
140
+ await prisma.$connect();
141
+ console.log('✅ Database connected');
142
+
143
+ const server = app.listen(env.PORT, () => {
144
+ console.log(`🚀 Server running on port ${env.PORT} [${env.NODE_ENV}]`);
145
+ });
146
+
147
+ // Graceful shutdown
148
+ const shutdown = async (signal) => {
149
+ console.log(`\n${signal} received — shutting down gracefully`);
150
+ server.close(async () => {
151
+ await prisma.$disconnect();
152
+ console.log('Database disconnected. Bye 👋');
153
+ process.exit(0);
154
+ });
155
+ };
156
+
157
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
158
+ process.on('SIGINT', () => shutdown('SIGINT'));
159
+
160
+ } catch (err) {
161
+ console.error('❌ Startup failed:', err);
162
+ await prisma.$disconnect();
163
+ process.exit(1);
164
+ }
165
+ };
166
+
167
+ start();
168
+ ```
169
+
170
+ ---
171
+
172
+ ## src/routes/index.js
173
+
174
+ ```js
175
+ import { Router } from 'express';
176
+ // import userRouter from '../modules/user/user.routes.js';
177
+ // import authRouter from '../modules/auth/auth.routes.js';
178
+
179
+ export const router = Router();
180
+
181
+ // router.use('/auth', authRouter);
182
+ // router.use('/users', userRouter);
183
+ ```
184
+
185
+ ---
186
+
187
+ ## .env.example
188
+
189
+ ```
190
+ NODE_ENV=development
191
+ PORT=3000
192
+ DATABASE_URL=postgresql://user:password@localhost:5432/dbname
193
+ JWT_SECRET=your-super-secret-key-at-least-32-characters-long
194
+ JWT_EXPIRES_IN=7d
195
+ CORS_ORIGIN=http://localhost:5173
196
+ ```
@@ -0,0 +1,121 @@
1
+ # Error Handling Reference
2
+
3
+ Express v5 + AppError pattern. Errors thrown anywhere in async route handlers automatically reach `errorHandler`.
4
+
5
+ ---
6
+
7
+ ## src/utils/AppError.js
8
+
9
+ ```js
10
+ export class AppError extends Error {
11
+ constructor(message, statusCode = 500, errors = null) {
12
+ super(message);
13
+ this.statusCode = statusCode;
14
+ this.status = statusCode >= 400 && statusCode < 500 ? 'fail' : 'error';
15
+ this.isOperational = true;
16
+ this.errors = errors; // optional: array of { field, message }
17
+ Error.captureStackTrace(this, this.constructor);
18
+ }
19
+ }
20
+ ```
21
+
22
+ ---
23
+
24
+ ## src/middlewares/errorHandler.js
25
+
26
+ Handles all error types in one place. Mount last in `app.js`.
27
+
28
+ ```js
29
+ import { AppError } from '../utils/AppError.js';
30
+ import { Prisma } from '@prisma/client';
31
+
32
+ export const errorHandler = (err, req, res, next) => {
33
+ let statusCode = err.statusCode || 500;
34
+ let message = err.message || 'Internal server error';
35
+ let errors = err.errors || null;
36
+
37
+ // Prisma known errors
38
+ if (err instanceof Prisma.PrismaClientKnownRequestError) {
39
+ switch (err.code) {
40
+ case 'P2002': statusCode = 409; message = `Duplicate: ${err.meta?.target}`; break;
41
+ case 'P2025': statusCode = 404; message = 'Record not found'; break;
42
+ case 'P2003': statusCode = 400; message = 'Related record not found'; break;
43
+ case 'P2014': statusCode = 400; message = 'Relation constraint failed'; break;
44
+ default: statusCode = 500; message = 'Database error';
45
+ }
46
+ }
47
+
48
+ // JWT errors
49
+ if (err.name === 'JsonWebTokenError') { statusCode = 401; message = 'Invalid token'; }
50
+ if (err.name === 'TokenExpiredError') { statusCode = 401; message = 'Token expired. Please log in again'; }
51
+
52
+ // Zod error (if validation middleware was bypassed)
53
+ if (err.name === 'ZodError') {
54
+ statusCode = 422;
55
+ message = 'Validation failed';
56
+ errors = err.issues.map((i) => ({ field: i.path.join('.'), message: i.message }));
57
+ }
58
+
59
+ const isDev = process.env.NODE_ENV === 'development';
60
+
61
+ res.status(statusCode).json({
62
+ success: false,
63
+ message,
64
+ ...(errors && { errors }),
65
+ ...(isDev && !err.isOperational && { stack: err.stack }),
66
+ });
67
+ };
68
+
69
+ export const notFound = (req, res, next) => {
70
+ next(new AppError(`Route not found: ${req.method} ${req.originalUrl}`, 404));
71
+ };
72
+ ```
73
+
74
+ ---
75
+
76
+ ## src/app.js — Error middleware order
77
+
78
+ ```js
79
+ // Routes
80
+ app.use('/api/v1', router);
81
+
82
+ // Must come AFTER routes
83
+ app.use(notFound);
84
+ app.use(errorHandler);
85
+ ```
86
+
87
+ ---
88
+
89
+ ## Express v5 — Async Error Propagation
90
+
91
+ Express v5 automatically catches rejected promises from async route handlers.
92
+ **No `catchAsync` / `asyncHandler` wrapper needed.**
93
+
94
+ ```js
95
+ // Service layer — throw for business errors
96
+ export const getById = async (id) => {
97
+ const item = await prisma.product.findUnique({ where: { id } });
98
+ if (!item) throw new AppError('Product not found', 404); // ← propagates automatically
99
+ return item;
100
+ };
101
+
102
+ // Controller — no try/catch
103
+ export const getById = async (req, res) => {
104
+ const item = await productService.getById(req.params.id); // throws → errorHandler
105
+ sendSuccess(res, 'Product fetched', item);
106
+ };
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Where Each Error Is Thrown
112
+
113
+ | Location | Pattern |
114
+ |----------|---------|
115
+ | Service — resource not found | `throw new AppError('X not found', 404)` |
116
+ | Service — conflict | `throw new AppError('Already exists', 409)` |
117
+ | Service — business rule | `throw new AppError('Insufficient balance', 400)` |
118
+ | Service — forbidden | `throw new AppError('Cannot perform this action', 403)` |
119
+ | Repository / service — Prisma | catch `PrismaClientKnownRequestError` → `throw new AppError(...)` |
120
+ | Middleware — auth | `return next(new AppError('No token', 401))` |
121
+ | Controller | **Never throw directly** — delegate to service |