How to attach a class level filter with dependency injection? - nestjs

I am trying to catch failures within a specific controller with filters. My filter requires access to another service (to save in db) and I am not sure how to have a class level filter with dependency injection (DI so that the filter has access to the service).
I've currently passing the service from the controller where I use the decorator UseFilters but realized that decorators don't share the same scope.
#UseFilters(new UnprocessableEntityExceptionFilter(myService))
#Catch(UnprocessableEntityException)
export class UnprocessableEntityExceptionFilter implements ExceptionFilter {
constructor(private readonly requestsService: RequestsService) { }
async catch(exception: UnprocessableEntityException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
const body = exception.message;
response.status(status).json(body);
await this.requestsService.create(request, response, body);
}
}
And I want to use this filter at the class level like this...
#UseFilters(new UnprocessableEntityExceptionFilter())
export class EventsController {
constructor() { }
#Get()
async get() {
...
}
But I clearly cannot create a new instance of UnprocessableEntityExceptionFilter because it requires dependency injection.
I understand the documentation tells us to use this method when filters have dependency injection, but I don't want this filter to be global.
#Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})

I ended up using this
#UseFilters(UnprocessableEntityExceptionFilter)
export class EventsController {}

Related

How would I mock the DataSource from TypeORM in NestJS Jest tests?

I am trying to write tests for a small project in NestJS. Here is the relevant code for context:
dummy.controller.ts
#Controller(UrlConstants.BASE_URL + 'dummy')
export class DummyContoller {
constructor(
private readonly sessionService: SessionService,
) { }
#Get('validateSession')
async checkValidateSession(#Query('sessionId') sessionId: string) {
const session = await this.sessionService.validateSession(sessionId);
console.log(session);
return { message: "OK" };
}
}
session.service.ts
#Injectable()
export class SessionService {
constructor(
private readonly sessionRepo: SessionRepository,
private readonly accountRepo: AccountRepository
) { }
#WithErrorBoundary(AuthCodes.UNKNOWN_LOGIN_ERROR)
async validateSession(sessionId: string) {
const session = await this.sessionRepo.findOneBy({ sessionId });
if (!session || this.isSessionExpired(session)) {
session && await this.sessionRepo.remove(session);
throw new HttpException({
code: AuthCodes.SESSION_TIMEOUT,
message: AuthMessages.SESSION_TIMEOUT
}, HttpStatus.UNAUTHORIZED)
}
return session;
}
...
}
session.repository.ts (Any repository)
#Injectable()
export class SessionRepository extends Repository<Session> {
constructor(private dataSource: DataSource) {
super(Session, dataSource.createEntityManager())
}
...
}
This is how I wrote my test (this is my first time writing a test using Jest and I am not really experienced in writing tests in general):
describe('DummyController', () => {
let dummyContoller: DummyContoller;
let sessionService: SessionService;
let sessionRepo: SessionRepository;
let accountRepo: AccountRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [DummyContoller],
providers: [SessionService, SessionRepository, AccountRepository]
}).compile();
dummyContoller = module.get<DummyContoller>(DummyContoller);
sessionService = module.get<SessionService>(SessionService);
sessionRepo = module.get<SessionRepository>(SessionRepository);
accountRepo = module.get<AccountRepository>(AccountRepository);
})
describe('checkValidateSession', () => {
it('should return valid session', async () => {
const sessionId = "sessionId1";
const session = new Session();
jest.spyOn(sessionService, 'validateSession').mockImplementation(async (sessionId) => session);
expect(await dummyContoller.checkValidateSession(sessionId)).toBe(session);
})
})
})
Upon running the test, I encounter:
Nest can't resolve dependencies of the SessionRepository (?). Please make sure that the argument DataSource at index [0] is available in the RootTestModule context.
Potential solutions:
- If DataSource is a provider, is it part of the current RootTestModule?
- If DataSource is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing DataSource */ ]
})
I looked this problem and I came across a number of solutions but most of them had #InjectRepository() instead of creating a separate Repository class where they would provide getRepositoryToken() and then use a mock factory [Link]. I couldn't find a way to make this work.
Another solution suggested using an in-memory database solution [Link]. But this felt more like a hack rather than a solution.
How can I test the above setup?
Based on this comment, I was able to get this working by using the following in the providers in the test:
providers: [
SessionService,
{ provide: SessionRepository, useClass: SessionMockRepository },
]
SessionMockRepository contains a mocked version of all additional functions in that particular repository:
export class SessionMockRepository extends Repository<Session> {
someFunction = async () => jest.fn();
}
Currently, this works for me so I am accepting this. I am still open to more answers if there is a better way to do this.

