0
# Exception Handling
1
2
Microservice-specific exception classes and error handling patterns for distributed system error management, providing structured error responses and transport-specific error handling capabilities.
3
4
## Capabilities
5
6
### RPC Exception
7
8
Base exception class for RPC operations providing structured error handling across all transport types.
9
10
```typescript { .api }
11
/**
12
* Base exception class for RPC operations
13
*/
14
class RpcException extends Error {
15
/**
16
* Creates a new RPC exception
17
* @param error - Error message string or structured error object
18
*/
19
constructor(error: string | object);
20
21
/**
22
* Returns the error data
23
* @returns Original error message or object
24
*/
25
getError(): string | object;
26
}
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import { Controller } from '@nestjs/common';
33
import { MessagePattern, RpcException } from '@nestjs/microservices';
34
35
@Controller()
36
export class UserController {
37
@MessagePattern({ cmd: 'get_user' })
38
getUser(data: { id: number }): any {
39
if (!data.id) {
40
throw new RpcException('User ID is required');
41
}
42
43
const user = this.userService.findById(data.id);
44
if (!user) {
45
throw new RpcException({
46
code: 'USER_NOT_FOUND',
47
message: `User with ID ${data.id} not found`,
48
statusCode: 404
49
});
50
}
51
52
return user;
53
}
54
55
@MessagePattern({ cmd: 'create_user' })
56
createUser(data: any): any {
57
try {
58
this.validateUserData(data);
59
return this.userService.create(data);
60
} catch (validationError) {
61
throw new RpcException({
62
code: 'VALIDATION_ERROR',
63
message: 'Invalid user data provided',
64
details: validationError.details,
65
statusCode: 400
66
});
67
}
68
}
69
70
@MessagePattern({ cmd: 'update_user' })
71
async updateUser(data: { id: number; updates: any }): Promise<any> {
72
try {
73
const result = await this.userService.update(data.id, data.updates);
74
return result;
75
} catch (error) {
76
if (error.code === 'CONCURRENT_MODIFICATION') {
77
throw new RpcException({
78
code: 'CONFLICT',
79
message: 'User was modified by another process',
80
timestamp: new Date().toISOString(),
81
statusCode: 409
82
});
83
}
84
85
// Re-throw as RPC exception
86
throw new RpcException({
87
code: 'INTERNAL_ERROR',
88
message: 'Failed to update user',
89
originalError: error.message
90
});
91
}
92
}
93
}
94
```
95
96
### Kafka Retriable Exception
97
98
Specialized exception for Kafka operations that can be retried, enabling proper error handling in streaming scenarios.
99
100
```typescript { .api }
101
/**
102
* Kafka-specific exception for retriable errors
103
*/
104
class KafkaRetriableException extends RpcException {
105
/**
106
* Creates a new Kafka retriable exception
107
* @param error - Error message string or structured error object
108
*/
109
constructor(error: string | object);
110
}
111
```
112
113
**Usage Examples:**
114
115
```typescript
116
import { Controller, Logger } from '@nestjs/common';
117
import { EventPattern, MessagePattern, Payload, Ctx } from '@nestjs/microservices';
118
import { KafkaRetriableException, KafkaContext } from '@nestjs/microservices';
119
120
@Controller()
121
export class KafkaErrorController {
122
private readonly logger = new Logger(KafkaErrorController.name);
123
124
@EventPattern('user.events.process')
125
async processUserEvent(
126
@Payload() event: any,
127
@Ctx() context: KafkaContext
128
): Promise<void> {
129
try {
130
await this.processEvent(event);
131
} catch (error) {
132
if (this.isRetriableError(error)) {
133
// Throw retriable exception - Kafka will retry
134
throw new KafkaRetriableException({
135
code: 'TEMPORARY_FAILURE',
136
message: 'Temporary processing failure, will retry',
137
originalError: error.message,
138
retryCount: this.getRetryCount(context),
139
topic: context.getTopic(),
140
partition: context.getPartition()
141
});
142
} else {
143
// Non-retriable error - log and continue
144
this.logger.error('Non-retriable error processing event:', error);
145
this.deadLetterService.send(event, error);
146
}
147
}
148
}
149
150
@MessagePattern('user.commands.process')
151
async processUserCommand(
152
@Payload() command: any,
153
@Ctx() context: KafkaContext
154
): Promise<any> {
155
const maxRetries = 3;
156
const retryCount = this.getRetryCount(context);
157
158
try {
159
return await this.commandService.process(command);
160
} catch (error) {
161
if (retryCount < maxRetries && this.shouldRetry(error)) {
162
throw new KafkaRetriableException({
163
code: 'PROCESSING_FAILED',
164
message: `Command processing failed, retry ${retryCount + 1}/${maxRetries}`,
165
commandId: command.id,
166
retryCount: retryCount,
167
maxRetries: maxRetries,
168
error: error.message
169
});
170
} else {
171
// Max retries reached or non-retriable error
172
throw new RpcException({
173
code: 'COMMAND_FAILED',
174
message: 'Command processing failed permanently',
175
commandId: command.id,
176
retryCount: retryCount,
177
finalError: error.message
178
});
179
}
180
}
181
}
182
183
private isRetriableError(error: any): boolean {
184
// Define logic for determining if error is retriable
185
return error.code === 'ECONNRESET' ||
186
error.code === 'ETIMEDOUT' ||
187
error.statusCode === 503 ||
188
error.statusCode === 429;
189
}
190
191
private shouldRetry(error: any): boolean {
192
return error.temporary === true ||
193
error.statusCode >= 500 ||
194
error.code === 'NETWORK_ERROR';
195
}
196
197
private getRetryCount(context: KafkaContext): number {
198
const message = context.getMessage();
199
const retryHeader = message.headers?.['retry-count'];
200
return retryHeader ? parseInt(retryHeader.toString()) : 0;
201
}
202
}
203
```
204
205
### Base RPC Exception Filter
206
207
Base exception filter for handling RPC exceptions across different transport types.
208
209
```typescript { .api }
210
/**
211
* Base exception filter for RPC operations
212
*/
213
class BaseRpcExceptionFilter {
214
/**
215
* Catches and processes RPC exceptions
216
* @param exception - The thrown exception
217
* @param host - Execution context host
218
*/
219
catch(exception: RpcException, host: any): any;
220
}
221
```
222
223
**Usage Examples:**
224
225
```typescript
226
import { Catch, RpcExceptionFilter, ArgumentsHost } from '@nestjs/common';
227
import { Observable, throwError } from 'rxjs';
228
import { RpcException } from '@nestjs/microservices';
229
230
@Catch(RpcException)
231
export class CustomRpcExceptionFilter implements RpcExceptionFilter<RpcException> {
232
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
233
const ctx = host.switchToRpc();
234
const data = ctx.getData();
235
const error = exception.getError();
236
237
// Log the error
238
console.error('RPC Exception:', {
239
error,
240
requestData: data,
241
timestamp: new Date().toISOString()
242
});
243
244
// Format error response
245
const errorResponse = {
246
success: false,
247
error: typeof error === 'string' ? { message: error } : error,
248
timestamp: new Date().toISOString(),
249
requestId: this.generateRequestId()
250
};
251
252
return throwError(() => errorResponse);
253
}
254
255
private generateRequestId(): string {
256
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
257
}
258
}
259
260
// Usage in main.ts or module
261
import { NestFactory } from '@nestjs/core';
262
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
263
264
async function bootstrap() {
265
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
266
AppModule,
267
{
268
transport: Transport.TCP,
269
options: { port: 3001 },
270
},
271
);
272
273
app.useGlobalFilters(new CustomRpcExceptionFilter());
274
await app.listen();
275
}
276
```
277
278
### Transport-Specific Error Handling
279
280
Different error handling patterns for various transport types.
281
282
**TCP Error Handling:**
283
284
```typescript
285
import { Controller } from '@nestjs/common';
286
import { MessagePattern, RpcException, Ctx, TcpContext } from '@nestjs/microservices';
287
288
@Controller()
289
export class TcpErrorController {
290
@MessagePattern({ cmd: 'risky_operation' })
291
performRiskyOperation(
292
data: any,
293
@Ctx() context: TcpContext
294
): any {
295
try {
296
return this.processData(data);
297
} catch (error) {
298
// TCP-specific error handling
299
const socket = context.getSocketRef();
300
301
throw new RpcException({
302
code: 'TCP_PROCESSING_ERROR',
303
message: error.message,
304
clientAddress: socket.socket.remoteAddress,
305
clientPort: socket.socket.remotePort,
306
timestamp: new Date().toISOString()
307
});
308
}
309
}
310
}
311
```
312
313
**Redis Error Handling:**
314
315
```typescript
316
@Controller()
317
export class RedisErrorController {
318
@MessagePattern('cache:operation')
319
performCacheOperation(
320
data: any,
321
@Ctx() context: RedisContext
322
): any {
323
try {
324
return this.cacheService.process(data);
325
} catch (error) {
326
throw new RpcException({
327
code: 'REDIS_ERROR',
328
message: 'Cache operation failed',
329
channel: context.getChannel(),
330
error: error.message,
331
retryable: error.code === 'ECONNRESET'
332
});
333
}
334
}
335
}
336
```
337
338
**gRPC Error Handling:**
339
340
```typescript
341
import { status } from '@grpc/grpc-js';
342
343
@Controller()
344
export class GrpcErrorController {
345
@GrpcMethod('UserService', 'GetUser')
346
getUser(data: { id: string }): any {
347
try {
348
const user = this.userService.findById(data.id);
349
if (!user) {
350
throw new RpcException({
351
code: status.NOT_FOUND,
352
message: `User with ID ${data.id} not found`,
353
details: 'USER_NOT_FOUND'
354
});
355
}
356
return user;
357
} catch (error) {
358
if (error.name === 'ValidationError') {
359
throw new RpcException({
360
code: status.INVALID_ARGUMENT,
361
message: 'Invalid request data',
362
details: error.details
363
});
364
}
365
366
throw new RpcException({
367
code: status.INTERNAL,
368
message: 'Internal server error'
369
});
370
}
371
}
372
}
373
```
374
375
### Error Response Patterns
376
377
Common patterns for structuring error responses across different scenarios.
378
379
```typescript
380
// Standard error response structure
381
interface ErrorResponse {
382
success: false;
383
error: {
384
code: string;
385
message: string;
386
details?: any;
387
timestamp: string;
388
requestId?: string;
389
};
390
}
391
392
// Validation error structure
393
interface ValidationErrorResponse extends ErrorResponse {
394
error: {
395
code: 'VALIDATION_ERROR';
396
message: string;
397
fields: Array<{
398
field: string;
399
message: string;
400
value?: any;
401
}>;
402
timestamp: string;
403
};
404
}
405
406
// Business logic error structure
407
interface BusinessErrorResponse extends ErrorResponse {
408
error: {
409
code: string;
410
message: string;
411
businessRules: string[];
412
timestamp: string;
413
context?: Record<string, any>;
414
};
415
}
416
417
// Usage examples
418
@Controller()
419
export class StructuredErrorController {
420
@MessagePattern({ cmd: 'validate_and_process' })
421
validateAndProcess(data: any): any {
422
// Validation errors
423
const validationErrors = this.validate(data);
424
if (validationErrors.length > 0) {
425
throw new RpcException({
426
code: 'VALIDATION_ERROR',
427
message: 'Request validation failed',
428
fields: validationErrors,
429
timestamp: new Date().toISOString()
430
});
431
}
432
433
// Business logic errors
434
if (!this.checkBusinessRules(data)) {
435
throw new RpcException({
436
code: 'BUSINESS_RULE_VIOLATION',
437
message: 'Business rules validation failed',
438
businessRules: ['INSUFFICIENT_BALANCE', 'ACCOUNT_SUSPENDED'],
439
timestamp: new Date().toISOString(),
440
context: { accountId: data.accountId, amount: data.amount }
441
});
442
}
443
444
return this.processData(data);
445
}
446
}
447
```