@venizia/ignis-docs 0.0.4-0 → 0.0.4-2
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/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +1 -0
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +259 -0
- package/wiki/best-practices/code-style-standards/constants-configuration.md +225 -0
- package/wiki/best-practices/code-style-standards/control-flow.md +245 -0
- package/wiki/best-practices/code-style-standards/documentation.md +221 -0
- package/wiki/best-practices/code-style-standards/function-patterns.md +142 -0
- package/wiki/best-practices/code-style-standards/index.md +110 -0
- package/wiki/best-practices/code-style-standards/naming-conventions.md +174 -0
- package/wiki/best-practices/code-style-standards/route-definitions.md +150 -0
- package/wiki/best-practices/code-style-standards/tooling.md +155 -0
- package/wiki/best-practices/code-style-standards/type-safety.md +165 -0
- package/wiki/best-practices/common-pitfalls.md +164 -3
- package/wiki/best-practices/contribution-workflow.md +1 -1
- package/wiki/best-practices/data-modeling.md +102 -2
- package/wiki/best-practices/error-handling.md +468 -0
- package/wiki/best-practices/index.md +204 -21
- package/wiki/best-practices/performance-optimization.md +180 -0
- package/wiki/best-practices/security-guidelines.md +249 -0
- package/wiki/best-practices/testing-strategies.md +620 -0
- package/wiki/changelogs/2026-01-05-range-queries-content-range.md +184 -0
- package/wiki/changelogs/2026-01-06-basic-authentication.md +103 -0
- package/wiki/changelogs/2026-01-07-controller-route-customization.md +209 -0
- package/wiki/changelogs/index.md +3 -0
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/persistent/models.md +10 -0
- package/wiki/guides/tutorials/complete-installation.md +1 -1
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/bootstrapping.md +4 -3
- package/wiki/references/base/components.md +47 -29
- package/wiki/references/base/controllers.md +220 -24
- package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
- package/wiki/references/base/middlewares.md +37 -3
- package/wiki/references/base/models.md +40 -2
- package/wiki/references/base/providers.md +1 -2
- package/wiki/references/base/repositories/index.md +3 -1
- package/wiki/references/base/services.md +2 -2
- package/wiki/references/components/authentication.md +261 -247
- package/wiki/references/helpers/index.md +1 -1
- package/wiki/references/helpers/socket-io.md +1 -1
- package/wiki/references/quick-reference.md +2 -2
- package/wiki/references/src-details/core.md +1 -1
- package/wiki/references/utilities/statuses.md +4 -4
- package/wiki/best-practices/code-style-standards.md +0 -1193
|
@@ -194,3 +194,183 @@ try {
|
|
|
194
194
|
|
|
195
195
|
> [!WARNING]
|
|
196
196
|
> Higher isolation levels reduce concurrency. Use `READ COMMITTED` unless you have specific consistency requirements.
|
|
197
|
+
|
|
198
|
+
## 7. Database Connection Pooling
|
|
199
|
+
|
|
200
|
+
Connection pooling significantly improves performance by reusing database connections instead of creating new ones for each request.
|
|
201
|
+
|
|
202
|
+
**Configure in DataSource:**
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { Pool } from 'pg';
|
|
206
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
207
|
+
|
|
208
|
+
export class PostgresDataSource extends AbstractDataSource {
|
|
209
|
+
override connect(): void {
|
|
210
|
+
const pool = new Pool({
|
|
211
|
+
host: this.settings.host,
|
|
212
|
+
port: this.settings.port,
|
|
213
|
+
user: this.settings.username,
|
|
214
|
+
password: this.settings.password,
|
|
215
|
+
database: this.settings.database,
|
|
216
|
+
|
|
217
|
+
// Connection pool settings
|
|
218
|
+
max: 20, // Maximum connections in pool
|
|
219
|
+
min: 5, // Minimum connections to maintain
|
|
220
|
+
idleTimeoutMillis: 30000, // Close idle connections after 30s
|
|
221
|
+
connectionTimeoutMillis: 5000, // Fail if can't connect in 5s
|
|
222
|
+
maxUses: 7500, // Close connection after 7500 queries
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
this.connector = drizzle({ client: pool, schema: this.schema });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Recommended Pool Sizes:**
|
|
231
|
+
|
|
232
|
+
| Server RAM | Concurrent Users | Max Pool Size | Min Pool Size |
|
|
233
|
+
|------------|------------------|---------------|---------------|
|
|
234
|
+
| < 2GB | < 100 | 10 | 2 |
|
|
235
|
+
| 2-4GB | 100-500 | 20 | 5 |
|
|
236
|
+
| 4-8GB | 500-1000 | 30 | 10 |
|
|
237
|
+
| > 8GB | > 1000 | 50+ | 15 |
|
|
238
|
+
|
|
239
|
+
**Formula:** `max_connections = (number_of_cores * 2) + effective_spindle_count`
|
|
240
|
+
|
|
241
|
+
For most applications: `max_connections = CPU_cores * 2 + 1`
|
|
242
|
+
|
|
243
|
+
**Monitoring Pool Health:**
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// Log pool statistics periodically
|
|
247
|
+
const pool = new Pool({ /* ... */ });
|
|
248
|
+
|
|
249
|
+
setInterval(() => {
|
|
250
|
+
this.logger.info('[pool] Stats | total: %d | idle: %d | waiting: %d',
|
|
251
|
+
pool.totalCount,
|
|
252
|
+
pool.idleCount,
|
|
253
|
+
pool.waitingCount
|
|
254
|
+
);
|
|
255
|
+
}, 60000); // Every minute
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Warning Signs:**
|
|
259
|
+
- `waitingCount > 0` consistently → Increase `max`
|
|
260
|
+
- `idleCount === totalCount` always → Decrease `max`
|
|
261
|
+
- Connection timeouts → Check network, increase `connectionTimeoutMillis`
|
|
262
|
+
|
|
263
|
+
## 8. Query Optimization Tips
|
|
264
|
+
|
|
265
|
+
### Use Indexes Strategically
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Create indexes on frequently queried columns
|
|
269
|
+
export const User = pgTable('User', {
|
|
270
|
+
id: text('id').primaryKey(),
|
|
271
|
+
email: text('email').notNull().unique(), // Implicit unique index
|
|
272
|
+
status: text('status').notNull(),
|
|
273
|
+
createdAt: timestamp('created_at').notNull(),
|
|
274
|
+
}, (table) => ({
|
|
275
|
+
// Composite index for common query patterns
|
|
276
|
+
statusCreatedIdx: index('idx_user_status_created').on(table.status, table.createdAt),
|
|
277
|
+
// Partial index for active users only
|
|
278
|
+
activeEmailIdx: index('idx_active_email').on(table.email).where(eq(table.status, 'ACTIVE')),
|
|
279
|
+
}));
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Avoid N+1 Queries
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// ❌ BAD - N+1 queries
|
|
286
|
+
const users = await userRepo.find({ filter: { limit: 100 } });
|
|
287
|
+
for (const user of users.data) {
|
|
288
|
+
user.posts = await postRepo.find({ filter: { where: { authorId: user.id } } });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ✅ GOOD - Single query with relations
|
|
292
|
+
const users = await userRepo.find({
|
|
293
|
+
filter: {
|
|
294
|
+
limit: 100,
|
|
295
|
+
include: [{ relation: 'posts' }],
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Batch Operations
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// ❌ BAD - Many individual inserts
|
|
304
|
+
for (const item of items) {
|
|
305
|
+
await repo.create({ data: item });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ✅ GOOD - Batch insert
|
|
309
|
+
await repo.createMany({ data: items });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## 9. Memory Management
|
|
313
|
+
|
|
314
|
+
### Stream Large Datasets
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// ❌ BAD - Load all records into memory
|
|
318
|
+
const allUsers = await userRepo.find({ filter: { limit: 100000 } });
|
|
319
|
+
|
|
320
|
+
// ✅ GOOD - Process in batches
|
|
321
|
+
const batchSize = 1000;
|
|
322
|
+
let offset = 0;
|
|
323
|
+
let hasMore = true;
|
|
324
|
+
|
|
325
|
+
while (hasMore) {
|
|
326
|
+
const batch = await userRepo.find({
|
|
327
|
+
filter: { limit: batchSize, offset },
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
for (const user of batch.data) {
|
|
331
|
+
await processUser(user);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
hasMore = batch.data.length === batchSize;
|
|
335
|
+
offset += batchSize;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Avoid Memory Leaks in Long-Running Processes
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// ❌ BAD - Growing array in long-running process
|
|
343
|
+
const processedIds: string[] = [];
|
|
344
|
+
// This array grows forever!
|
|
345
|
+
|
|
346
|
+
// ✅ GOOD - Use Set with cleanup or external storage
|
|
347
|
+
const processedIds = new Set<string>();
|
|
348
|
+
|
|
349
|
+
// Periodically clear or use Redis
|
|
350
|
+
setInterval(() => {
|
|
351
|
+
if (processedIds.size > 10000) {
|
|
352
|
+
processedIds.clear();
|
|
353
|
+
}
|
|
354
|
+
}, 3600000); // Every hour
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Performance Checklist
|
|
358
|
+
|
|
359
|
+
| Category | Check | Impact |
|
|
360
|
+
|----------|-------|--------|
|
|
361
|
+
| **Database** | Connection pooling configured | High |
|
|
362
|
+
| **Database** | Indexes on WHERE/JOIN columns | High |
|
|
363
|
+
| **Database** | Limit on all queries | High |
|
|
364
|
+
| **Queries** | Using `fields` to select specific columns | Medium |
|
|
365
|
+
| **Queries** | Relations limited to 2 levels | Medium |
|
|
366
|
+
| **Queries** | Batch operations for bulk data | High |
|
|
367
|
+
| **Memory** | Large datasets processed in batches | High |
|
|
368
|
+
| **Caching** | Expensive queries cached | High |
|
|
369
|
+
| **Workers** | CPU-intensive tasks offloaded | High |
|
|
370
|
+
| **Monitoring** | Performance logging enabled | Low |
|
|
371
|
+
|
|
372
|
+
## See Also
|
|
373
|
+
|
|
374
|
+
- [Data Modeling](./data-modeling) - Schema design for performance
|
|
375
|
+
- [Deployment Strategies](./deployment-strategies) - Production scaling
|
|
376
|
+
- [Common Pitfalls](./common-pitfalls) - Performance mistakes to avoid
|
|
@@ -216,3 +216,252 @@ bun update
|
|
|
216
216
|
- `jose` - JWT handling
|
|
217
217
|
- `drizzle-orm` - Database ORM
|
|
218
218
|
- `@venizia/ignis` - Framework core
|
|
219
|
+
|
|
220
|
+
## 7. CORS Configuration
|
|
221
|
+
|
|
222
|
+
Configure Cross-Origin Resource Sharing to control which domains can access your API.
|
|
223
|
+
|
|
224
|
+
**Default (Development):**
|
|
225
|
+
```typescript
|
|
226
|
+
import { cors } from 'hono/cors';
|
|
227
|
+
|
|
228
|
+
// Allow all origins (ONLY for development)
|
|
229
|
+
this.server.use('*', cors());
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Production (Restrictive):**
|
|
233
|
+
```typescript
|
|
234
|
+
import { cors } from 'hono/cors';
|
|
235
|
+
|
|
236
|
+
this.server.use('/api/*', cors({
|
|
237
|
+
origin: ['https://yourdomain.com', 'https://app.yourdomain.com'],
|
|
238
|
+
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
239
|
+
allowHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
|
|
240
|
+
exposeHeaders: ['X-Request-ID'],
|
|
241
|
+
credentials: true,
|
|
242
|
+
maxAge: 86400, // Cache preflight for 24 hours
|
|
243
|
+
}));
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Dynamic Origin Validation:**
|
|
247
|
+
```typescript
|
|
248
|
+
this.server.use('/api/*', cors({
|
|
249
|
+
origin: (origin) => {
|
|
250
|
+
const allowedDomains = ['yourdomain.com', 'yourdomain.io'];
|
|
251
|
+
try {
|
|
252
|
+
const url = new URL(origin);
|
|
253
|
+
return allowedDomains.some(domain => url.hostname.endsWith(domain))
|
|
254
|
+
? origin
|
|
255
|
+
: null;
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
}));
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
> [!WARNING]
|
|
264
|
+
> Never use `origin: '*'` with `credentials: true` in production. This is a security vulnerability.
|
|
265
|
+
|
|
266
|
+
## 8. Rate Limiting
|
|
267
|
+
|
|
268
|
+
Protect against brute force attacks and denial of service.
|
|
269
|
+
|
|
270
|
+
**Basic Rate Limiter:**
|
|
271
|
+
```typescript
|
|
272
|
+
import { createMiddleware } from 'hono/factory';
|
|
273
|
+
|
|
274
|
+
const rateLimiter = (opts: { windowMs: number; max: number }) => {
|
|
275
|
+
const requests = new Map<string, { count: number; resetAt: number }>();
|
|
276
|
+
|
|
277
|
+
return createMiddleware(async (c, next) => {
|
|
278
|
+
const ip = c.req.header('x-forwarded-for') ?? c.req.header('x-real-ip') ?? 'unknown';
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
const record = requests.get(ip);
|
|
281
|
+
|
|
282
|
+
if (!record || now > record.resetAt) {
|
|
283
|
+
requests.set(ip, { count: 1, resetAt: now + opts.windowMs });
|
|
284
|
+
} else if (record.count >= opts.max) {
|
|
285
|
+
return c.json({
|
|
286
|
+
statusCode: 429,
|
|
287
|
+
message: 'Too many requests. Please try again later.',
|
|
288
|
+
}, 429);
|
|
289
|
+
} else {
|
|
290
|
+
record.count++;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await next();
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Apply to sensitive endpoints
|
|
298
|
+
this.server.use('/api/auth/login', rateLimiter({ windowMs: 15 * 60 * 1000, max: 5 }));
|
|
299
|
+
this.server.use('/api/auth/register', rateLimiter({ windowMs: 60 * 60 * 1000, max: 10 }));
|
|
300
|
+
this.server.use('/api/*', rateLimiter({ windowMs: 60 * 1000, max: 100 }));
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Recommended Limits:**
|
|
304
|
+
|
|
305
|
+
| Endpoint | Window | Max Requests | Reason |
|
|
306
|
+
|----------|--------|--------------|--------|
|
|
307
|
+
| `/auth/login` | 15 min | 5 | Prevent brute force |
|
|
308
|
+
| `/auth/register` | 1 hour | 10 | Prevent spam accounts |
|
|
309
|
+
| `/auth/forgot-password` | 1 hour | 3 | Prevent email flooding |
|
|
310
|
+
| `/api/*` (general) | 1 min | 100 | General protection |
|
|
311
|
+
|
|
312
|
+
**Production Recommendation:** Use Redis-backed rate limiting for distributed deployments:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { RedisHelper } from '@venizia/ignis-helpers';
|
|
316
|
+
|
|
317
|
+
// Rate limiter with Redis for multi-instance deployments
|
|
318
|
+
const distributedRateLimiter = async (key: string, max: number, windowSec: number) => {
|
|
319
|
+
const redis = RedisHelper.getClient();
|
|
320
|
+
const current = await redis.incr(key);
|
|
321
|
+
if (current === 1) {
|
|
322
|
+
await redis.expire(key, windowSec);
|
|
323
|
+
}
|
|
324
|
+
return current <= max;
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## 9. SQL Injection Prevention
|
|
329
|
+
|
|
330
|
+
Drizzle ORM automatically parameterizes queries, protecting against SQL injection. However, **raw queries require care**.
|
|
331
|
+
|
|
332
|
+
**Safe (Parameterized):**
|
|
333
|
+
```typescript
|
|
334
|
+
// ✅ Repository methods are safe - queries are parameterized
|
|
335
|
+
await userRepository.find({
|
|
336
|
+
filter: { where: { email: userInput } },
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ✅ Drizzle query builder is safe
|
|
340
|
+
await db.select().from(users).where(eq(users.email, userInput));
|
|
341
|
+
|
|
342
|
+
// ✅ sql`` template with placeholders is safe
|
|
343
|
+
await db.execute(sql`SELECT * FROM users WHERE email = ${userInput}`);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Dangerous (String Interpolation):**
|
|
347
|
+
```typescript
|
|
348
|
+
// ❌ NEVER use string interpolation in raw SQL
|
|
349
|
+
const query = `SELECT * FROM users WHERE email = '${userInput}'`;
|
|
350
|
+
await db.execute(sql.raw(query)); // Vulnerable to SQL injection!
|
|
351
|
+
|
|
352
|
+
// ❌ NEVER build WHERE clauses with string concatenation
|
|
353
|
+
const condition = `status = '${status}' AND role = '${role}'`;
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**If You Must Use Dynamic SQL:**
|
|
357
|
+
```typescript
|
|
358
|
+
// Use parameterized queries with sql.raw only for table/column names
|
|
359
|
+
const tableName = allowedTables.includes(input) ? input : 'default_table';
|
|
360
|
+
await db.execute(sql`SELECT * FROM ${sql.identifier(tableName)} WHERE id = ${id}`);
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## 10. Security Headers
|
|
364
|
+
|
|
365
|
+
Add security headers to protect against common attacks:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { secureHeaders } from 'hono/secure-headers';
|
|
369
|
+
|
|
370
|
+
// Add security headers to all responses
|
|
371
|
+
this.server.use('*', secureHeaders({
|
|
372
|
+
// Prevent clickjacking
|
|
373
|
+
xFrameOptions: 'DENY',
|
|
374
|
+
// Prevent MIME type sniffing
|
|
375
|
+
xContentTypeOptions: 'nosniff',
|
|
376
|
+
// Enable XSS filter
|
|
377
|
+
xXssProtection: '1; mode=block',
|
|
378
|
+
// Control referrer information
|
|
379
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
380
|
+
// Content Security Policy
|
|
381
|
+
contentSecurityPolicy: {
|
|
382
|
+
defaultSrc: ["'self'"],
|
|
383
|
+
scriptSrc: ["'self'"],
|
|
384
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
385
|
+
imgSrc: ["'self'", 'data:', 'https:'],
|
|
386
|
+
},
|
|
387
|
+
}));
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## 11. Request Size Limits
|
|
391
|
+
|
|
392
|
+
Prevent denial of service through large payloads:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
396
|
+
|
|
397
|
+
// Limit request body size
|
|
398
|
+
this.server.use('/api/*', bodyLimit({
|
|
399
|
+
maxSize: 1024 * 1024, // 1MB for general API
|
|
400
|
+
onError: (c) => c.json({ message: 'Request body too large' }, 413),
|
|
401
|
+
}));
|
|
402
|
+
|
|
403
|
+
// Allow larger uploads for file endpoints
|
|
404
|
+
this.server.use('/api/upload/*', bodyLimit({
|
|
405
|
+
maxSize: 50 * 1024 * 1024, // 50MB for file uploads
|
|
406
|
+
}));
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## 12. Logging Security Events
|
|
410
|
+
|
|
411
|
+
Log security-relevant events for monitoring and forensics:
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { BaseService } from '@venizia/ignis';
|
|
415
|
+
|
|
416
|
+
export class AuthService extends BaseService {
|
|
417
|
+
async login(email: string, password: string, context: Context) {
|
|
418
|
+
const ip = context.req.header('x-forwarded-for') ?? 'unknown';
|
|
419
|
+
const userAgent = context.req.header('user-agent') ?? 'unknown';
|
|
420
|
+
|
|
421
|
+
const user = await this.userRepo.findByEmail(email);
|
|
422
|
+
|
|
423
|
+
if (!user || !await this.verifyPassword(password, user.password)) {
|
|
424
|
+
// Log failed attempt
|
|
425
|
+
this.logger.warn('[login] Failed login attempt | email: %s | ip: %s | userAgent: %s',
|
|
426
|
+
email, ip, userAgent);
|
|
427
|
+
throw getError({ statusCode: 401, message: 'Invalid credentials' });
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Log successful login
|
|
431
|
+
this.logger.info('[login] Successful login | userId: %s | ip: %s', user.id, ip);
|
|
432
|
+
|
|
433
|
+
return this.generateToken(user);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Events to Log:**
|
|
439
|
+
- Failed login attempts
|
|
440
|
+
- Successful logins
|
|
441
|
+
- Password changes
|
|
442
|
+
- Permission changes
|
|
443
|
+
- Suspicious activity (rate limit hits, invalid tokens)
|
|
444
|
+
- Admin actions
|
|
445
|
+
|
|
446
|
+
## Security Checklist
|
|
447
|
+
|
|
448
|
+
Before deploying to production, verify:
|
|
449
|
+
|
|
450
|
+
| Category | Check |
|
|
451
|
+
|----------|-------|
|
|
452
|
+
| **Secrets** | All secrets in environment variables, not in code |
|
|
453
|
+
| **Auth** | JWT tokens have reasonable expiration (15min - 24h) |
|
|
454
|
+
| **Input** | All user input validated with Zod schemas |
|
|
455
|
+
| **CORS** | Specific origins configured, not `*` |
|
|
456
|
+
| **Rate Limiting** | Applied to auth endpoints and general API |
|
|
457
|
+
| **Headers** | Security headers configured |
|
|
458
|
+
| **Logging** | Security events logged for monitoring |
|
|
459
|
+
| **Dependencies** | No known vulnerabilities (`bun audit`) |
|
|
460
|
+
| **HTTPS** | TLS configured for production |
|
|
461
|
+
| **Hidden Data** | Sensitive fields use `hiddenProperties` |
|
|
462
|
+
|
|
463
|
+
## See Also
|
|
464
|
+
|
|
465
|
+
- [Authentication Component](../references/components/authentication) - JWT setup and configuration
|
|
466
|
+
- [Common Pitfalls](./common-pitfalls) - Security-related mistakes to avoid
|
|
467
|
+
- [Deployment Strategies](./deployment-strategies) - Secure deployment practices
|