NestJS v9: implement durable providers

[SOLVED] I'm pretty new to NestJS and trying to get my head around durable providers but i can't get them to work.
My scenario is that i have a service with some logic and two providers that implement the same interface to get some data. Depending on a custom header value i want to use Provider1 or Provider2 and the service itself does not have to know about the existing provider implementations.
Since i'm in a request scoped scenario but i know there are only 2 possible dependency-subtrees i want to use durable providers that the dependencies are not newly initialised for each request but reused instead.
I set up the ContextIdStrategy as described in the official docs and it is executed on each request but i miss the part how to connect my provider implementations with the ContextSubtreeIds created in the ContextIdStrategy.
Interface:
export abstract class ITest {
abstract getData(): string;
}
Implementations:
export class Test1Provider implements ITest {
getData() {
return "TEST1";
}
}
export class Test2Provider implements ITest {
getData() {
return "TEST2";
}
}
Service:
#Injectable()
export class AppService {
constructor(private readonly testProvider: ITest) {}
getHello(): string {
return this.testProvider.getData();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getData(): string {
return this.appService.getData();
}
}
ContextIdStrategy:
const providers = new Map<string, ContextId>([
["provider1", ContextIdFactory.create()],
["provider2", ContextIdFactory.create()],
]);
export class AggregateByProviderContextIdStrategy implements ContextIdStrategy {
attach(contextId: ContextId, request: Request) {
const providerId = request.headers["x-provider-id"] as string;
let providerSubTreeId: ContextId;
if (providerId == "provider1") {
providerSubTreeId = providers["provider1"];
} else if (providerId == "provider2") {
providerSubTreeId = providers["provider2"];
} else {
throw Error(`x-provider-id ${providerId} not supported`);
}
// If tree is not durable, return the original "contextId" object
return (info: HostComponentInfo) =>
info.isTreeDurable ? providerSubTreeId : contextId;
}
}
Main:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
ContextIdFactory.apply(new AggregateByProviderContextIdStrategy());
await app.listen(3000);
}
bootstrap();
Module:
#Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: ITest,
useFactory: () => {
// THIS IS THE MISSING PIECE.
// Return either Test1Provider or Test2Provider based on the ContextSubtreeId
// which is created by the ContextIdStrategy
return new Test1Provider();
},
},
AppService,
],
})
export class AppModule {}
The missing part was a modification of the ContextIdStrategy return statement:
return {
resolve: (info: HostComponentInfo) => {
const context = info.isTreeDurable ? providerSubTreeId : contextId;
return context;
},
payload: { providerId },
}
after that change, the request object can be injected in the module and where it will only contain the providerId property and based on that, the useFactory statement can return different implementations

Get current user in nestjs on a route without an AuthGuard

