agentic-team-templates 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Database Patterns
|
|
2
|
+
|
|
3
|
+
Best practices for working with databases in backend applications.
|
|
4
|
+
|
|
5
|
+
## General Principles
|
|
6
|
+
|
|
7
|
+
### 1. Use a Data Access Layer
|
|
8
|
+
|
|
9
|
+
Separate database operations from business logic.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// repository/userRepository.ts
|
|
13
|
+
export const userRepository = {
|
|
14
|
+
async findById(id: string): Promise<User | null> {
|
|
15
|
+
return db.user.findUnique({ where: { id } });
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
async create(data: CreateUserInput): Promise<User> {
|
|
19
|
+
return db.user.create({ data });
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async update(id: string, data: UpdateUserInput): Promise<User> {
|
|
23
|
+
return db.user.update({ where: { id }, data });
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// services/userService.ts
|
|
28
|
+
export const createUser = async (input: CreateUserInput) => {
|
|
29
|
+
// Business logic here
|
|
30
|
+
const validated = validateUserInput(input);
|
|
31
|
+
return userRepository.create(validated);
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Use Transactions for Related Operations
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// Good: Atomic operation
|
|
39
|
+
await db.$transaction(async (tx) => {
|
|
40
|
+
const order = await tx.order.create({ data: orderData });
|
|
41
|
+
await tx.inventory.update({
|
|
42
|
+
where: { productId: order.productId },
|
|
43
|
+
data: { quantity: { decrement: order.quantity } },
|
|
44
|
+
});
|
|
45
|
+
await tx.payment.create({ data: { orderId: order.id, ...paymentData } });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Bad: Non-atomic, can leave inconsistent state
|
|
49
|
+
const order = await db.order.create({ data: orderData });
|
|
50
|
+
await db.inventory.update({ ... }); // What if this fails?
|
|
51
|
+
await db.payment.create({ ... });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Handle Connection Errors
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const connectWithRetry = async (maxRetries = 5) => {
|
|
58
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
59
|
+
try {
|
|
60
|
+
await db.$connect();
|
|
61
|
+
return;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (i === maxRetries - 1) throw error;
|
|
64
|
+
await sleep(Math.pow(2, i) * 1000); // Exponential backoff
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Query Optimization
|
|
71
|
+
|
|
72
|
+
### Select Only Needed Fields
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// Good: Select specific fields
|
|
76
|
+
const users = await db.user.findMany({
|
|
77
|
+
select: { id: true, name: true, email: true },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Bad: Select everything when you don't need it
|
|
81
|
+
const users = await db.user.findMany(); // Returns all fields including large blobs
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Use Pagination
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// Offset-based (simple, but slow for large offsets)
|
|
88
|
+
const users = await db.user.findMany({
|
|
89
|
+
skip: (page - 1) * limit,
|
|
90
|
+
take: limit,
|
|
91
|
+
orderBy: { createdAt: 'desc' },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Cursor-based (better for large datasets)
|
|
95
|
+
const users = await db.user.findMany({
|
|
96
|
+
take: limit,
|
|
97
|
+
cursor: cursor ? { id: cursor } : undefined,
|
|
98
|
+
orderBy: { id: 'asc' },
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Avoid N+1 Queries
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// Bad: N+1 queries
|
|
106
|
+
const posts = await db.post.findMany();
|
|
107
|
+
for (const post of posts) {
|
|
108
|
+
post.author = await db.user.findUnique({ where: { id: post.authorId } });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Good: Single query with include
|
|
112
|
+
const posts = await db.post.findMany({
|
|
113
|
+
include: { author: true },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Good: Batch query
|
|
117
|
+
const posts = await db.post.findMany();
|
|
118
|
+
const authorIds = [...new Set(posts.map(p => p.authorId))];
|
|
119
|
+
const authors = await db.user.findMany({ where: { id: { in: authorIds } } });
|
|
120
|
+
const authorMap = new Map(authors.map(a => [a.id, a]));
|
|
121
|
+
posts.forEach(p => p.author = authorMap.get(p.authorId));
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Use Indexes
|
|
125
|
+
|
|
126
|
+
```sql
|
|
127
|
+
-- Create indexes for frequently queried columns
|
|
128
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
129
|
+
CREATE INDEX idx_posts_user_created ON posts(user_id, created_at);
|
|
130
|
+
|
|
131
|
+
-- Composite index for common query patterns
|
|
132
|
+
CREATE INDEX idx_orders_status_date ON orders(status, created_at);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Migrations
|
|
136
|
+
|
|
137
|
+
### Version Control Migrations
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
migrations/
|
|
141
|
+
├── 001_create_users_table.sql
|
|
142
|
+
├── 002_add_email_index.sql
|
|
143
|
+
├── 003_create_posts_table.sql
|
|
144
|
+
└── 004_add_user_avatar.sql
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Write Reversible Migrations
|
|
148
|
+
|
|
149
|
+
```sql
|
|
150
|
+
-- migrations/003_create_posts_table.sql
|
|
151
|
+
|
|
152
|
+
-- Up
|
|
153
|
+
CREATE TABLE posts (
|
|
154
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
155
|
+
title VARCHAR(255) NOT NULL,
|
|
156
|
+
content TEXT,
|
|
157
|
+
user_id UUID REFERENCES users(id),
|
|
158
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
-- Down
|
|
162
|
+
DROP TABLE posts;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Test Migrations
|
|
166
|
+
|
|
167
|
+
- Test in staging before production
|
|
168
|
+
- Have a rollback plan
|
|
169
|
+
- Consider zero-downtime migrations for large tables
|
|
170
|
+
|
|
171
|
+
## Connection Pooling
|
|
172
|
+
|
|
173
|
+
### Configure Pool Size
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// Match pool size to expected concurrency
|
|
177
|
+
const pool = new Pool({
|
|
178
|
+
host: process.env.DB_HOST,
|
|
179
|
+
max: 20, // Max connections
|
|
180
|
+
idleTimeoutMillis: 30000,
|
|
181
|
+
connectionTimeoutMillis: 2000,
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Handle Pool Exhaustion
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// Set appropriate timeouts
|
|
189
|
+
const result = await Promise.race([
|
|
190
|
+
db.query(sql),
|
|
191
|
+
new Promise((_, reject) =>
|
|
192
|
+
setTimeout(() => reject(new Error('Query timeout')), 5000)
|
|
193
|
+
),
|
|
194
|
+
]);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Data Integrity
|
|
198
|
+
|
|
199
|
+
### Use Constraints
|
|
200
|
+
|
|
201
|
+
```sql
|
|
202
|
+
-- Primary keys
|
|
203
|
+
PRIMARY KEY (id)
|
|
204
|
+
|
|
205
|
+
-- Foreign keys with appropriate actions
|
|
206
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
207
|
+
|
|
208
|
+
-- Unique constraints
|
|
209
|
+
UNIQUE (email)
|
|
210
|
+
|
|
211
|
+
-- Check constraints
|
|
212
|
+
CHECK (price >= 0)
|
|
213
|
+
CHECK (status IN ('pending', 'active', 'completed'))
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Use Appropriate Data Types
|
|
217
|
+
|
|
218
|
+
```sql
|
|
219
|
+
-- Good: Appropriate types
|
|
220
|
+
id UUID PRIMARY KEY
|
|
221
|
+
email VARCHAR(255) NOT NULL
|
|
222
|
+
price DECIMAL(10, 2)
|
|
223
|
+
created_at TIMESTAMP WITH TIME ZONE
|
|
224
|
+
|
|
225
|
+
-- Bad: Wrong types
|
|
226
|
+
id TEXT
|
|
227
|
+
price FLOAT -- Precision issues
|
|
228
|
+
created_at VARCHAR(50)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Soft Deletes (When Needed)
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
// Schema
|
|
235
|
+
model User {
|
|
236
|
+
id String @id
|
|
237
|
+
email String @unique
|
|
238
|
+
deletedAt DateTime?
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Query active records
|
|
242
|
+
const activeUsers = await db.user.findMany({
|
|
243
|
+
where: { deletedAt: null },
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Soft delete
|
|
247
|
+
await db.user.update({
|
|
248
|
+
where: { id },
|
|
249
|
+
data: { deletedAt: new Date() },
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Security
|
|
254
|
+
|
|
255
|
+
### Parameterized Queries
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
// Good: Parameterized
|
|
259
|
+
const user = await db.query(
|
|
260
|
+
'SELECT * FROM users WHERE id = $1',
|
|
261
|
+
[userId]
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Bad: SQL injection vulnerability
|
|
265
|
+
const user = await db.query(
|
|
266
|
+
`SELECT * FROM users WHERE id = '${userId}'`
|
|
267
|
+
);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Encrypt Sensitive Data
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
// Encrypt at rest
|
|
274
|
+
const encrypted = encrypt(sensitiveData, encryptionKey);
|
|
275
|
+
await db.user.update({
|
|
276
|
+
where: { id },
|
|
277
|
+
data: { ssn: encrypted },
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Decrypt when needed
|
|
281
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
282
|
+
const ssn = decrypt(user.ssn, encryptionKey);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Audit Logging
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
// Track changes for sensitive operations
|
|
289
|
+
await db.auditLog.create({
|
|
290
|
+
data: {
|
|
291
|
+
action: 'USER_UPDATE',
|
|
292
|
+
userId: currentUser.id,
|
|
293
|
+
targetId: targetUser.id,
|
|
294
|
+
changes: JSON.stringify(diff(before, after)),
|
|
295
|
+
timestamp: new Date(),
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
```
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Best practices for handling errors in backend applications.
|
|
4
|
+
|
|
5
|
+
## Principles
|
|
6
|
+
|
|
7
|
+
### 1. Fail Fast
|
|
8
|
+
Detect errors early and fail immediately with clear diagnostics.
|
|
9
|
+
|
|
10
|
+
### 2. Fail Gracefully
|
|
11
|
+
Users should receive helpful messages, not stack traces.
|
|
12
|
+
|
|
13
|
+
### 3. Log Everything
|
|
14
|
+
Errors need context for debugging. Log appropriately.
|
|
15
|
+
|
|
16
|
+
### 4. Recover When Possible
|
|
17
|
+
Some errors are transient. Implement retry logic where appropriate.
|
|
18
|
+
|
|
19
|
+
## Error Types
|
|
20
|
+
|
|
21
|
+
### Custom Error Classes
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// Base application error
|
|
25
|
+
class AppError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
public statusCode: number,
|
|
28
|
+
public code: string,
|
|
29
|
+
message: string,
|
|
30
|
+
public details?: unknown
|
|
31
|
+
) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'AppError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Specific error types
|
|
38
|
+
class ValidationError extends AppError {
|
|
39
|
+
constructor(message: string, details?: ValidationDetail[]) {
|
|
40
|
+
super(422, 'VALIDATION_ERROR', message, details);
|
|
41
|
+
this.name = 'ValidationError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class NotFoundError extends AppError {
|
|
46
|
+
constructor(resource: string, id: string) {
|
|
47
|
+
super(404, 'NOT_FOUND', `${resource} with id ${id} not found`);
|
|
48
|
+
this.name = 'NotFoundError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class UnauthorizedError extends AppError {
|
|
53
|
+
constructor(message = 'Authentication required') {
|
|
54
|
+
super(401, 'UNAUTHORIZED', message);
|
|
55
|
+
this.name = 'UnauthorizedError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class ForbiddenError extends AppError {
|
|
60
|
+
constructor(message = 'Access denied') {
|
|
61
|
+
super(403, 'FORBIDDEN', message);
|
|
62
|
+
this.name = 'ForbiddenError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class ConflictError extends AppError {
|
|
67
|
+
constructor(message: string) {
|
|
68
|
+
super(409, 'CONFLICT', message);
|
|
69
|
+
this.name = 'ConflictError';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Result Types
|
|
75
|
+
|
|
76
|
+
Use Result types for explicit error handling:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
type Result<T, E = Error> =
|
|
80
|
+
| { ok: true; value: T }
|
|
81
|
+
| { ok: false; error: E };
|
|
82
|
+
|
|
83
|
+
const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
|
|
84
|
+
const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
|
|
85
|
+
|
|
86
|
+
// Usage
|
|
87
|
+
const findUser = async (id: string): Promise<Result<User, NotFoundError>> => {
|
|
88
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
89
|
+
|
|
90
|
+
if (!user) {
|
|
91
|
+
return err(new NotFoundError('User', id));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return ok(user);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Handling
|
|
98
|
+
const result = await findUser(id);
|
|
99
|
+
if (!result.ok) {
|
|
100
|
+
return res.status(result.error.statusCode).json({
|
|
101
|
+
error: { code: result.error.code, message: result.error.message }
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const user = result.value;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Global Error Handler
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// Centralized error handling middleware
|
|
111
|
+
const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
|
|
112
|
+
// Log error with context
|
|
113
|
+
logger.error({
|
|
114
|
+
error: err.message,
|
|
115
|
+
stack: err.stack,
|
|
116
|
+
requestId: req.id,
|
|
117
|
+
method: req.method,
|
|
118
|
+
path: req.path,
|
|
119
|
+
userId: req.user?.id,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Handle known application errors
|
|
123
|
+
if (err instanceof AppError) {
|
|
124
|
+
return res.status(err.statusCode).json({
|
|
125
|
+
error: {
|
|
126
|
+
code: err.code,
|
|
127
|
+
message: err.message,
|
|
128
|
+
details: err.details,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Handle validation errors (e.g., from Zod)
|
|
134
|
+
if (err.name === 'ZodError') {
|
|
135
|
+
return res.status(422).json({
|
|
136
|
+
error: {
|
|
137
|
+
code: 'VALIDATION_ERROR',
|
|
138
|
+
message: 'Validation failed',
|
|
139
|
+
details: err.errors,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle unknown errors (don't leak details)
|
|
145
|
+
res.status(500).json({
|
|
146
|
+
error: {
|
|
147
|
+
code: 'INTERNAL_ERROR',
|
|
148
|
+
message: 'An unexpected error occurred',
|
|
149
|
+
requestId: req.id, // For support reference
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Register at end of middleware chain
|
|
155
|
+
app.use(errorHandler);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Async Error Handling
|
|
159
|
+
|
|
160
|
+
### Wrap Async Handlers
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// Utility to catch async errors
|
|
164
|
+
const asyncHandler = (fn: RequestHandler): RequestHandler => {
|
|
165
|
+
return (req, res, next) => {
|
|
166
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Usage
|
|
171
|
+
app.get('/users/:id', asyncHandler(async (req, res) => {
|
|
172
|
+
const user = await userService.findById(req.params.id);
|
|
173
|
+
res.json({ data: user });
|
|
174
|
+
}));
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Or Use Try-Catch
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
app.get('/users/:id', async (req, res, next) => {
|
|
181
|
+
try {
|
|
182
|
+
const user = await userService.findById(req.params.id);
|
|
183
|
+
res.json({ data: user });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
next(error);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Error Responses
|
|
191
|
+
|
|
192
|
+
### Consistent Format
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
interface ErrorResponse {
|
|
196
|
+
error: {
|
|
197
|
+
code: string; // Machine-readable code
|
|
198
|
+
message: string; // Human-readable message
|
|
199
|
+
details?: unknown; // Additional context
|
|
200
|
+
requestId?: string; // For support/debugging
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Examples
|
|
205
|
+
// 400 Bad Request
|
|
206
|
+
{
|
|
207
|
+
"error": {
|
|
208
|
+
"code": "BAD_REQUEST",
|
|
209
|
+
"message": "Invalid JSON in request body"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 422 Validation Error
|
|
214
|
+
{
|
|
215
|
+
"error": {
|
|
216
|
+
"code": "VALIDATION_ERROR",
|
|
217
|
+
"message": "Validation failed",
|
|
218
|
+
"details": [
|
|
219
|
+
{ "field": "email", "message": "Invalid email format" },
|
|
220
|
+
{ "field": "age", "message": "Must be a positive number" }
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 500 Internal Error
|
|
226
|
+
{
|
|
227
|
+
"error": {
|
|
228
|
+
"code": "INTERNAL_ERROR",
|
|
229
|
+
"message": "An unexpected error occurred",
|
|
230
|
+
"requestId": "req_abc123"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Retry Logic
|
|
236
|
+
|
|
237
|
+
For transient failures:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const retry = async <T>(
|
|
241
|
+
fn: () => Promise<T>,
|
|
242
|
+
options: { maxAttempts: number; delay: number; backoff?: number }
|
|
243
|
+
): Promise<T> => {
|
|
244
|
+
const { maxAttempts, delay, backoff = 2 } = options;
|
|
245
|
+
|
|
246
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
247
|
+
try {
|
|
248
|
+
return await fn();
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (attempt === maxAttempts) throw error;
|
|
251
|
+
|
|
252
|
+
const waitTime = delay * Math.pow(backoff, attempt - 1);
|
|
253
|
+
await sleep(waitTime);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
throw new Error('Retry failed'); // Unreachable but TypeScript needs it
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Usage
|
|
261
|
+
const result = await retry(
|
|
262
|
+
() => externalApi.call(),
|
|
263
|
+
{ maxAttempts: 3, delay: 1000 }
|
|
264
|
+
);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Logging Best Practices
|
|
268
|
+
|
|
269
|
+
### What to Log
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
// Log errors with context
|
|
273
|
+
logger.error({
|
|
274
|
+
message: 'Failed to process payment',
|
|
275
|
+
error: error.message,
|
|
276
|
+
stack: error.stack,
|
|
277
|
+
paymentId: payment.id,
|
|
278
|
+
userId: user.id,
|
|
279
|
+
amount: payment.amount,
|
|
280
|
+
// Never log: card numbers, passwords, tokens
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Log important events
|
|
284
|
+
logger.info({
|
|
285
|
+
message: 'Payment processed',
|
|
286
|
+
paymentId: payment.id,
|
|
287
|
+
userId: user.id,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Log warnings for concerning but non-critical issues
|
|
291
|
+
logger.warn({
|
|
292
|
+
message: 'Rate limit approaching',
|
|
293
|
+
userId: user.id,
|
|
294
|
+
remaining: 5,
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### What NOT to Log
|
|
299
|
+
|
|
300
|
+
- Passwords and credentials
|
|
301
|
+
- API keys and tokens
|
|
302
|
+
- Credit card numbers
|
|
303
|
+
- Personal identification numbers
|
|
304
|
+
- Full request/response bodies with sensitive data
|
|
305
|
+
|
|
306
|
+
## Anti-Patterns
|
|
307
|
+
|
|
308
|
+
### Swallowing Errors
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
// Bad: Silent failure
|
|
312
|
+
try {
|
|
313
|
+
await riskyOperation();
|
|
314
|
+
} catch (e) {
|
|
315
|
+
// Nothing happens
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Good: Handle or rethrow
|
|
319
|
+
try {
|
|
320
|
+
await riskyOperation();
|
|
321
|
+
} catch (e) {
|
|
322
|
+
logger.error('Risky operation failed', e);
|
|
323
|
+
throw new AppError(500, 'OPERATION_FAILED', 'Could not complete operation');
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Leaking Implementation Details
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
// Bad: Exposes internals
|
|
331
|
+
res.status(500).json({ error: error.stack });
|
|
332
|
+
|
|
333
|
+
// Good: Generic message
|
|
334
|
+
res.status(500).json({
|
|
335
|
+
error: { code: 'INTERNAL_ERROR', message: 'An error occurred' }
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Catching Too Broadly
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// Bad: Catches everything including programming errors
|
|
343
|
+
try {
|
|
344
|
+
const data = processInput(input);
|
|
345
|
+
await saveToDb(data);
|
|
346
|
+
sendNotification(data);
|
|
347
|
+
} catch (e) {
|
|
348
|
+
res.status(400).json({ error: 'Bad request' });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Good: Catch specific errors
|
|
352
|
+
try {
|
|
353
|
+
const data = processInput(input);
|
|
354
|
+
await saveToDb(data);
|
|
355
|
+
sendNotification(data);
|
|
356
|
+
} catch (e) {
|
|
357
|
+
if (e instanceof ValidationError) {
|
|
358
|
+
res.status(422).json({ error: e.message });
|
|
359
|
+
} else if (e instanceof DatabaseError) {
|
|
360
|
+
logger.error('Database error', e);
|
|
361
|
+
res.status(500).json({ error: 'Database error' });
|
|
362
|
+
} else {
|
|
363
|
+
throw e; // Let unknown errors bubble up
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Web Backend Development
|
|
2
|
+
|
|
3
|
+
Guidelines for building robust backend APIs and services.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
This ruleset applies to:
|
|
8
|
+
- RESTful APIs
|
|
9
|
+
- GraphQL APIs
|
|
10
|
+
- Microservices
|
|
11
|
+
- Backend-for-frontend (BFF) services
|
|
12
|
+
- Server-side web applications
|
|
13
|
+
|
|
14
|
+
## Core Technologies
|
|
15
|
+
|
|
16
|
+
Backend development typically involves:
|
|
17
|
+
- Server frameworks (Express, Fastify, Koa, Hono, etc.)
|
|
18
|
+
- Database systems (PostgreSQL, MySQL, MongoDB, Redis, etc.)
|
|
19
|
+
- Authentication/Authorization (JWT, OAuth, sessions)
|
|
20
|
+
- API documentation (OpenAPI/Swagger)
|
|
21
|
+
- Testing frameworks
|
|
22
|
+
|
|
23
|
+
## Key Principles
|
|
24
|
+
|
|
25
|
+
### 1. Security First
|
|
26
|
+
Every input is hostile until validated. Defense in depth.
|
|
27
|
+
|
|
28
|
+
### 2. Reliability
|
|
29
|
+
Handle errors gracefully. Fail fast with clear diagnostics.
|
|
30
|
+
|
|
31
|
+
### 3. Observability
|
|
32
|
+
Log meaningfully. Track metrics. Enable debugging.
|
|
33
|
+
|
|
34
|
+
### 4. Scalability
|
|
35
|
+
Design for horizontal scaling. Avoid single points of failure.
|
|
36
|
+
|
|
37
|
+
## Project Structure
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
src/
|
|
41
|
+
├── routes/ # Route handlers/controllers
|
|
42
|
+
├── services/ # Business logic
|
|
43
|
+
├── repositories/ # Data access layer
|
|
44
|
+
├── middleware/ # Request/response middleware
|
|
45
|
+
├── lib/ # Shared utilities
|
|
46
|
+
├── types/ # TypeScript definitions
|
|
47
|
+
├── validation/ # Input validation schemas
|
|
48
|
+
└── config/ # Configuration management
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Design Principles
|
|
52
|
+
|
|
53
|
+
- Use consistent naming conventions
|
|
54
|
+
- Return appropriate HTTP status codes
|
|
55
|
+
- Provide meaningful error messages
|
|
56
|
+
- Version your APIs
|
|
57
|
+
- Document endpoints thoroughly
|
|
58
|
+
|
|
59
|
+
## Definition of Done
|
|
60
|
+
|
|
61
|
+
A backend feature is complete when:
|
|
62
|
+
- [ ] Endpoint works as specified
|
|
63
|
+
- [ ] Input validation implemented
|
|
64
|
+
- [ ] Error handling covers edge cases
|
|
65
|
+
- [ ] Authentication/authorization enforced
|
|
66
|
+
- [ ] Unit and integration tests passing
|
|
67
|
+
- [ ] API documentation updated
|
|
68
|
+
- [ ] No security vulnerabilities
|
|
69
|
+
- [ ] Logging in place for debugging
|