@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
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Comprehensive guide to handling errors gracefully in Ignis applications.
|
|
4
|
+
|
|
5
|
+
## Error Handling Philosophy
|
|
6
|
+
|
|
7
|
+
| Principle | Description |
|
|
8
|
+
|-----------|-------------|
|
|
9
|
+
| **Fail Fast** | Detect and report errors as early as possible |
|
|
10
|
+
| **Don't Swallow** | Never catch errors without logging or re-throwing |
|
|
11
|
+
| **User-Friendly** | Return clear, actionable messages to clients |
|
|
12
|
+
| **Debuggable** | Include context for debugging in logs |
|
|
13
|
+
|
|
14
|
+
## 1. Using `getError` Helper
|
|
15
|
+
|
|
16
|
+
Ignis provides `getError` for creating consistent, structured errors.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { getError, HTTP } from '@venizia/ignis';
|
|
20
|
+
|
|
21
|
+
// Basic error
|
|
22
|
+
throw getError({
|
|
23
|
+
statusCode: HTTP.ResultCodes.RS_4.NotFound,
|
|
24
|
+
message: 'User not found',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Error with details
|
|
28
|
+
throw getError({
|
|
29
|
+
statusCode: HTTP.ResultCodes.RS_4.BadRequest,
|
|
30
|
+
message: 'Invalid request',
|
|
31
|
+
details: {
|
|
32
|
+
field: 'email',
|
|
33
|
+
reason: 'Must be a valid email address',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Error with context (for logging)
|
|
38
|
+
throw getError({
|
|
39
|
+
statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
|
|
40
|
+
message: '[UserService][create] Database connection failed',
|
|
41
|
+
details: { userId: requestedId },
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 2. HTTP Status Code Reference
|
|
46
|
+
|
|
47
|
+
Use the correct status code for each error type:
|
|
48
|
+
|
|
49
|
+
| Code | Constant | Use When |
|
|
50
|
+
|------|----------|----------|
|
|
51
|
+
| 400 | `RS_4.BadRequest` | Invalid input format, missing required fields, database constraint violations (auto-handled) |
|
|
52
|
+
| 401 | `RS_4.Unauthorized` | Missing or invalid authentication |
|
|
53
|
+
| 403 | `RS_4.Forbidden` | Authenticated but insufficient permissions |
|
|
54
|
+
| 404 | `RS_4.NotFound` | Resource does not exist |
|
|
55
|
+
| 409 | `RS_4.Conflict` | Resource already exists (custom duplicate handling) |
|
|
56
|
+
| 422 | `RS_4.UnprocessableEntity` | Validation failed (Zod errors) |
|
|
57
|
+
| 429 | `RS_4.TooManyRequests` | Rate limit exceeded |
|
|
58
|
+
| 500 | `RS_5.InternalServerError` | Unexpected server error |
|
|
59
|
+
| 502 | `RS_5.BadGateway` | External service failed |
|
|
60
|
+
| 503 | `RS_5.ServiceUnavailable` | Service temporarily down |
|
|
61
|
+
|
|
62
|
+
:::tip Automatic Database Error Handling
|
|
63
|
+
Database constraint violations (unique, foreign key, not null, check) are automatically converted to HTTP 400 by the global error middleware. You don't need to catch these errors manually.
|
|
64
|
+
:::
|
|
65
|
+
|
|
66
|
+
## 3. Error Handling Patterns
|
|
67
|
+
|
|
68
|
+
### Service Layer Errors
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { BaseService, getError, HTTP } from '@venizia/ignis';
|
|
72
|
+
|
|
73
|
+
export class UserService extends BaseService {
|
|
74
|
+
async createUser(data: TCreateUserRequest): Promise<TUser> {
|
|
75
|
+
// Validate business rules
|
|
76
|
+
const existingUser = await this.userRepo.findOne({
|
|
77
|
+
filter: { where: { email: data.email } },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (existingUser.data) {
|
|
81
|
+
throw getError({
|
|
82
|
+
statusCode: HTTP.ResultCodes.RS_4.Conflict,
|
|
83
|
+
message: 'Email already registered',
|
|
84
|
+
details: { email: data.email },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle external service errors
|
|
89
|
+
try {
|
|
90
|
+
await this.emailService.sendWelcome(data.email);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// Log but don't fail user creation
|
|
93
|
+
this.logger.error('[createUser] Failed to send welcome email | email: %s | error: %s',
|
|
94
|
+
data.email, error.message);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.userRepo.create({ data });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getUserOrFail(id: string): Promise<TUser> {
|
|
101
|
+
const user = await this.userRepo.findById({ id });
|
|
102
|
+
|
|
103
|
+
if (!user.data) {
|
|
104
|
+
throw getError({
|
|
105
|
+
statusCode: HTTP.ResultCodes.RS_4.NotFound,
|
|
106
|
+
message: 'User not found',
|
|
107
|
+
details: { id },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return user.data;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Controller Layer Errors
|
|
117
|
+
|
|
118
|
+
Controllers should delegate to services and let the global error handler catch exceptions:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { BaseController, controller, get, post } from '@venizia/ignis';
|
|
122
|
+
|
|
123
|
+
@controller({ path: '/users' })
|
|
124
|
+
export class UserController extends BaseController {
|
|
125
|
+
|
|
126
|
+
@post({ configs: RouteConfigs.CREATE_USER })
|
|
127
|
+
async createUser(c: Context) {
|
|
128
|
+
const data = c.req.valid('json');
|
|
129
|
+
|
|
130
|
+
// Service throws appropriate errors
|
|
131
|
+
const user = await this.userService.createUser(data);
|
|
132
|
+
|
|
133
|
+
return c.json(user, HTTP.ResultCodes.RS_2.Created);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@get({ configs: RouteConfigs.GET_USER })
|
|
137
|
+
async getUser(c: Context) {
|
|
138
|
+
const { id } = c.req.valid('param');
|
|
139
|
+
|
|
140
|
+
// Service throws 404 if not found
|
|
141
|
+
const user = await this.userService.getUserOrFail(id);
|
|
142
|
+
|
|
143
|
+
return c.json(user, HTTP.ResultCodes.RS_2.Ok);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Repository Layer Errors
|
|
149
|
+
|
|
150
|
+
Database constraint violations (unique, foreign key, not null, check) are **automatically handled** by the global error middleware. They return HTTP 400 with a human-readable message:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"message": "Unique constraint violation\nDetail: Key (email)=(test@example.com) already exists.\nTable: User\nConstraint: UQ_User_email",
|
|
155
|
+
"statusCode": 400,
|
|
156
|
+
"requestId": "abc123"
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
You don't need to wrap repository calls in try-catch for constraint errors. If you need custom error messages, you can still handle them explicitly:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { BaseRepository, getError, HTTP } from '@venizia/ignis';
|
|
164
|
+
|
|
165
|
+
export class UserRepository extends BaseRepository<typeof User.schema> {
|
|
166
|
+
async createWithCustomError(data: TCreateUser): Promise<TCreateResult<TUser>> {
|
|
167
|
+
try {
|
|
168
|
+
return await this.create({ data });
|
|
169
|
+
} catch (error) {
|
|
170
|
+
// Custom message for specific constraint
|
|
171
|
+
if (error.cause?.code === '23505' && error.cause?.constraint === 'UQ_User_email') {
|
|
172
|
+
throw getError({
|
|
173
|
+
statusCode: HTTP.ResultCodes.RS_4.Conflict,
|
|
174
|
+
message: 'This email is already registered. Please use a different email or login.',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
throw error; // Re-throw for automatic handling
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 4. Global Error Handler
|
|
184
|
+
|
|
185
|
+
Ignis includes a built-in error handler. Customize behavior in your application:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { BaseApplication, ApplicationError } from '@venizia/ignis';
|
|
189
|
+
|
|
190
|
+
export class Application extends BaseApplication {
|
|
191
|
+
override setupMiddlewares(): void {
|
|
192
|
+
super.setupMiddlewares();
|
|
193
|
+
|
|
194
|
+
// Custom error handler (optional)
|
|
195
|
+
this.server.onError((error, c) => {
|
|
196
|
+
const requestId = c.get('requestId') ?? 'unknown';
|
|
197
|
+
|
|
198
|
+
// Log all errors
|
|
199
|
+
this.logger.error('[%s] Error | %s', requestId, error.message);
|
|
200
|
+
|
|
201
|
+
// Handle known application errors
|
|
202
|
+
if (error instanceof ApplicationError) {
|
|
203
|
+
return c.json({
|
|
204
|
+
statusCode: error.statusCode,
|
|
205
|
+
message: error.message,
|
|
206
|
+
details: error.details,
|
|
207
|
+
requestId,
|
|
208
|
+
}, error.statusCode as StatusCode);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Handle Zod validation errors
|
|
212
|
+
if (error.name === 'ZodError') {
|
|
213
|
+
return c.json({
|
|
214
|
+
statusCode: 422,
|
|
215
|
+
message: 'Validation failed',
|
|
216
|
+
details: { cause: error.errors },
|
|
217
|
+
requestId,
|
|
218
|
+
}, 422);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Unknown errors - don't expose details
|
|
222
|
+
return c.json({
|
|
223
|
+
statusCode: 500,
|
|
224
|
+
message: 'Internal server error',
|
|
225
|
+
requestId,
|
|
226
|
+
}, 500);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 5. Error Response Format
|
|
233
|
+
|
|
234
|
+
All errors should follow a consistent format:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
interface ErrorResponse {
|
|
238
|
+
statusCode: number;
|
|
239
|
+
message: string;
|
|
240
|
+
requestId: string;
|
|
241
|
+
details?: {
|
|
242
|
+
cause?: Array<{
|
|
243
|
+
path: string;
|
|
244
|
+
message: string;
|
|
245
|
+
code: string;
|
|
246
|
+
}>;
|
|
247
|
+
[key: string]: unknown;
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Example Responses:**
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
// 400 Bad Request
|
|
256
|
+
{
|
|
257
|
+
"statusCode": 400,
|
|
258
|
+
"message": "Invalid request body",
|
|
259
|
+
"requestId": "abc123"
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 404 Not Found
|
|
263
|
+
{
|
|
264
|
+
"statusCode": 404,
|
|
265
|
+
"message": "User not found",
|
|
266
|
+
"requestId": "abc123",
|
|
267
|
+
"details": { "id": "user-uuid" }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 422 Validation Error
|
|
271
|
+
{
|
|
272
|
+
"statusCode": 422,
|
|
273
|
+
"message": "Validation failed",
|
|
274
|
+
"requestId": "abc123",
|
|
275
|
+
"details": {
|
|
276
|
+
"cause": [
|
|
277
|
+
{
|
|
278
|
+
"path": "email",
|
|
279
|
+
"message": "Invalid email format",
|
|
280
|
+
"code": "invalid_string"
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 500 Internal Error (production)
|
|
287
|
+
{
|
|
288
|
+
"statusCode": 500,
|
|
289
|
+
"message": "Internal server error",
|
|
290
|
+
"requestId": "abc123"
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## 6. Logging Errors
|
|
295
|
+
|
|
296
|
+
### What to Log
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// ✅ Good - Context for debugging
|
|
300
|
+
this.logger.error('[createOrder] Failed | userId: %s | orderId: %s | error: %s',
|
|
301
|
+
userId, orderId, error.message);
|
|
302
|
+
|
|
303
|
+
// ✅ Good - Include stack trace for unexpected errors
|
|
304
|
+
this.logger.error('[createOrder] Unexpected error | %s', error.stack);
|
|
305
|
+
|
|
306
|
+
// ❌ Bad - No context
|
|
307
|
+
this.logger.error(error.message);
|
|
308
|
+
|
|
309
|
+
// ❌ Bad - Sensitive data
|
|
310
|
+
this.logger.error('Login failed for user | password: %s', password);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Log Levels
|
|
314
|
+
|
|
315
|
+
| Level | Use For |
|
|
316
|
+
|-------|---------|
|
|
317
|
+
| `error` | Exceptions that need attention |
|
|
318
|
+
| `warn` | Recoverable issues, deprecation warnings |
|
|
319
|
+
| `info` | Important business events |
|
|
320
|
+
| `debug` | Detailed debugging information |
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// Error - requires attention
|
|
324
|
+
this.logger.error('[payment] Transaction failed | orderId: %s', orderId);
|
|
325
|
+
|
|
326
|
+
// Warn - recovered but should investigate
|
|
327
|
+
this.logger.warn('[cache] Redis unavailable, falling back to memory');
|
|
328
|
+
|
|
329
|
+
// Info - business event
|
|
330
|
+
this.logger.info('[order] Created | orderId: %s | userId: %s', orderId, userId);
|
|
331
|
+
|
|
332
|
+
// Debug - detailed trace
|
|
333
|
+
this.logger.debug('[query] Executing | sql: %s | params: %j', sql, params);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## 7. Async Error Handling
|
|
337
|
+
|
|
338
|
+
### Promises
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// ✅ Good - Errors propagate naturally with async/await
|
|
342
|
+
async function processOrder(orderId: string) {
|
|
343
|
+
const order = await orderRepo.findById({ id: orderId }); // Throws if fails
|
|
344
|
+
const payment = await paymentService.charge(order); // Throws if fails
|
|
345
|
+
return payment;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ✅ Good - Explicit catch when you need to handle
|
|
349
|
+
async function processOrderWithFallback(orderId: string) {
|
|
350
|
+
try {
|
|
351
|
+
return await paymentService.charge(order);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this.logger.warn('[processOrder] Primary payment failed, trying backup');
|
|
354
|
+
return await backupPaymentService.charge(order);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ❌ Bad - Swallowing errors
|
|
359
|
+
async function processOrder(orderId: string) {
|
|
360
|
+
try {
|
|
361
|
+
await dangerousOperation();
|
|
362
|
+
} catch (error) {
|
|
363
|
+
// Error is swallowed - no one knows it happened!
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Fire-and-Forget with Error Handling
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// ✅ Good - Log errors from fire-and-forget operations
|
|
372
|
+
this.sendNotification(userId).catch(error => {
|
|
373
|
+
this.logger.error('[notify] Failed | userId: %s | error: %s', userId, error.message);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// ✅ Good - Use void to indicate intentional fire-and-forget
|
|
377
|
+
void this.analytics.track('order_created', { orderId });
|
|
378
|
+
|
|
379
|
+
// ❌ Bad - Unhandled promise rejection
|
|
380
|
+
this.sendNotification(userId); // If this rejects, crash!
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## 8. Transaction Error Handling
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
async function transferFunds(from: string, to: string, amount: number) {
|
|
387
|
+
const tx = await accountRepo.beginTransaction();
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
await accountRepo.debit({ id: from, amount }, { transaction: tx });
|
|
391
|
+
await accountRepo.credit({ id: to, amount }, { transaction: tx });
|
|
392
|
+
|
|
393
|
+
await tx.commit();
|
|
394
|
+
return { success: true };
|
|
395
|
+
} catch (error) {
|
|
396
|
+
await tx.rollback();
|
|
397
|
+
|
|
398
|
+
// Re-throw with context
|
|
399
|
+
throw getError({
|
|
400
|
+
statusCode: HTTP.ResultCodes.RS_5.InternalServerError,
|
|
401
|
+
message: '[transferFunds] Transaction failed',
|
|
402
|
+
details: { from, to, amount, originalError: error.message },
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## 9. Client-Side Error Handling
|
|
409
|
+
|
|
410
|
+
Guide for API consumers:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// TypeScript client example
|
|
414
|
+
async function createUser(data: CreateUserRequest): Promise<User> {
|
|
415
|
+
const response = await fetch('/api/users', {
|
|
416
|
+
method: 'POST',
|
|
417
|
+
headers: { 'Content-Type': 'application/json' },
|
|
418
|
+
body: JSON.stringify(data),
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (!response.ok) {
|
|
422
|
+
const error = await response.json();
|
|
423
|
+
|
|
424
|
+
switch (response.status) {
|
|
425
|
+
case 400:
|
|
426
|
+
throw new ValidationError(error.message, error.details);
|
|
427
|
+
case 401:
|
|
428
|
+
// Redirect to login
|
|
429
|
+
window.location.href = '/login';
|
|
430
|
+
throw new AuthError('Please log in');
|
|
431
|
+
case 404:
|
|
432
|
+
throw new NotFoundError(error.message);
|
|
433
|
+
case 422:
|
|
434
|
+
// Handle field-level errors
|
|
435
|
+
const fieldErrors = error.details?.cause?.reduce((acc, e) => {
|
|
436
|
+
acc[e.path] = e.message;
|
|
437
|
+
return acc;
|
|
438
|
+
}, {});
|
|
439
|
+
throw new ValidationError('Validation failed', fieldErrors);
|
|
440
|
+
case 429:
|
|
441
|
+
throw new RateLimitError('Too many requests. Try again later.');
|
|
442
|
+
default:
|
|
443
|
+
throw new ApiError(error.message || 'Something went wrong');
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return response.json();
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Error Handling Checklist
|
|
452
|
+
|
|
453
|
+
| Category | Check |
|
|
454
|
+
|----------|-------|
|
|
455
|
+
| **Services** | Business rule violations throw appropriate errors |
|
|
456
|
+
| **Repositories** | Database errors are caught and wrapped |
|
|
457
|
+
| **Controllers** | Errors propagate to global handler |
|
|
458
|
+
| **Async** | All promises have error handling |
|
|
459
|
+
| **Transactions** | Always rollback on error |
|
|
460
|
+
| **Logging** | Errors logged with context |
|
|
461
|
+
| **Responses** | Consistent error format returned |
|
|
462
|
+
| **Security** | No sensitive data in error messages |
|
|
463
|
+
|
|
464
|
+
## See Also
|
|
465
|
+
|
|
466
|
+
- [Common Pitfalls](./common-pitfalls) - Error handling mistakes
|
|
467
|
+
- [Testing Strategies](./testing-strategies) - Testing error scenarios
|
|
468
|
+
- [Troubleshooting Tips](./troubleshooting-tips) - Debugging errors
|
|
@@ -1,27 +1,210 @@
|
|
|
1
1
|
# Best Practices
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Production-ready patterns, security guidelines, and optimization strategies for building robust Ignis applications. These best practices are distilled from real-world experience building enterprise applications.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<div class="guide-cards">
|
|
6
|
+
|
|
7
|
+
<a href="./architectural-patterns" class="guide-card highlight">
|
|
8
|
+
<span class="guide-icon">🏗️</span>
|
|
9
|
+
<h3>Architecture</h3>
|
|
10
|
+
<p>Layered architecture, DI, components, lifecycle hooks</p>
|
|
11
|
+
</a>
|
|
12
|
+
|
|
13
|
+
<a href="./code-style-standards/" class="guide-card">
|
|
14
|
+
<span class="guide-icon">📝</span>
|
|
15
|
+
<h3>Code Standards</h3>
|
|
16
|
+
<p>Naming, types, patterns, ESLint, Prettier</p>
|
|
17
|
+
</a>
|
|
18
|
+
|
|
19
|
+
<a href="./security-guidelines" class="guide-card highlight">
|
|
20
|
+
<span class="guide-icon">🔒</span>
|
|
21
|
+
<h3>Security</h3>
|
|
22
|
+
<p>Auth, validation, secrets, CORS, rate limiting</p>
|
|
23
|
+
</a>
|
|
24
|
+
|
|
25
|
+
<a href="./data-modeling" class="guide-card">
|
|
26
|
+
<span class="guide-icon">🗄️</span>
|
|
27
|
+
<h3>Data Modeling</h3>
|
|
28
|
+
<p>Schemas, enrichers, relations, migrations</p>
|
|
29
|
+
</a>
|
|
30
|
+
|
|
31
|
+
<a href="./testing-strategies" class="guide-card">
|
|
32
|
+
<span class="guide-icon">🧪</span>
|
|
33
|
+
<h3>Testing</h3>
|
|
34
|
+
<p>Unit tests, integration tests, mocking</p>
|
|
35
|
+
</a>
|
|
36
|
+
|
|
37
|
+
<a href="./performance-optimization" class="guide-card">
|
|
38
|
+
<span class="guide-icon">⚡</span>
|
|
39
|
+
<h3>Performance</h3>
|
|
40
|
+
<p>Query optimization, caching, pooling</p>
|
|
41
|
+
</a>
|
|
42
|
+
|
|
43
|
+
<a href="./error-handling" class="guide-card">
|
|
44
|
+
<span class="guide-icon">🚨</span>
|
|
45
|
+
<h3>Error Handling</h3>
|
|
46
|
+
<p>Error patterns, logging, user-friendly messages</p>
|
|
47
|
+
</a>
|
|
48
|
+
|
|
49
|
+
<a href="./deployment-strategies" class="guide-card">
|
|
50
|
+
<span class="guide-icon">🚀</span>
|
|
51
|
+
<h3>Deployment</h3>
|
|
52
|
+
<p>Docker, Kubernetes, cloud platforms, CI/CD</p>
|
|
53
|
+
</a>
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
## Learning Path
|
|
58
|
+
|
|
59
|
+
<div class="roadmap">
|
|
60
|
+
|
|
61
|
+
<div class="roadmap-stage">
|
|
62
|
+
<div class="stage-header">
|
|
63
|
+
<span class="stage-num">1</span>
|
|
64
|
+
<h4>Foundation</h4>
|
|
65
|
+
</div>
|
|
66
|
+
<p><a href="./architectural-patterns">Architecture</a> → <a href="./architecture-decisions">Decisions Guide</a> → <a href="./code-style-standards/">Code Standards</a></p>
|
|
67
|
+
<span class="stage-desc">Understand patterns and establish coding conventions</span>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="roadmap-stage">
|
|
71
|
+
<div class="stage-header">
|
|
72
|
+
<span class="stage-num">2</span>
|
|
73
|
+
<h4>Data Layer</h4>
|
|
74
|
+
</div>
|
|
75
|
+
<p><a href="./data-modeling">Data Modeling</a> → <a href="./api-usage-examples">API Patterns</a> → <a href="./error-handling">Error Handling</a></p>
|
|
76
|
+
<span class="stage-desc">Design your data layer and handle edge cases</span>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="roadmap-stage">
|
|
80
|
+
<div class="stage-header">
|
|
81
|
+
<span class="stage-num">3</span>
|
|
82
|
+
<h4>Quality Assurance</h4>
|
|
83
|
+
</div>
|
|
84
|
+
<p><a href="./testing-strategies">Testing</a> → <a href="./common-pitfalls">Avoid Pitfalls</a> → <a href="./troubleshooting-tips">Troubleshooting</a></p>
|
|
85
|
+
<span class="stage-desc">Write tests and prevent common mistakes</span>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="roadmap-stage">
|
|
89
|
+
<div class="stage-header">
|
|
90
|
+
<span class="stage-num">4</span>
|
|
91
|
+
<h4>Production Ready</h4>
|
|
92
|
+
</div>
|
|
93
|
+
<p><a href="./security-guidelines">Security</a> → <a href="./performance-optimization">Performance</a> → <a href="./deployment-strategies">Deployment</a></p>
|
|
94
|
+
<span class="stage-desc">Secure, optimize, and deploy your application</span>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
## Quick Reference
|
|
100
|
+
|
|
101
|
+
### Essential Patterns
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// ✅ Layered Architecture
|
|
105
|
+
Controller → Service → Repository → DataSource
|
|
106
|
+
|
|
107
|
+
// ✅ Dependency Injection
|
|
108
|
+
@inject({ key: BindingKeys.build({ namespace: BindingNamespaces.SERVICE, key: UserService.name }) })
|
|
109
|
+
|
|
110
|
+
// ✅ Error Handling
|
|
111
|
+
throw getError({ statusCode: 404, message: 'User not found' });
|
|
112
|
+
|
|
113
|
+
// ✅ Input Validation
|
|
114
|
+
request: { body: jsonContent({ schema: z.object({ email: z.string().email() }) }) }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Anti-Patterns to Avoid
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// ❌ Business logic in controllers
|
|
121
|
+
@get({ configs: RouteConfigs.GET_USER })
|
|
122
|
+
async getUser(c: Context) {
|
|
123
|
+
const user = await this.userRepo.findById(id);
|
|
124
|
+
if (user.lastLogin < cutoff) await this.sendReminder(user); // Move to service!
|
|
125
|
+
return c.json(user);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ❌ Catching all errors silently
|
|
129
|
+
try { await riskyOperation(); } catch (e) { /* swallowed */ }
|
|
130
|
+
|
|
131
|
+
// ❌ Using `any` type
|
|
132
|
+
const data: any = await fetchData(); // Use proper types!
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Security Checklist
|
|
136
|
+
|
|
137
|
+
| Check | Action |
|
|
138
|
+
|-------|--------|
|
|
139
|
+
| Secrets | Store in environment variables, never in code |
|
|
140
|
+
| Input | Validate with Zod schemas at API boundaries |
|
|
141
|
+
| Auth | Protect routes with `authStrategies: [Authentication.STRATEGY_JWT]` |
|
|
142
|
+
| Sensitive data | Use `hiddenProperties` in model settings |
|
|
143
|
+
| File uploads | Use `sanitizeFilename()` for all user-provided filenames |
|
|
144
|
+
| CORS | Configure allowed origins explicitly |
|
|
145
|
+
|
|
146
|
+
### Performance Checklist
|
|
147
|
+
|
|
148
|
+
| Check | Action |
|
|
149
|
+
|-------|--------|
|
|
150
|
+
| Queries | Use `fields` to select only needed columns |
|
|
151
|
+
| Pagination | Always set `limit` on find operations |
|
|
152
|
+
| Relations | Limit `include` depth to 2 levels max |
|
|
153
|
+
| Connection pool | Configure pool size based on load |
|
|
154
|
+
| Background jobs | Offload CPU-intensive tasks to workers |
|
|
155
|
+
| Caching | Cache expensive queries with Redis |
|
|
156
|
+
|
|
157
|
+
## All Best Practices
|
|
158
|
+
|
|
159
|
+
### Architecture & Design
|
|
160
|
+
|
|
161
|
+
| Guide | Description |
|
|
162
|
+
|-------|-------------|
|
|
163
|
+
| [Architectural Patterns](./architectural-patterns) | Layered architecture, DI, components, mixins |
|
|
164
|
+
| [Architecture Decisions](./architecture-decisions) | When to use services, repositories, components |
|
|
165
|
+
|
|
166
|
+
### Development
|
|
167
|
+
|
|
168
|
+
| Guide | Description |
|
|
169
|
+
|-------|-------------|
|
|
170
|
+
| [Code Style Standards](./code-style-standards/) | Naming conventions, types, ESLint, Prettier |
|
|
171
|
+
| [Data Modeling](./data-modeling) | Schema design, enrichers, relations, migrations |
|
|
172
|
+
| [API Usage Examples](./api-usage-examples) | Routing, repositories, middleware, services |
|
|
173
|
+
|
|
174
|
+
### Quality
|
|
6
175
|
|
|
7
176
|
| Guide | Description |
|
|
8
177
|
|-------|-------------|
|
|
9
|
-
| [
|
|
10
|
-
| [
|
|
11
|
-
| [
|
|
12
|
-
| [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
|
17
|
-
|
|
18
|
-
| [
|
|
19
|
-
| [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
178
|
+
| [Testing Strategies](./testing-strategies) | Unit tests, integration tests, mocking, E2E |
|
|
179
|
+
| [Error Handling](./error-handling) | Error patterns, structured errors, logging |
|
|
180
|
+
| [Common Pitfalls](./common-pitfalls) | Mistakes to avoid and how to fix them |
|
|
181
|
+
| [Troubleshooting Tips](./troubleshooting-tips) | Debug common issues quickly |
|
|
182
|
+
|
|
183
|
+
### Production
|
|
184
|
+
|
|
185
|
+
| Guide | Description |
|
|
186
|
+
|-------|-------------|
|
|
187
|
+
| [Security Guidelines](./security-guidelines) | Authentication, validation, secrets, CORS |
|
|
188
|
+
| [Performance Optimization](./performance-optimization) | Query optimization, caching, connection pooling |
|
|
189
|
+
| [Deployment Strategies](./deployment-strategies) | Docker, Kubernetes, cloud platforms, CI/CD |
|
|
190
|
+
|
|
191
|
+
### Contributing
|
|
192
|
+
|
|
193
|
+
| Guide | Description |
|
|
194
|
+
|-------|-------------|
|
|
195
|
+
| [Contribution Workflow](./contribution-workflow) | Git workflow, PR guidelines, code review |
|
|
196
|
+
|
|
197
|
+
::: tip New to Ignis?
|
|
198
|
+
Start with the [Getting Started Guide](/guides/) for tutorials, then return here for production-ready patterns.
|
|
199
|
+
:::
|
|
200
|
+
|
|
201
|
+
::: warning Production Deployment?
|
|
202
|
+
Before deploying, review the [Security Guidelines](./security-guidelines) and [Deployment Strategies](./deployment-strategies) thoroughly.
|
|
203
|
+
:::
|
|
204
|
+
|
|
205
|
+
## See Also
|
|
206
|
+
|
|
207
|
+
- [Getting Started](/guides/) - New to Ignis? Start here
|
|
208
|
+
- [API Reference](/references/) - Detailed API documentation
|
|
209
|
+
- [Core Concepts](/guides/core-concepts/application/) - Deep dive into architecture
|
|
210
|
+
- [Changelogs](/changelogs/) - Version history and updates
|