I use nestjs with passport with jwt strategy. And I want to get a current user on some of my requests.
Currently, I have a decorator that looks like this:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const user = ctx.switchToHttp().getRequest().user;
if (!user) {
return null;
}
return data ? user[data] : user; // extract a specific property only if specified or get a user object
},
);
It works as intended when i use it on a route with an AuthGuard:
#Get('test')
#UseGuards(AuthGuard())
testRoute(#CurrentUser() user: User) {
console.log('Current User: ', user);
return { user };
}
But how do i make it work (get current user) on non-guarded routes? I need users to be able to post their comments regardless of if they are authorized or not, however, when they are logged in, i need to get their name.
Basically, I need a way to propagate req.user on every(or at least on some of not AuthGuard'ed request), it is really straight forward to do in express by applying passport middleware, but I'm not sure how to do it with #nestjs/passport.
[EDIT]
Thanks to vpdiongzon for pointing me in the right direction, I chose to make a guard based on his answer, that just populates req.user with either user or null:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class ApplyUser extends AuthGuard('jwt') {
handleRequest(err: any, user: any) {
if (user) return user;
return null;
}
}
And now I could just use it on any unprotected route that needs to get the current user
#Get('me')
#UseGuards(ApplyUser)
me(#CurrentUser() user: User) {
return { user };
}
You need to apply your AuthGuard to every route regardless but if you have a route that don't require authentication just add a custom decorator, example:
the Auth Guard
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
handleRequest(err, user, info, context) {
const request = context.switchToHttp().getRequest();
const allowAny = this.reflector.get<string[]>('allow-any', context.getHandler());
if (user) return user;
if (allowAny) return true;
throw new UnauthorizedException();
}
}
Apply globally the AuthGuard in app.module.js
import { APP_GUARD, Reflector } from '#nestjs/core';
import { AppController } from './app.controller';
import { JwtAuthGuard } from './app.guard';
#Module({
imports: ],
controllers: [AppController],
providers: [
{
provide: APP_GUARD,
useFactory: ref => new JwtAuthGuard(ref),
inject: [Reflector],
},
AppService,
],
})
export class AppModule {
}
The custom decorator to allow a route without authentication
import { SetMetadata } from '#nestjs/common';
export const AllowAny = () => SetMetadata('allow-any', true);
Apply AllowAny in a route, if AllowAny decorator is not attached in a controller route it will required a user.
#Post('testPost')
#AllowAny()
async testPost(#Req() request) {
console.log(request.user)
}
"Basically, I need a way to propagate req.user on every(or at least on some of not AuthGuard'ed request), it is realy straight forward to do in express by applying passport middleware, but im not sure how to do it with #nestjs/passport."
To achieve this we write an interceptor because we need to use the UsersService. UserService is part of the dependency injection system. We cannot just import the user service and create a new instance of it ourselves. The service makes use of the users repository and that users repository is setup only through dependency injection.
The thing is we cannot make use of dependency injection with a parameter decorator. This decorator cannot reach into the system in any way and try to get access to some instance of anything inside there. This is how we write the interceptor. I make comments on the code:
// this interceptor will be used by the custom param decoratro to fetch the current User
import {NestInterceptor,ExecutionContext,CallHandler,Injectable} from '#nestjs/common';
import { UsersService } from '../users.service';
#Injectable()
// "implements" guide us how to put together an interceptor
export class CurrentUserInterceptor implements NestInterceptor {
constructor(private userService: UsersService) {}
// handler refers to the route handler
async intercept(context: ExecutionContext, handler: CallHandler) {
const request = context.switchToHttp().getRequest();
const { userId } = request.session || {};
if (userId) {
const user = await this.userService.findOne(userId);
// we need to pass this down to the decorator. SO we assign the user to request because req can be retrieved inside the decorator
// ------THIS IS WHAT YOU WANTED--------
request.currentUser = user;
}
// run the actual route handler
return handler.handle();
}
}
Now you need to register this to the module:
#Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService, AuthService, CurrentUserInterceptor],
})
Inside controller:
#Controller('auth')
#UseInterceptors(CurrentUserInterceptor)
export class UsersController {
constructor("inject services) {}
#Get('/me')
me(#CurrentUser() user: User) {
return user;
}
}
In any route handler you use CurrentUser param decorator, you will have access to "user".
You actually do not need to write a custom param decorator
you could just use the interceptor, its implementation would be different:
#Get('/me')
me(#CurrentUserInterceptor() request: Request) {
// You have access to request.currentUser
return request.currentUser
}
Set interceptor globally
The current setup for the interceptor is tedious. We are applying the interceptor to one controller at a time. (Thats called controlled scope) Instead you could globally make this interceptor available:
users Module:
import { APP_INTERCEPTOR } from '#nestjs/core';
#Module({
// this createes repository
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [
UsersService,
AuthService,
{
provide: APP_INTERCEPTOR,
useClass: CurrentUserInterceptor,
},
],
})
This approach has one downside. Not every controller cares about what the current user is. In those controllers, you still have to make request to the database to fetch the current User.
the parsed userinfo stored in request.user
import {Req} from '#nestjs/common'
import { Request } from 'express'
#Post()
create(#Req() request: Request) {
console.log('user', request.user)
}

