@esreekarreddy/ai-prompts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +361 -0
- package/chains/_index.md +33 -0
- package/chains/bug-fix.md +222 -0
- package/chains/new-feature.md +216 -0
- package/chains/production-launch.md +291 -0
- package/chains/refactor.md +210 -0
- package/chains/security-hardening.md +242 -0
- package/contexts/guides/api-design.md +229 -0
- package/contexts/guides/error-handling.md +219 -0
- package/contexts/patterns/agentic-coding.md +368 -0
- package/contexts/patterns/mcp-server-patterns.md +267 -0
- package/contexts/patterns/repository-pattern.md +163 -0
- package/contexts/patterns/service-layer.md +185 -0
- package/contexts/stacks/fastapi.md +187 -0
- package/contexts/stacks/nextjs-14.md +149 -0
- package/contexts/stacks/prisma.md +228 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +284 -0
- package/dist/index.js.map +1 -0
- package/examples/architecture-docs/sample-architecture.md +270 -0
- package/examples/code-reviews/sample-review.md +232 -0
- package/examples/prds/sample-prd.md +179 -0
- package/instructions/_index.md +57 -0
- package/instructions/personas/code-reviewer.md +83 -0
- package/instructions/personas/devops-engineer.md +90 -0
- package/instructions/personas/security-expert.md +69 -0
- package/instructions/personas/senior-engineer.md +243 -0
- package/instructions/personas/ux-engineer.md +88 -0
- package/instructions/standards/fastapi.md +241 -0
- package/instructions/standards/go.md +427 -0
- package/instructions/standards/nextjs.md +350 -0
- package/instructions/standards/nodejs.md +284 -0
- package/instructions/standards/python.md +245 -0
- package/instructions/standards/react.md +227 -0
- package/instructions/standards/rust.md +318 -0
- package/instructions/standards/typescript-react.md +822 -0
- package/instructions/standards/typescript.md +294 -0
- package/instructions/workflows/feature-development.md +222 -0
- package/instructions/workflows/incident-response.md +192 -0
- package/instructions/workflows/pr-review.md +149 -0
- package/instructions/workflows/tdd.md +160 -0
- package/package.json +84 -0
- package/prompts/_index.md +70 -0
- package/prompts/agentic/agentic-loop.md +83 -0
- package/prompts/agentic/context-manager.md +37 -0
- package/prompts/agentic/test-driven-fix.md +41 -0
- package/prompts/analysis/deep-debugger.md +488 -0
- package/prompts/design/design-system-extractor.md +147 -0
- package/prompts/development/code-cleaner.md +119 -0
- package/prompts/development/debugger.md +64 -0
- package/prompts/development/tech-debt-audit.md +88 -0
- package/prompts/planning/architecture-analyzer.md +72 -0
- package/prompts/planning/implementation-plan.md +98 -0
- package/prompts/planning/prd-generator.md +66 -0
- package/prompts/planning/scope-killer.md +74 -0
- package/prompts/quality/critical-path-tester.md +133 -0
- package/prompts/quality/pre-launch-checklist.md +137 -0
- package/prompts/quality/security-audit.md +115 -0
- package/prompts/quality/security-fixer.md +117 -0
- package/prompts/quality/security-hardening.md +157 -0
- package/prompts/system/master-system-prompt.md +252 -0
- package/skills/_index.md +60 -0
- package/skills/code-review-advanced.md +435 -0
- package/skills/code-review.md +86 -0
- package/skills/debugging.md +86 -0
- package/skills/documentation.md +97 -0
- package/skills/pr-description.md +116 -0
- package/skills/project-setup.md +123 -0
- package/skills/refactoring.md +93 -0
- package/skills/testing.md +134 -0
- package/snippets/_index.md +57 -0
- package/snippets/constraints/mvp-only.md +50 -0
- package/snippets/constraints/no-external-deps.md +45 -0
- package/snippets/constraints/read-only.md +45 -0
- package/snippets/constraints/security-first.md +50 -0
- package/snippets/modifiers/be-ruthless.md +52 -0
- package/snippets/modifiers/be-thorough.md +50 -0
- package/snippets/modifiers/effort-high.md +56 -0
- package/snippets/modifiers/explain-reasoning.md +50 -0
- package/snippets/modifiers/megathink.md +314 -0
- package/snippets/modifiers/meta-cot.md +101 -0
- package/snippets/modifiers/no-code-yet.md +55 -0
- package/snippets/modifiers/step-by-step.md +50 -0
- package/snippets/modifiers/ultrathink.md +359 -0
- package/snippets/output-formats/checklist.md +61 -0
- package/snippets/output-formats/json.md +53 -0
- package/snippets/output-formats/markdown-table.md +44 -0
- package/snippets/output-formats/numbered-list.md +44 -0
- package/templates/_index.md +101 -0
- package/templates/claude-md/auto-enhance.md +258 -0
- package/templates/claude-md/cli-tool.md +243 -0
- package/templates/claude-md/full.md +449 -0
- package/templates/claude-md/minimal.md +52 -0
- package/templates/claude-md/nextjs-app.md +207 -0
- package/templates/claude-md/nodejs-service.md +251 -0
- package/templates/claude-md/python-api.md +236 -0
- package/templates/copilot/instructions.md +33 -0
- package/templates/cursor-rules/fullstack.txt +98 -0
- package/templates/cursor-rules/minimal.txt +20 -0
- package/templates/cursor-rules/nextjs.txt +61 -0
- package/templates/cursor-rules/python.txt +79 -0
- package/templates/docs/adr-template.md +119 -0
- package/templates/docs/api-spec-template.md +277 -0
- package/templates/docs/prd-template.md +140 -0
- package/templates/docs/runbook-template.md +238 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# Next.js Coding Standards
|
|
2
|
+
|
|
3
|
+
> Conventions for Next.js 15+ with App Router (January 2026)
|
|
4
|
+
|
|
5
|
+
## File Structure (Next.js 15)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
app/
|
|
9
|
+
├── (auth)/ # Route groups (no URL impact)
|
|
10
|
+
│ ├── login/
|
|
11
|
+
│ │ └── page.tsx
|
|
12
|
+
│ └── layout.tsx # Auth-specific layout
|
|
13
|
+
├── (dashboard)/
|
|
14
|
+
│ ├── dashboard/
|
|
15
|
+
│ │ ├── page.tsx
|
|
16
|
+
│ │ ├── loading.tsx # Streaming loading UI
|
|
17
|
+
│ │ └── error.tsx # Error boundary
|
|
18
|
+
│ └── layout.tsx
|
|
19
|
+
├── api/
|
|
20
|
+
│ └── users/
|
|
21
|
+
│ └── route.ts # API route
|
|
22
|
+
├── layout.tsx # Root layout
|
|
23
|
+
├── page.tsx # Home page
|
|
24
|
+
├── not-found.tsx # 404 page
|
|
25
|
+
└── globals.css
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Server vs Client Components (Default: Server)
|
|
29
|
+
|
|
30
|
+
### Server Components (No directive needed)
|
|
31
|
+
```tsx
|
|
32
|
+
// This is a Server Component by default
|
|
33
|
+
export default async function UsersPage() {
|
|
34
|
+
const users = await db.users.findMany();
|
|
35
|
+
return <UserList users={users} />;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Client Components (Only when required)
|
|
40
|
+
```tsx
|
|
41
|
+
'use client';
|
|
42
|
+
|
|
43
|
+
import { useState } from 'react';
|
|
44
|
+
|
|
45
|
+
export function Counter() {
|
|
46
|
+
const [count, setCount] = useState(0);
|
|
47
|
+
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### When to Use 'use client'
|
|
52
|
+
- `useState`, `useEffect`, `useRef`, etc.
|
|
53
|
+
- Event handlers (`onClick`, `onChange`)
|
|
54
|
+
- Browser APIs (`window`, `document`)
|
|
55
|
+
- Third-party client libraries (charts, maps)
|
|
56
|
+
|
|
57
|
+
## Data Fetching (Next.js 15)
|
|
58
|
+
|
|
59
|
+
### In Server Components (Direct async/await)
|
|
60
|
+
```tsx
|
|
61
|
+
export default async function Page() {
|
|
62
|
+
// Default: cached indefinitely
|
|
63
|
+
const data = await fetch('https://api.example.com/data');
|
|
64
|
+
|
|
65
|
+
// Revalidate every 60 seconds
|
|
66
|
+
const freshData = await fetch('https://api.example.com/data', {
|
|
67
|
+
next: { revalidate: 60 }
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Never cache (dynamic data)
|
|
71
|
+
const dynamicData = await fetch('https://api.example.com/data', {
|
|
72
|
+
cache: 'no-store'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return <Display data={data} />;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Database Queries (Direct in Server Components)
|
|
80
|
+
```tsx
|
|
81
|
+
import { db } from '@/lib/db';
|
|
82
|
+
|
|
83
|
+
export default async function UsersPage() {
|
|
84
|
+
// Direct database access - no API layer needed
|
|
85
|
+
const users = await db.users.findMany({
|
|
86
|
+
take: 10,
|
|
87
|
+
orderBy: { createdAt: 'desc' }
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return <UserList users={users} />;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Parallel Data Fetching
|
|
95
|
+
```tsx
|
|
96
|
+
export default async function Dashboard() {
|
|
97
|
+
// Parallel fetching - much faster
|
|
98
|
+
const [user, posts, analytics] = await Promise.all([
|
|
99
|
+
getUser(),
|
|
100
|
+
getPosts(),
|
|
101
|
+
getAnalytics()
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<UserCard user={user} />
|
|
107
|
+
<PostList posts={posts} />
|
|
108
|
+
<AnalyticsChart data={analytics} />
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Server Actions (Next.js 15)
|
|
115
|
+
|
|
116
|
+
### Defining Server Actions
|
|
117
|
+
```tsx
|
|
118
|
+
// lib/actions/users.ts
|
|
119
|
+
'use server';
|
|
120
|
+
|
|
121
|
+
import { revalidatePath } from 'next/cache';
|
|
122
|
+
import { redirect } from 'next/navigation';
|
|
123
|
+
import { z } from 'zod';
|
|
124
|
+
|
|
125
|
+
const CreateUserSchema = z.object({
|
|
126
|
+
name: z.string().min(1),
|
|
127
|
+
email: z.string().email(),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export async function createUser(formData: FormData) {
|
|
131
|
+
// Validate with Zod
|
|
132
|
+
const result = CreateUserSchema.safeParse({
|
|
133
|
+
name: formData.get('name'),
|
|
134
|
+
email: formData.get('email'),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!result.success) {
|
|
138
|
+
return { error: result.error.flatten().fieldErrors };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create user
|
|
142
|
+
await db.users.create({ data: result.data });
|
|
143
|
+
|
|
144
|
+
// Revalidate and redirect
|
|
145
|
+
revalidatePath('/users');
|
|
146
|
+
redirect('/users');
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Using Server Actions with Forms
|
|
151
|
+
```tsx
|
|
152
|
+
// Simple form
|
|
153
|
+
<form action={createUser}>
|
|
154
|
+
<input name="name" required />
|
|
155
|
+
<input name="email" type="email" required />
|
|
156
|
+
<button type="submit">Create</button>
|
|
157
|
+
</form>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### With useActionState (React 19)
|
|
161
|
+
```tsx
|
|
162
|
+
'use client';
|
|
163
|
+
import { useActionState } from 'react';
|
|
164
|
+
import { createUser } from '@/lib/actions/users';
|
|
165
|
+
|
|
166
|
+
export function CreateUserForm() {
|
|
167
|
+
const [state, action, isPending] = useActionState(createUser, null);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<form action={action}>
|
|
171
|
+
{state?.error && <p className="text-red-500">{state.error}</p>}
|
|
172
|
+
<input name="name" disabled={isPending} />
|
|
173
|
+
<input name="email" type="email" disabled={isPending} />
|
|
174
|
+
<button type="submit" disabled={isPending}>
|
|
175
|
+
{isPending ? 'Creating...' : 'Create'}
|
|
176
|
+
</button>
|
|
177
|
+
</form>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Optimistic Updates (React 19)
|
|
183
|
+
```tsx
|
|
184
|
+
'use client';
|
|
185
|
+
import { useOptimistic } from 'react';
|
|
186
|
+
|
|
187
|
+
export function TodoList({ todos, addTodo }) {
|
|
188
|
+
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
|
|
189
|
+
todos,
|
|
190
|
+
(state, newTodo) => [...state, { ...newTodo, pending: true }]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
async function handleSubmit(formData: FormData) {
|
|
194
|
+
const newTodo = { title: formData.get('title') as string };
|
|
195
|
+
addOptimisticTodo(newTodo);
|
|
196
|
+
await addTodo(newTodo);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<>
|
|
201
|
+
<form action={handleSubmit}>
|
|
202
|
+
<input name="title" />
|
|
203
|
+
<button>Add</button>
|
|
204
|
+
</form>
|
|
205
|
+
<ul>
|
|
206
|
+
{optimisticTodos.map(todo => (
|
|
207
|
+
<li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
|
|
208
|
+
{todo.title}
|
|
209
|
+
</li>
|
|
210
|
+
))}
|
|
211
|
+
</ul>
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Route Handlers (API Routes)
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// app/api/users/route.ts
|
|
221
|
+
import { NextResponse } from 'next/server';
|
|
222
|
+
import { z } from 'zod';
|
|
223
|
+
|
|
224
|
+
export async function GET(request: Request) {
|
|
225
|
+
const { searchParams } = new URL(request.url);
|
|
226
|
+
const limit = parseInt(searchParams.get('limit') || '10');
|
|
227
|
+
|
|
228
|
+
const users = await db.users.findMany({ take: limit });
|
|
229
|
+
return NextResponse.json(users);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function POST(request: Request) {
|
|
233
|
+
const body = await request.json();
|
|
234
|
+
|
|
235
|
+
// Validate
|
|
236
|
+
const result = UserSchema.safeParse(body);
|
|
237
|
+
if (!result.success) {
|
|
238
|
+
return NextResponse.json(
|
|
239
|
+
{ error: result.error.flatten() },
|
|
240
|
+
{ status: 400 }
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const user = await db.users.create({ data: result.data });
|
|
245
|
+
return NextResponse.json(user, { status: 201 });
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Streaming & Suspense
|
|
250
|
+
|
|
251
|
+
### Loading UI (Automatic Suspense)
|
|
252
|
+
```tsx
|
|
253
|
+
// app/dashboard/loading.tsx
|
|
254
|
+
export default function Loading() {
|
|
255
|
+
return <DashboardSkeleton />;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Manual Suspense Boundaries
|
|
260
|
+
```tsx
|
|
261
|
+
import { Suspense } from 'react';
|
|
262
|
+
|
|
263
|
+
export default function Page() {
|
|
264
|
+
return (
|
|
265
|
+
<>
|
|
266
|
+
<Header />
|
|
267
|
+
<Suspense fallback={<PostsSkeleton />}>
|
|
268
|
+
<Posts />
|
|
269
|
+
</Suspense>
|
|
270
|
+
<Suspense fallback={<CommentsSkeleton />}>
|
|
271
|
+
<Comments />
|
|
272
|
+
</Suspense>
|
|
273
|
+
</>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Error Handling
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
// app/dashboard/error.tsx
|
|
282
|
+
'use client';
|
|
283
|
+
|
|
284
|
+
export default function Error({
|
|
285
|
+
error,
|
|
286
|
+
reset,
|
|
287
|
+
}: {
|
|
288
|
+
error: Error & { digest?: string };
|
|
289
|
+
reset: () => void;
|
|
290
|
+
}) {
|
|
291
|
+
return (
|
|
292
|
+
<div className="p-4 bg-red-50 rounded">
|
|
293
|
+
<h2 className="text-red-800 font-bold">Something went wrong!</h2>
|
|
294
|
+
<p className="text-red-600">{error.message}</p>
|
|
295
|
+
<button
|
|
296
|
+
onClick={reset}
|
|
297
|
+
className="mt-2 px-4 py-2 bg-red-600 text-white rounded"
|
|
298
|
+
>
|
|
299
|
+
Try again
|
|
300
|
+
</button>
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Metadata (SEO)
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
// Static metadata
|
|
310
|
+
export const metadata = {
|
|
311
|
+
title: 'Page Title',
|
|
312
|
+
description: 'Page description',
|
|
313
|
+
openGraph: {
|
|
314
|
+
title: 'OG Title',
|
|
315
|
+
description: 'OG Description',
|
|
316
|
+
images: ['/og-image.png'],
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Dynamic metadata
|
|
321
|
+
export async function generateMetadata({ params }) {
|
|
322
|
+
const product = await getProduct(params.id);
|
|
323
|
+
return {
|
|
324
|
+
title: product.name,
|
|
325
|
+
description: product.description,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Patterns to Avoid (Anti-patterns)
|
|
331
|
+
|
|
332
|
+
| ❌ Avoid | ✅ Instead |
|
|
333
|
+
|----------|-----------|
|
|
334
|
+
| `useEffect` for data fetching | Fetch in Server Components |
|
|
335
|
+
| `'use client'` by default | Server Components by default |
|
|
336
|
+
| Deep prop drilling | Server Components with direct data |
|
|
337
|
+
| `getServerSideProps` | Server Components or Route Handlers |
|
|
338
|
+
| API routes for internal data | Server Actions or direct DB access |
|
|
339
|
+
| `useState` for server data | Server Components + Suspense |
|
|
340
|
+
| Manual loading states | `loading.tsx` + Suspense |
|
|
341
|
+
|
|
342
|
+
## Performance Checklist
|
|
343
|
+
|
|
344
|
+
- [ ] Use Server Components by default
|
|
345
|
+
- [ ] Parallel data fetching with `Promise.all`
|
|
346
|
+
- [ ] Streaming with Suspense for slow data
|
|
347
|
+
- [ ] Image optimization with `next/image`
|
|
348
|
+
- [ ] Font optimization with `next/font`
|
|
349
|
+
- [ ] Route prefetching enabled (default)
|
|
350
|
+
- [ ] Static generation where possible
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Node.js Coding Standards
|
|
2
|
+
|
|
3
|
+
> Conventions for Node.js/Express backend services
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── index.ts # Entry point
|
|
10
|
+
├── app.ts # Express app setup
|
|
11
|
+
├── config/
|
|
12
|
+
│ ├── env.ts # Environment variables
|
|
13
|
+
│ └── database.ts # DB configuration
|
|
14
|
+
├── routes/
|
|
15
|
+
│ ├── index.ts # Route aggregator
|
|
16
|
+
│ └── users.ts # User routes
|
|
17
|
+
├── controllers/
|
|
18
|
+
│ └── users.controller.ts
|
|
19
|
+
├── services/
|
|
20
|
+
│ └── users.service.ts
|
|
21
|
+
├── repositories/
|
|
22
|
+
│ └── users.repository.ts
|
|
23
|
+
├── middleware/
|
|
24
|
+
│ ├── auth.ts
|
|
25
|
+
│ ├── error-handler.ts
|
|
26
|
+
│ └── validate.ts
|
|
27
|
+
├── schemas/ # Zod schemas
|
|
28
|
+
│ └── user.schema.ts
|
|
29
|
+
├── types/
|
|
30
|
+
│ └── index.ts
|
|
31
|
+
└── utils/
|
|
32
|
+
├── logger.ts
|
|
33
|
+
└── errors.ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Express App Setup
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// src/app.ts
|
|
40
|
+
import express from 'express';
|
|
41
|
+
import cors from 'cors';
|
|
42
|
+
import helmet from 'helmet';
|
|
43
|
+
import { errorHandler } from './middleware/error-handler';
|
|
44
|
+
import routes from './routes';
|
|
45
|
+
|
|
46
|
+
const app = express();
|
|
47
|
+
|
|
48
|
+
// Middleware
|
|
49
|
+
app.use(helmet());
|
|
50
|
+
app.use(cors());
|
|
51
|
+
app.use(express.json());
|
|
52
|
+
|
|
53
|
+
// Routes
|
|
54
|
+
app.use('/api', routes);
|
|
55
|
+
|
|
56
|
+
// Error handler (must be last)
|
|
57
|
+
app.use(errorHandler);
|
|
58
|
+
|
|
59
|
+
export default app;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Routes
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// src/routes/users.ts
|
|
66
|
+
import { Router } from 'express';
|
|
67
|
+
import * as controller from '../controllers/users.controller';
|
|
68
|
+
import { validate } from '../middleware/validate';
|
|
69
|
+
import { createUserSchema, updateUserSchema } from '../schemas/user.schema';
|
|
70
|
+
import { authenticate } from '../middleware/auth';
|
|
71
|
+
|
|
72
|
+
const router = Router();
|
|
73
|
+
|
|
74
|
+
router.get('/', authenticate, controller.list);
|
|
75
|
+
router.get('/:id', authenticate, controller.get);
|
|
76
|
+
router.post('/', validate(createUserSchema), controller.create);
|
|
77
|
+
router.patch('/:id', authenticate, validate(updateUserSchema), controller.update);
|
|
78
|
+
router.delete('/:id', authenticate, controller.remove);
|
|
79
|
+
|
|
80
|
+
export default router;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Controllers
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// src/controllers/users.controller.ts
|
|
87
|
+
import { Request, Response, NextFunction } from 'express';
|
|
88
|
+
import * as userService from '../services/users.service';
|
|
89
|
+
|
|
90
|
+
export async function list(req: Request, res: Response, next: NextFunction) {
|
|
91
|
+
try {
|
|
92
|
+
const users = await userService.findAll();
|
|
93
|
+
res.json(users);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
next(error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function get(req: Request, res: Response, next: NextFunction) {
|
|
100
|
+
try {
|
|
101
|
+
const { id } = req.params;
|
|
102
|
+
const user = await userService.findById(id);
|
|
103
|
+
if (!user) {
|
|
104
|
+
return res.status(404).json({ error: 'User not found' });
|
|
105
|
+
}
|
|
106
|
+
res.json(user);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
next(error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function create(req: Request, res: Response, next: NextFunction) {
|
|
113
|
+
try {
|
|
114
|
+
const user = await userService.create(req.body);
|
|
115
|
+
res.status(201).json(user);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
next(error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Services
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// src/services/users.service.ts
|
|
126
|
+
import * as userRepo from '../repositories/users.repository';
|
|
127
|
+
import { CreateUserInput, UpdateUserInput } from '../schemas/user.schema';
|
|
128
|
+
import { AppError } from '../utils/errors';
|
|
129
|
+
import { hashPassword } from '../utils/auth';
|
|
130
|
+
|
|
131
|
+
export async function findAll() {
|
|
132
|
+
return userRepo.findMany();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function findById(id: string) {
|
|
136
|
+
return userRepo.findById(id);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function create(data: CreateUserInput) {
|
|
140
|
+
const existing = await userRepo.findByEmail(data.email);
|
|
141
|
+
if (existing) {
|
|
142
|
+
throw new AppError('Email already exists', 409);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const hashedPassword = await hashPassword(data.password);
|
|
146
|
+
return userRepo.create({
|
|
147
|
+
...data,
|
|
148
|
+
password: hashedPassword
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Validation with Zod
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// src/schemas/user.schema.ts
|
|
157
|
+
import { z } from 'zod';
|
|
158
|
+
|
|
159
|
+
export const createUserSchema = z.object({
|
|
160
|
+
body: z.object({
|
|
161
|
+
email: z.string().email('Invalid email'),
|
|
162
|
+
name: z.string().min(2, 'Name too short').max(100),
|
|
163
|
+
password: z.string().min(8, 'Password must be at least 8 characters')
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export const updateUserSchema = z.object({
|
|
168
|
+
body: z.object({
|
|
169
|
+
name: z.string().min(2).max(100).optional(),
|
|
170
|
+
email: z.string().email().optional()
|
|
171
|
+
}),
|
|
172
|
+
params: z.object({
|
|
173
|
+
id: z.string()
|
|
174
|
+
})
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export type CreateUserInput = z.infer<typeof createUserSchema>['body'];
|
|
178
|
+
export type UpdateUserInput = z.infer<typeof updateUserSchema>['body'];
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Validation Middleware
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// src/middleware/validate.ts
|
|
185
|
+
import { Request, Response, NextFunction } from 'express';
|
|
186
|
+
import { ZodSchema } from 'zod';
|
|
187
|
+
|
|
188
|
+
export function validate(schema: ZodSchema) {
|
|
189
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
190
|
+
const result = schema.safeParse({
|
|
191
|
+
body: req.body,
|
|
192
|
+
query: req.query,
|
|
193
|
+
params: req.params
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!result.success) {
|
|
197
|
+
return res.status(400).json({
|
|
198
|
+
error: 'Validation failed',
|
|
199
|
+
details: result.error.issues
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
next();
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Error Handling
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// src/utils/errors.ts
|
|
212
|
+
export class AppError extends Error {
|
|
213
|
+
constructor(
|
|
214
|
+
message: string,
|
|
215
|
+
public statusCode: number = 500,
|
|
216
|
+
public code?: string
|
|
217
|
+
) {
|
|
218
|
+
super(message);
|
|
219
|
+
this.name = 'AppError';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export class NotFoundError extends AppError {
|
|
224
|
+
constructor(resource: string) {
|
|
225
|
+
super(`${resource} not found`, 404, 'NOT_FOUND');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/middleware/error-handler.ts
|
|
230
|
+
import { Request, Response, NextFunction } from 'express';
|
|
231
|
+
import { AppError } from '../utils/errors';
|
|
232
|
+
import { logger } from '../utils/logger';
|
|
233
|
+
|
|
234
|
+
export function errorHandler(
|
|
235
|
+
err: Error,
|
|
236
|
+
req: Request,
|
|
237
|
+
res: Response,
|
|
238
|
+
next: NextFunction
|
|
239
|
+
) {
|
|
240
|
+
if (err instanceof AppError) {
|
|
241
|
+
return res.status(err.statusCode).json({
|
|
242
|
+
error: { message: err.message, code: err.code }
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
logger.error('Unhandled error', { error: err, path: req.path });
|
|
247
|
+
res.status(500).json({ error: { message: 'Internal server error' } });
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Configuration
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// src/config/env.ts
|
|
255
|
+
import { z } from 'zod';
|
|
256
|
+
|
|
257
|
+
const envSchema = z.object({
|
|
258
|
+
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
259
|
+
PORT: z.coerce.number().default(3000),
|
|
260
|
+
DATABASE_URL: z.string(),
|
|
261
|
+
JWT_SECRET: z.string()
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
export const env = envSchema.parse(process.env);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Async Handler Wrapper
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Avoid try/catch in every controller
|
|
271
|
+
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
272
|
+
|
|
273
|
+
export function asyncHandler(fn: RequestHandler): RequestHandler {
|
|
274
|
+
return (req, res, next) => {
|
|
275
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Usage
|
|
280
|
+
router.get('/', asyncHandler(async (req, res) => {
|
|
281
|
+
const users = await userService.findAll();
|
|
282
|
+
res.json(users);
|
|
283
|
+
}));
|
|
284
|
+
```
|