create-tigra 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 +87 -0
- package/bin/create-tigra.js +292 -0
- package/package.json +41 -0
- package/template/.agent/rules/client/01-project-structure.md +326 -0
- package/template/.agent/rules/client/02-component-patterns.md +249 -0
- package/template/.agent/rules/client/03-typescript-rules.md +226 -0
- package/template/.agent/rules/client/04-state-management.md +474 -0
- package/template/.agent/rules/client/05-api-integration.md +129 -0
- package/template/.agent/rules/client/06-forms-validation.md +129 -0
- package/template/.agent/rules/client/07-common-patterns.md +150 -0
- package/template/.agent/rules/client/08-color-system.md +93 -0
- package/template/.agent/rules/client/09-security-rules.md +97 -0
- package/template/.agent/rules/client/10-testing-strategy.md +370 -0
- package/template/.agent/rules/global/ai-edit-safety.md +38 -0
- package/template/.agent/rules/server/01-db-and-migrations.md +242 -0
- package/template/.agent/rules/server/02-general-rules.md +111 -0
- package/template/.agent/rules/server/03-migrations.md +20 -0
- package/template/.agent/rules/server/04-pagination.md +130 -0
- package/template/.agent/rules/server/05-project-conventions.md +71 -0
- package/template/.agent/rules/server/06-response-handling.md +173 -0
- package/template/.agent/rules/server/07-testing-strategy.md +506 -0
- package/template/.agent/rules/server/08-observability.md +180 -0
- package/template/.agent/rules/server/09-api-documentation-v2.md +168 -0
- package/template/.agent/rules/server/10-background-jobs-v2.md +185 -0
- package/template/.agent/rules/server/11-rate-limiting-v2.md +210 -0
- package/template/.agent/rules/server/12-performance-optimization.md +567 -0
- package/template/.claude/rules/client-01-project-structure.md +327 -0
- package/template/.claude/rules/client-02-component-patterns.md +250 -0
- package/template/.claude/rules/client-03-typescript-rules.md +227 -0
- package/template/.claude/rules/client-04-state-management.md +475 -0
- package/template/.claude/rules/client-05-api-integration.md +130 -0
- package/template/.claude/rules/client-06-forms-validation.md +130 -0
- package/template/.claude/rules/client-07-common-patterns.md +151 -0
- package/template/.claude/rules/client-08-color-system.md +94 -0
- package/template/.claude/rules/client-09-security-rules.md +98 -0
- package/template/.claude/rules/client-10-testing-strategy.md +371 -0
- package/template/.claude/rules/global-ai-edit-safety.md +39 -0
- package/template/.claude/rules/server-01-db-and-migrations.md +243 -0
- package/template/.claude/rules/server-02-general-rules.md +112 -0
- package/template/.claude/rules/server-03-migrations.md +21 -0
- package/template/.claude/rules/server-04-pagination.md +131 -0
- package/template/.claude/rules/server-05-project-conventions.md +72 -0
- package/template/.claude/rules/server-06-response-handling.md +174 -0
- package/template/.claude/rules/server-07-testing-strategy.md +507 -0
- package/template/.claude/rules/server-08-observability.md +181 -0
- package/template/.claude/rules/server-09-api-documentation-v2.md +169 -0
- package/template/.claude/rules/server-10-background-jobs-v2.md +186 -0
- package/template/.claude/rules/server-11-rate-limiting-v2.md +211 -0
- package/template/.claude/rules/server-12-performance-optimization.md +568 -0
- package/template/.cursor/rules/client-01-project-structure.mdc +327 -0
- package/template/.cursor/rules/client-02-component-patterns.mdc +250 -0
- package/template/.cursor/rules/client-03-typescript-rules.mdc +227 -0
- package/template/.cursor/rules/client-04-state-management.mdc +475 -0
- package/template/.cursor/rules/client-05-api-integration.mdc +130 -0
- package/template/.cursor/rules/client-06-forms-validation.mdc +130 -0
- package/template/.cursor/rules/client-07-common-patterns.mdc +151 -0
- package/template/.cursor/rules/client-08-color-system.mdc +94 -0
- package/template/.cursor/rules/client-09-security-rules.mdc +98 -0
- package/template/.cursor/rules/client-10-testing-strategy.mdc +371 -0
- package/template/.cursor/rules/global-ai-edit-safety.mdc +39 -0
- package/template/.cursor/rules/server-01-db-and-migrations.mdc +243 -0
- package/template/.cursor/rules/server-02-general-rules.mdc +112 -0
- package/template/.cursor/rules/server-03-migrations.mdc +21 -0
- package/template/.cursor/rules/server-04-pagination.mdc +131 -0
- package/template/.cursor/rules/server-05-project-conventions.mdc +72 -0
- package/template/.cursor/rules/server-06-response-handling.mdc +174 -0
- package/template/.cursor/rules/server-07-testing-strategy.mdc +507 -0
- package/template/.cursor/rules/server-08-observability.mdc +181 -0
- package/template/.cursor/rules/server-09-api-documentation-v2.mdc +169 -0
- package/template/.cursor/rules/server-10-background-jobs-v2.mdc +186 -0
- package/template/.cursor/rules/server-11-rate-limiting-v2.mdc +211 -0
- package/template/.cursor/rules/server-12-performance-optimization.mdc +568 -0
- package/template/CLAUDE.md +207 -0
- package/template/server/.env.example +148 -0
- package/template/server/.tsc-aliasrc.json +12 -0
- package/template/server/README.md +175 -0
- package/template/server/SECURITY.md +190 -0
- package/template/server/biome.json +42 -0
- package/template/server/docker-compose.yml +111 -0
- package/template/server/package.json +83 -0
- package/template/server/postman_collection.json +733 -0
- package/template/server/prisma/schema.prisma +92 -0
- package/template/server/prisma/seed.ts +142 -0
- package/template/server/scripts/wait-for-db.js +60 -0
- package/template/server/src/app.ts +74 -0
- package/template/server/src/config/env.ts +101 -0
- package/template/server/src/hooks/request-timing.hook.ts +26 -0
- package/template/server/src/libs/auth/authenticate.middleware.ts +22 -0
- package/template/server/src/libs/auth/rbac.middleware.test.ts +134 -0
- package/template/server/src/libs/auth/rbac.middleware.ts +147 -0
- package/template/server/src/libs/db.ts +76 -0
- package/template/server/src/libs/error-handler.ts +89 -0
- package/template/server/src/libs/logger.ts +60 -0
- package/template/server/src/libs/queue.ts +79 -0
- package/template/server/src/libs/redis.ts +79 -0
- package/template/server/src/libs/swagger-schemas.ts +16 -0
- package/template/server/src/modules/admin/admin.controller.ts +122 -0
- package/template/server/src/modules/admin/admin.routes.ts +100 -0
- package/template/server/src/modules/admin/admin.schemas.ts +35 -0
- package/template/server/src/modules/admin/admin.service.ts +167 -0
- package/template/server/src/modules/auth/auth.controller.ts +141 -0
- package/template/server/src/modules/auth/auth.integration.test.ts +150 -0
- package/template/server/src/modules/auth/auth.repo.ts +218 -0
- package/template/server/src/modules/auth/auth.routes.ts +204 -0
- package/template/server/src/modules/auth/auth.schemas.ts +137 -0
- package/template/server/src/modules/auth/auth.service.test.ts +119 -0
- package/template/server/src/modules/auth/auth.service.ts +329 -0
- package/template/server/src/modules/auth/auth.types.ts +97 -0
- package/template/server/src/modules/resources/resources.controller.ts +218 -0
- package/template/server/src/modules/resources/resources.repo.ts +253 -0
- package/template/server/src/modules/resources/resources.routes.ts +355 -0
- package/template/server/src/modules/resources/resources.schemas.ts +146 -0
- package/template/server/src/modules/resources/resources.service.ts +218 -0
- package/template/server/src/modules/resources/resources.types.ts +73 -0
- package/template/server/src/plugins/rate-limit.plugin.ts +21 -0
- package/template/server/src/plugins/security.plugin.ts +21 -0
- package/template/server/src/plugins/swagger.plugin.ts +41 -0
- package/template/server/src/routes/health.routes.ts +31 -0
- package/template/server/src/server.ts +142 -0
- package/template/server/src/test/setup.ts +38 -0
- package/template/server/src/types/fastify.d.ts +36 -0
- package/template/server/src/utils/errors.ts +108 -0
- package/template/server/src/utils/pagination.ts +120 -0
- package/template/server/src/utils/response.ts +110 -0
- package/template/server/src/workers/file.worker.ts +106 -0
- package/template/server/tsconfig.build.json +30 -0
- package/template/server/tsconfig.build.tsbuildinfo +1 -0
- package/template/server/tsconfig.json +89 -0
- package/template/server/tsconfig.test.json +22 -0
- package/template/server/vitest.config.ts +98 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: always_on
|
|
3
|
+
globs: "server/**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **SCOPE**: These rules apply specifically to the **server** directory.
|
|
7
|
+
|
|
8
|
+
# Performance Optimization
|
|
9
|
+
|
|
10
|
+
## Core Principles
|
|
11
|
+
|
|
12
|
+
- **Measure first, optimize second** - Use profiling to find actual bottlenecks
|
|
13
|
+
- **Database queries** are the #1 performance killer
|
|
14
|
+
- **Caching** can eliminate 80% of database queries
|
|
15
|
+
- **Async everything** - Never block the event loop
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Database Query Optimization
|
|
20
|
+
|
|
21
|
+
### 1. Prevent N+1 Queries
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// ❌ BAD - N+1 query problem
|
|
25
|
+
async function getResourcesWithOwners() {
|
|
26
|
+
const resources = await prisma.resource.findMany();
|
|
27
|
+
|
|
28
|
+
// This executes 1 query per resource!
|
|
29
|
+
for (const resource of resources) {
|
|
30
|
+
resource.owner = await prisma.user.findUnique({
|
|
31
|
+
where: { id: resource.ownerId },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return resources;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ✅ GOOD - Single query with join
|
|
39
|
+
async function getResourcesWithOwners() {
|
|
40
|
+
return await prisma.resource.findMany({
|
|
41
|
+
include: {
|
|
42
|
+
owner: true, // Prisma joins in one query
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Select Only Needed Fields
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ❌ BAD - Fetches all columns
|
|
52
|
+
const users = await prisma.user.findMany();
|
|
53
|
+
|
|
54
|
+
// ✅ GOOD - Select specific fields
|
|
55
|
+
const users = await prisma.user.findMany({
|
|
56
|
+
select: {
|
|
57
|
+
id: true,
|
|
58
|
+
email: true,
|
|
59
|
+
name: true,
|
|
60
|
+
// Don't fetch password, createdAt, etc.
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Use Pagination
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// ❌ BAD - Loads all records into memory
|
|
69
|
+
const resources = await prisma.resource.findMany();
|
|
70
|
+
|
|
71
|
+
// ✅ GOOD - Paginate
|
|
72
|
+
const resources = await prisma.resource.findMany({
|
|
73
|
+
take: limit,
|
|
74
|
+
skip: (page - 1) * limit,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. Index Frequently Queried Fields
|
|
79
|
+
|
|
80
|
+
```prisma
|
|
81
|
+
// schema.prisma
|
|
82
|
+
model Resource {
|
|
83
|
+
id String @id @default(uuid())
|
|
84
|
+
ownerId String
|
|
85
|
+
status String
|
|
86
|
+
price Float
|
|
87
|
+
category String
|
|
88
|
+
createdAt DateTime @default(now())
|
|
89
|
+
|
|
90
|
+
// Add indexes for fields used in WHERE clauses
|
|
91
|
+
@@index([ownerId])
|
|
92
|
+
@@index([status])
|
|
93
|
+
@@index([category])
|
|
94
|
+
@@index([createdAt])
|
|
95
|
+
|
|
96
|
+
// Composite index for common queries
|
|
97
|
+
@@index([category, status, createdAt])
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 5. Use Transactions Sparingly
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// ❌ BAD - Unnecessary transaction
|
|
105
|
+
await prisma.$transaction([
|
|
106
|
+
prisma.user.findUnique({ where: { id: '1' } }), // Just a read!
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// ✅ GOOD - Transaction only for writes
|
|
110
|
+
await prisma.$transaction([
|
|
111
|
+
prisma.user.update({ where: { id: '1' }, data: { credits: { decrement: 10 } } }),
|
|
112
|
+
prisma.payment.create({ data: { userId: '1', amount: 10 } }),
|
|
113
|
+
]);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Redis Caching Strategies
|
|
119
|
+
|
|
120
|
+
### 1. Cache Expensive Queries
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// libs/cache.ts
|
|
124
|
+
import { redis } from './redis';
|
|
125
|
+
|
|
126
|
+
export async function getCachedOrFetch<T>(
|
|
127
|
+
key: string,
|
|
128
|
+
fetchFn: () => Promise<T>,
|
|
129
|
+
ttl = 300 // 5 minutes
|
|
130
|
+
): Promise<T> {
|
|
131
|
+
// Try cache first
|
|
132
|
+
const cached = await redis.get(key);
|
|
133
|
+
if (cached) {
|
|
134
|
+
return JSON.parse(cached);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Fetch from DB
|
|
138
|
+
const data = await fetchFn();
|
|
139
|
+
|
|
140
|
+
// Store in cache
|
|
141
|
+
await redis.setex(key, ttl, JSON.stringify(data));
|
|
142
|
+
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Usage in service
|
|
149
|
+
async function getPopularResources() {
|
|
150
|
+
return getCachedOrFetch(
|
|
151
|
+
'popular-resources',
|
|
152
|
+
async () => {
|
|
153
|
+
return await prisma.resource.findMany({
|
|
154
|
+
where: { isPopular: true },
|
|
155
|
+
take: 10,
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
600 // Cache for 10 minutes
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 2. Cache Invalidation
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// When resource is updated, invalidate cache
|
|
167
|
+
async function updateResource(id: string, data: any) {
|
|
168
|
+
const updated = await prisma.resource.update({
|
|
169
|
+
where: { id },
|
|
170
|
+
data,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Invalidate relevant caches
|
|
174
|
+
await redis.del(`resource:${id}`);
|
|
175
|
+
await redis.del('popular-resources');
|
|
176
|
+
await redis.del('recent-resources');
|
|
177
|
+
|
|
178
|
+
return updated;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 3. Cache Patterns
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Pattern: Cache-Aside (Lazy Loading)
|
|
186
|
+
async function getResource(id: string) {
|
|
187
|
+
const cacheKey = `resource:${id}`;
|
|
188
|
+
const cached = await redis.get(cacheKey);
|
|
189
|
+
|
|
190
|
+
if (cached) return JSON.parse(cached);
|
|
191
|
+
|
|
192
|
+
const resource = await prisma.resource.findUnique({ where: { id } });
|
|
193
|
+
if (resource) {
|
|
194
|
+
await redis.setex(cacheKey, 300, JSON.stringify(resource));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return resource;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Pattern: Write-Through
|
|
201
|
+
async function createResource(data: CreateResourceInput) {
|
|
202
|
+
const resource = await prisma.resource.create({ data });
|
|
203
|
+
|
|
204
|
+
// Immediately cache new resource
|
|
205
|
+
await redis.setex(
|
|
206
|
+
`resource:${resource.id}`,
|
|
207
|
+
300,
|
|
208
|
+
JSON.stringify(resource)
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return resource;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Query Performance Monitoring
|
|
218
|
+
|
|
219
|
+
### 1. Log Slow Queries
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// libs/db.ts (already in observability.md)
|
|
223
|
+
prisma.$on('query', (e) => {
|
|
224
|
+
if (e.duration > 1000) {
|
|
225
|
+
logger.warn(
|
|
226
|
+
{
|
|
227
|
+
query: e.query,
|
|
228
|
+
duration: e.duration,
|
|
229
|
+
params: e.params,
|
|
230
|
+
},
|
|
231
|
+
'Slow query detected'
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 2. Database Connection Pooling
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Prisma auto-manages connection pool
|
|
241
|
+
// Configure in DATABASE_URL
|
|
242
|
+
// mysql://user:pass@localhost:3306/db?connection_limit=10
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Response Compression
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// app.ts
|
|
251
|
+
import compress from '@fastify/compress';
|
|
252
|
+
|
|
253
|
+
await app.register(compress, {
|
|
254
|
+
global: true,
|
|
255
|
+
threshold: 1024, // Only compress responses > 1KB
|
|
256
|
+
encodings: ['gzip', 'deflate'],
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Async Processing Best Practices
|
|
263
|
+
|
|
264
|
+
### 1. Use Background Jobs for Slow Operations
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// ❌ BAD - Blocks HTTP response
|
|
268
|
+
app.post('/resources', async (request, reply) => {
|
|
269
|
+
const resource = await createResource(request.body);
|
|
270
|
+
|
|
271
|
+
// This takes 5 seconds!
|
|
272
|
+
await sendEmailToOwner(resource.ownerId);
|
|
273
|
+
await generateThumbnails(resource.images);
|
|
274
|
+
await notifyFollowers(resource.ownerId);
|
|
275
|
+
|
|
276
|
+
return reply.send(resource);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// ✅ GOOD - Offload to background jobs
|
|
280
|
+
app.post('/resources', async (request, reply) => {
|
|
281
|
+
const resource = await createResource(request.body);
|
|
282
|
+
|
|
283
|
+
// Queue background jobs (non-blocking)
|
|
284
|
+
await emailQueue.add('notify-owner', { resourceId: resource.id });
|
|
285
|
+
await fileQueue.add('generate-thumbnails', { resourceId: resource.id });
|
|
286
|
+
await notificationQueue.add('notify-followers', { ownerId: resource.ownerId });
|
|
287
|
+
|
|
288
|
+
// Return immediately
|
|
289
|
+
return reply.send(resource);
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 2. Parallel Execution
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// ❌ BAD - Sequential (slow)
|
|
297
|
+
const user = await prisma.user.findUnique({ where: { id } });
|
|
298
|
+
const resources = await prisma.resource.findMany({ where: { ownerId: id } });
|
|
299
|
+
const stats = await calculateUserStats(id);
|
|
300
|
+
|
|
301
|
+
// ✅ GOOD - Parallel (fast)
|
|
302
|
+
const [user, resources, stats] = await Promise.all([
|
|
303
|
+
prisma.user.findUnique({ where: { id } }),
|
|
304
|
+
prisma.resource.findMany({ where: { ownerId: id } }),
|
|
305
|
+
calculateUserStats(id),
|
|
306
|
+
]);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Response Optimization
|
|
312
|
+
|
|
313
|
+
### 1. Minimize Response Size
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// ❌ BAD - Returns entire objects
|
|
317
|
+
app.get('/resources', async (request, reply) => {
|
|
318
|
+
const resources = await prisma.resource.findMany();
|
|
319
|
+
return resources; // Includes unnecessary fields
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ✅ GOOD - Select only needed fields
|
|
323
|
+
app.get('/resources', async (request, reply) => {
|
|
324
|
+
const resources = await prisma.resource.findMany({
|
|
325
|
+
select: {
|
|
326
|
+
id: true,
|
|
327
|
+
title: true,
|
|
328
|
+
price: true,
|
|
329
|
+
thumbnailUrl: true,
|
|
330
|
+
// Don't include description, metadata, etc. in list view
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
return resources;
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 2. Use Streaming for Large Responses
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// For large datasets, stream instead of buffering
|
|
341
|
+
app.get('/export/resources', async (request, reply) => {
|
|
342
|
+
reply.type('application/json');
|
|
343
|
+
|
|
344
|
+
const stream = prisma.resource.findMany({ stream: true });
|
|
345
|
+
|
|
346
|
+
return reply.send(stream);
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Memory Management
|
|
353
|
+
|
|
354
|
+
### 1. Avoid Memory Leaks
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// ❌ BAD - Memory leak
|
|
358
|
+
const cache = new Map();
|
|
359
|
+
|
|
360
|
+
app.get('/data', (request, reply) => {
|
|
361
|
+
const data = getExpensiveData();
|
|
362
|
+
cache.set(request.id, data); // Never cleaned up!
|
|
363
|
+
return data;
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// ✅ GOOD - Use Redis or LRU cache
|
|
367
|
+
import LRU from 'lru-cache';
|
|
368
|
+
|
|
369
|
+
const cache = new LRU({
|
|
370
|
+
max: 500, // Max 500 items
|
|
371
|
+
ttl: 1000 * 60 * 5, // 5 minutes
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 2. Batch Processing
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// ❌ BAD - Process one by one
|
|
379
|
+
for (const user of users) {
|
|
380
|
+
await sendEmail(user.email);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ✅ GOOD - Batch process
|
|
384
|
+
const chunks = chunkArray(users, 100);
|
|
385
|
+
for (const chunk of chunks) {
|
|
386
|
+
await Promise.all(chunk.map(user => sendEmail(user.email)));
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## API Design for Performance
|
|
393
|
+
|
|
394
|
+
### 1. Cursor-Based Pagination (Large Datasets)
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
// Better than offset pagination for large datasets
|
|
398
|
+
app.get('/resources', async (request, reply) => {
|
|
399
|
+
const { cursor, limit = 10 } = request.query;
|
|
400
|
+
|
|
401
|
+
const resources = await prisma.resource.findMany({
|
|
402
|
+
take: limit + 1, // Fetch one extra to check if more exist
|
|
403
|
+
cursor: cursor ? { id: cursor } : undefined,
|
|
404
|
+
orderBy: { createdAt: 'desc' },
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const hasMore = resources.length > limit;
|
|
408
|
+
const items = hasMore ? resources.slice(0, -1) : resources;
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
items,
|
|
412
|
+
nextCursor: hasMore ? items[items.length - 1].id : null,
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 2. Field Selection (GraphQL-style)
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
// Allow clients to request only needed fields
|
|
421
|
+
app.get('/users/:id', async (request, reply) => {
|
|
422
|
+
const { fields } = request.query; // ?fields=id,email,name
|
|
423
|
+
|
|
424
|
+
const select = fields
|
|
425
|
+
? fields.split(',').reduce((acc, field) => {
|
|
426
|
+
acc[field] = true;
|
|
427
|
+
return acc;
|
|
428
|
+
}, {})
|
|
429
|
+
: undefined;
|
|
430
|
+
|
|
431
|
+
const user = await prisma.user.findUnique({
|
|
432
|
+
where: { id: request.params.id },
|
|
433
|
+
select,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
return user;
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Load Testing
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
# Install k6 for load testing
|
|
446
|
+
brew install k6 # macOS
|
|
447
|
+
# or
|
|
448
|
+
choco install k6 # Windows
|
|
449
|
+
|
|
450
|
+
# Create load test script
|
|
451
|
+
cat > load-test.js << 'EOF'
|
|
452
|
+
import http from 'k6/http';
|
|
453
|
+
import { check } from 'k6';
|
|
454
|
+
|
|
455
|
+
export const options = {
|
|
456
|
+
stages: [
|
|
457
|
+
{ duration: '30s', target: 50 }, // Ramp up to 50 users
|
|
458
|
+
{ duration: '1m', target: 100 }, // Stay at 100 users
|
|
459
|
+
{ duration: '30s', target: 0 }, // Ramp down
|
|
460
|
+
],
|
|
461
|
+
thresholds: {
|
|
462
|
+
http_req_duration: ['p(95)<500'], // 95% of requests < 500ms
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
export default function () {
|
|
467
|
+
const res = http.get('http://localhost:3000/api/v1/resources');
|
|
468
|
+
check(res, {
|
|
469
|
+
'status is 200': (r) => r.status === 200,
|
|
470
|
+
'response time < 500ms': (r) => r.timings.duration < 500,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
EOF
|
|
474
|
+
|
|
475
|
+
# Run test
|
|
476
|
+
k6 run load-test.js
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Performance Monitoring
|
|
482
|
+
|
|
483
|
+
### 1. Response Time Tracking
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
// Already covered in observability.md
|
|
487
|
+
app.addHook('onRequest', (request, reply, done) => {
|
|
488
|
+
request.startTime = Date.now();
|
|
489
|
+
done();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
app.addHook('onResponse', (request, reply, done) => {
|
|
493
|
+
const duration = Date.now() - request.startTime;
|
|
494
|
+
|
|
495
|
+
logger.info({
|
|
496
|
+
method: request.method,
|
|
497
|
+
url: request.url,
|
|
498
|
+
statusCode: reply.statusCode,
|
|
499
|
+
duration,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
done();
|
|
503
|
+
});
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### 2. Database Query Metrics
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
let queryCount = 0;
|
|
510
|
+
let totalQueryTime = 0;
|
|
511
|
+
|
|
512
|
+
prisma.$on('query', (e) => {
|
|
513
|
+
queryCount++;
|
|
514
|
+
totalQueryTime += e.duration;
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
app.get('/metrics/db', async (request, reply) => {
|
|
518
|
+
return {
|
|
519
|
+
totalQueries: queryCount,
|
|
520
|
+
avgDuration: totalQueryTime / queryCount,
|
|
521
|
+
};
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Performance Checklist
|
|
528
|
+
|
|
529
|
+
### Database
|
|
530
|
+
- [ ] Indexes on frequently queried fields
|
|
531
|
+
- [ ] No N+1 queries
|
|
532
|
+
- [ ] Pagination implemented
|
|
533
|
+
- [ ] Select only needed fields
|
|
534
|
+
- [ ] Connection pooling configured
|
|
535
|
+
- [ ] Slow query logging enabled
|
|
536
|
+
|
|
537
|
+
### Caching
|
|
538
|
+
- [ ] Redis caching for expensive queries
|
|
539
|
+
- [ ] Cache invalidation strategy
|
|
540
|
+
- [ ] TTL set appropriately
|
|
541
|
+
- [ ] Cache hit/miss monitoring
|
|
542
|
+
|
|
543
|
+
### API
|
|
544
|
+
- [ ] Response compression enabled
|
|
545
|
+
- [ ] Background jobs for slow operations
|
|
546
|
+
- [ ] Parallel execution where possible
|
|
547
|
+
- [ ] Minimal response payloads
|
|
548
|
+
- [ ] Rate limiting configured
|
|
549
|
+
|
|
550
|
+
### Monitoring
|
|
551
|
+
- [ ] Response time tracking
|
|
552
|
+
- [ ] Database metrics collected
|
|
553
|
+
- [ ] Load testing performed
|
|
554
|
+
- [ ] Error rates monitored
|
|
555
|
+
- [ ] Memory usage monitored
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## Performance Targets
|
|
560
|
+
|
|
561
|
+
| Metric | Target |
|
|
562
|
+
|--------|--------|
|
|
563
|
+
| API Response Time (p95) | < 500ms |
|
|
564
|
+
| API Response Time (p99) | < 1s |
|
|
565
|
+
| Database Query Time (p95) | < 100ms |
|
|
566
|
+
| Cache Hit Rate | > 80% |
|
|
567
|
+
| Error Rate | < 0.1% |
|
|
568
|
+
| Throughput | > 1000 req/s |
|