How To Mock Repository, Service and Controller In NestJS (Typeorm & Jest)

I'm new at typescript. My Nestjs project app is something like this. I'm trying to use repository pattern, so i separated business logic (service) and persistance logic (repository)
UserRepository
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from './entities/user.entity';
#Injectable()
export class UserRepo {
constructor(#InjectRepository(UserEntity) private readonly repo: Repository<UserEntity>) {}
public find(): Promise<UserEntity[]> {
return this.repo.find();
}
}
UserService
import { Injectable } from '#nestjs/common';
import { UserRepo } from './user.repository';
#Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepo) {}
public async get() {
return this.userRepo.find();
}
}
UserController
import { Controller, Get } from '#nestjs/common';
import { UserService } from './user.service';
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
// others method //
#Get()
public async getUsers() {
try {
const payload = this.userService.get();
return this.Ok(payload);
} catch (err) {
return this.InternalServerError(err);
}
}
}
How do i create unit testing for repository, service & controller without actually persist or retrieve data to DB (using mock)?
Mocking in NestJS is pretty easily obtainable using the testing tools Nest exposes is #nestjs/testing. In short, you'll want to create a Custom Provider for the dependency you are looking to mock, and that's all there is. However, it's always better to see an example, so here is a possibility of a mock for the controller:
describe('UserController', () => {
let controller: UserController;
let service: UserService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
get: jest.fn(() => mockUserEntity) // really it can be anything, but the closer to your actual logic the better
}
}
]
}).compile();
controller = moduleRef.get(UserController);
service = moduleRef.get(UserService);
});
});
And from there you can go on and write your tests. This is pretty much the same set up for all tests using Nest's DI system, the only thing to be aware of is things like #InjectRepository() and #InjectModel() (Mongoose and Sequilize decorators) where you'll need to use getRepositoryToken() or getModelToken() for the injection token. If you're looking for more exmaples take a look at this repository

Nest JS unable to inject service into guard if used in module

I created basic AuthGuard, but can't inject TokenService. I am getting this error:
Error: Nest can't resolve dependencies of the AuthGuard (?). Please verify whether [0] argument is available in the current context.
app.module.ts:
#Module({
modules: [
WorkModule,
],
components: [TokenService],
})
export class ApplicationModule { }
auth.guard.ts:
#Guard()
export class AuthGuard implements CanActivate {
constructor(
private readonly tokenService: TokenService,
) { }
public canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
work.module.ts:
#Module({
controllers: [WorkController],
components: [WorkService],
})
export class WorkModule { }
Update, work.service.ts:
import { Component, Inject, HttpStatus, HttpException } from '#nestjs/common';
const dataStore = require('nedb');
const workDB = new dataStore({ filename: '../db/work.db', autoload: true });
import * as moment from 'moment';
import { WorkDay, WorkDayDTO } from './work.model';
import { WorkHelpers } from './work.helpers';
#Component()
export class WorkService {
public async getWorkGraphic(month: number, year: number) {
return new Promise((resolve, reject) => {
// logic here
});
}
public async addOrUpdateWorkDay(day: WorkDayDTO) {
return new Promise((resolve, reject) => {
// logic here
});
}
public async removeWorkDay(workDayId: string) {
return new Promise((resolve, reject) => {
// logic here
});
}
}
But with this configuration everything is working:
#Module({
controllers: [
WorkController,
],
components: [TokenService, WorkService],
})
export class ApplicationModule { }
What exactly is causing this error and how can I get it work with 1st solution (Modules) ?
Is possible to show your TokenService and WorkerService?
You should register both always in your components to use inside all of application scope.
If you are using inside a specific module and trying to use in another module, probably you will not be able.
Another scenario. Imagine if you have A component registered in A module, B component registered in B module and imagine if ure trying to use A component inside of B module, you cant do that unless you register in Application Module or register inside A component inside B module(dont do that, only shared services should be used in all of the scopes, is just an architecture overview).

Resources