Skip to content

Commit 6541015

Browse files
authored
Merge pull request #33 from Mkalbani/feat/error_handling
added global validation pipe and exception filter, and implement test validation controller
2 parents 9a53b53 + 5dd4372 commit 6541015

File tree

8 files changed

+109
-8
lines changed

8 files changed

+109
-8
lines changed

package-lock.json

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { AuthModule } from './auth/auth.module';
1414
import { UserModule } from './user/user.module';
1515
import { AppController } from './app.controller';
1616
import { AppService } from './app.service';
17+
import { TestValidationController } from './test-validation.controller';
1718
import { NotificationsModule } from './notifications/notifications.module';
1819
import { OffersModule } from './offers/offers.module';
1920

@@ -35,7 +36,7 @@ import { OffersModule } from './offers/offers.module';
3536
NotificationsModule,
3637
OffersModule,
3738
],
38-
controllers: [AppController],
39+
controllers: [AppController, TestValidationController],
3940
providers: [AppService],
4041
})
4142
export class AppModule {}

src/auth/auth.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Injectable, BadRequestException } from '@nestjs/common';
22
import { InjectRepository } from '@nestjs/typeorm';
33
import { Repository } from 'typeorm';
44
import { RegisterDto } from './dtos/register.dto';
5-
import { User } from 'src/user/user.entity';
5+
import { User } from '../user/user.entity';
66
import * as bcrypt from 'bcryptjs';
77
import { generateVerificationToken, verifyJwtToken } from '../utils/jwt.util';
88
import { MailService } from '../mail/mail.service';

src/main.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ async function bootstrap() {
1616
credentials: true,
1717
});
1818

19-
await app.listen(process.env.PORT ?? 3000);
19+
// Global Validation Pipe
20+
const { ValidationPipe } = await import('@nestjs/common');
21+
app.useGlobalPipes(new ValidationPipe({
22+
whitelist: true,
23+
forbidNonWhitelisted: true,
24+
transform: true,
25+
}));
26+
27+
// Global Exception Filter
28+
const { AllExceptionsFilter } = await import('./utils/all-exceptions.filter');
29+
app.useGlobalFilters(new AllExceptionsFilter());
30+
31+
await app.listen(process.env.PORT ?? 3000);
2032
}
2133
bootstrap();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { NotificationsService } from './notifications.service';
3+
import { NotificationsGateway } from './notifications.gateway';
4+
import { getRepositoryToken } from '@nestjs/typeorm';
5+
import { Notification } from './entities/notification.entity';
6+
import { Repository } from 'typeorm';
7+
8+
describe('NotificationsService', () => {
9+
let service: NotificationsService;
10+
11+
beforeEach(async () => {
12+
const module: TestingModule = await Test.createTestingModule({
13+
providers: [
14+
NotificationsService,
15+
NotificationsGateway,
16+
{ provide: getRepositoryToken(Notification), useClass: Repository },
17+
],
18+
}).compile();
19+
service = module.get<NotificationsService>(NotificationsService);
20+
});
21+
22+
it('should be defined', () => {
23+
expect(service).toBeDefined();
24+
});
25+
});

src/password-reset/password-reset.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { PasswordResetController } from './password-reset.controller';
55
import { PasswordResetService } from './password-reset.service';
66
import { PasswordResetToken } from './entities/password-reset-token.entity';
77
import { EmailModule } from '../email/email.module';
8-
import { throttlerConfig } from 'src/config/throttler.config';
9-
import { UserModule } from 'src/user/user.module';
8+
import { throttlerConfig } from '../config/throttler.config';
9+
import { UserModule } from '../user/user.module';
1010

1111
@Module({
1212
imports: [

src/test-validation.controller.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Controller, Post, Body } from '@nestjs/common';
2+
import { IsEmail, IsNotEmpty } from 'class-validator';
3+
4+
class TestDto {
5+
@IsEmail()
6+
email: string;
7+
8+
@IsNotEmpty()
9+
name: string;
10+
}
11+
12+
@Controller('test-validation')
13+
export class TestValidationController {
14+
@Post()
15+
test(@Body() dto: TestDto) {
16+
return { message: 'Validation passed', data: dto };
17+
}
18+
}

src/utils/all-exceptions.filter.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {
2+
ExceptionFilter,
3+
Catch,
4+
ArgumentsHost,
5+
HttpException,
6+
HttpStatus,
7+
} from '@nestjs/common';
8+
import { Request, Response } from 'express';
9+
import { Logger } from '@nestjs/common';
10+
11+
@Catch()
12+
export class AllExceptionsFilter implements ExceptionFilter {
13+
private readonly logger = new Logger(AllExceptionsFilter.name);
14+
15+
catch(exception: unknown, host: ArgumentsHost) {
16+
const ctx = host.switchToHttp();
17+
const response = ctx.getResponse<Response>();
18+
const request = ctx.getRequest<Request>();
19+
20+
let status = HttpStatus.INTERNAL_SERVER_ERROR;
21+
let message = 'Internal server error';
22+
let errorResponse: any = {};
23+
24+
if (exception instanceof HttpException) {
25+
status = exception.getStatus();
26+
const exceptionResponse = exception.getResponse();
27+
if (typeof exceptionResponse === 'string') {
28+
message = exceptionResponse;
29+
} else if (typeof exceptionResponse === 'object') {
30+
errorResponse = exceptionResponse;
31+
message = (exceptionResponse as any).message || message;
32+
}
33+
} else if (exception instanceof Error) {
34+
message = exception.message;
35+
}
36+
37+
this.logger.error(`Status: ${status} Error: ${message}`, exception instanceof Error ? exception.stack : '');
38+
39+
response.status(status).json({
40+
statusCode: status,
41+
timestamp: new Date().toISOString(),
42+
path: request.url,
43+
message,
44+
...errorResponse,
45+
});
46+
}
47+
}

0 commit comments

Comments
 (0)