How to create a NestJs Pipe with a config object and dependency? - nestjs

I would Like to pass a configuration string to a Pipe but also want to inject a service. The NesJs docs describe how to do both of these independent of each other but not together. Take the following example:
pipe.ts
#Injectable()
export class FileExistsPipe implements PipeTransform {
constructor(private filePath: string, db: DatabaseService) { }
async transform(value: any, metadata: ArgumentMetadata) {
const path = value[this.filePath];
const doesExist = await this.db.file(path).exists()
if(!doesExist) throw new BadRequestException();
return value;
}
}
controller.ts
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
#Body( new FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
Basically, I want to be able to pass a property name to my pipe (e.g.'input') and then have the pipe look up the value of the property in the request (e.g.const path = value[this.filePath]) and then look to see if the file exists or not in the database. If it doesn't, throw a Bad Request error, otherwise continue.
The issue I am facing is that I need NestJs to inject my DataBaseService. With the current example, It won't and my IDE gives me an error that new FileExistsPipe('input') only has one argument passed but was expecting two (e.g. DatabaseService).
Is there anyway to achieve this?

EDIT: I just checked your repo (sorry for missing it before). Your DatabaseService is undefined in the FIleExistPipe because you use the pipe in AppController. AppController will be resolved before the DatabaseModule gets resolved. You can use forwardRef() to inject the DatabaseService in your pipe if you are going to use the pipe in AppController. The good practice here is to have feature controllers provided in feature modules.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(
createFileExistPipe
);
function createFileExistPipe(filePath: string): Type<PipeTransform> {
class MixinFileExistPipe implements PipeTransform {
constructor(
// use forwardRef here
#Inject(forwardRef(() => DatabaseService)) private db: DatabaseService
) {
console.log(db);
}
async transform(value: ITranscodeRequest, metadata: ArgumentMetadata) {
console.log(filePath, this.db);
const doesExist = await this.db.checkFileExists(filePath);
if (!doesExist) throw new BadRequestException();
return value;
}
}
return mixin(MixinFileExistPipe);
}
You can achieve this with Mixin. Instead of exporting an injectable class, you'd export a factory function that would return such class.
export const FileExistPipe: (filePath: string) => PipeTransform = memoize(createFileExistPipe);
function createFileExistPipe(filePath: string) {
class MixinFileExistPipe implements PipeTransform {
constructor(private db: DatabaseService) {}
...
}
return mixin(MixinFileExistPipe);
}
memoize is just a simple function to cache the created mixin-pipe with the filePath. So for each filePath, you'd only have a single version of that pipe.
mixin is a helper function imported from nestjs/common which will wrap the MixinFileExistPipe class and make the DI container available (so DatabaseService can be injected).
Usage:
#Controller('transcode')
export class TranscodeController {
#Post()
async transcode (
// notice, there's no "new"
#Body(FileExistsPipe('input')) transcodeRequest: JobRequest) {
return await this.videoProducer.addJob(transcodeRequest);
}
a mixin guard injecting the MongoDB Connection
the console shows the connection being logged

Related

NestJS lifecycle methods invoked without implementing their interface

I am having a small question about NestJS. In my code, there is a service which looks something like:
`
import { Inject, Injectable } from '#nestjs/common';
import neo4j, { Driver, int, Result, Transaction } from 'neo4j-driver';
import { Neo4jConfig } from './neo4j-config.interface';
import { NEO4J_CONFIG, NEO4J_DRIVER } from './neo4j.constants';
#Injectable()
export class Neo4jService {
constructor(
#Inject(NEO4J_CONFIG) private readonly config: Neo4jConfig,
#Inject(NEO4J_DRIVER) private readonly driver: Driver,
) {}
onApplicationBootstrap() {
console.log('Hello');
}
getDriver(): Driver {
return this.driver;
}
getConfig(): Neo4jConfig {
return this.config;
}
int(value: number) {
return int(value);
}
beginTransaction(database?: string): Transaction {
const session = this.getWriteSession(database);
return session.beginTransaction();
}
getReadSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.READ,
});
}
getWriteSession(database?: string) {
return this.driver.session({
database: database || this.config.database,
defaultAccessMode: neo4j.session.WRITE,
});
}
read(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getReadSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
write(
cypher: string,
params?: Record<string, unknown>,
databaseOrTransaction?: string | Transaction,
): Result {
if (databaseOrTransaction instanceof Transaction) {
return (<Transaction>databaseOrTransaction).run(cypher, params);
}
const session = this.getWriteSession(<string>databaseOrTransaction);
return session.run(cypher, params);
}
private onApplicationShutdown() {
console.log('Goodbye')
return this.driver.close();
}
}
`
Then in my main.ts file I have this method called:
`
await app.listen(port);
`
As you can see my service does not implement neither onApplicationBootstrap nor onApplicationShutdown.
How does it come that those methods still get invoked? Should I implement onApplicationBootstrap and onApplicationShutdown or not?
As you can also see I' d like that my onApplicationBootstrap is a private method which would not be possible if I implement the interface.
So, I would like to ask you:
Why the two lifecycle methods get called event without implementing the interface?
Should I implement those interfaces at all or just go on and use the methods which would allow me to define them as private?
I expected those methods to not work without implementing the interfaces
The Typescript interface is there to help us as devs. It doesn't exist at runtime, there's no information about it, so the only thing Nest can do is just check "Hey, does this class have the onModuleInit method?" If yes, add it to a list of classes to call onModuleInit. Do the same with the other lifecycle methods.
The interfaces aren't explicitly necessary, but they do give us devs a better idea of the class by just looking at the export class... line because we can see what is implemented/extended.

NestJs - Validate request body using class-validator having 2 options for body class

I have a rest call, which might receive body of type classA or classB.
I need to keep it as 2 different classes.
Example -
// classes -
class ClassA {
#IsString()
#Length(1, 128)
public readonly name: string;
#IsString()
#Length(1, 128)
public readonly address: string;
}
class ClassB {
#IsString()
#Length(1, 10)
public readonly id: string;
}
// my request controller -
#Post('/somecall')
public async doSomething(
#Body(new ValidationPipe({transform: true})) bodyDto: (ClassA | ClassB) // < not validating any of them..
): Promise<any> {
// do something
}
The issue is, that when having more than one class, body is not validated.
How can I use 2 or more classes and validate them using class-validator?
I don't want to use same class..
Thank you all :)
I don't want to use same class..
Then it won't be possible, at least not with Nest's built-in ValidationPipe. Typescript doesn't reflect unions, intersections, or other kinds of generic types, so there's no returned metadata for this parameter, and if there's no metadata that's actionable Nest will end up skipping the pipe.
You could probably create a custom pipe to do the validation for you, and if you have two types you're probably going to have to. You can still call the appropriate class-transformer and class-validator methods inside of the class too.
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { of } from 'rxjs';
#Injectable()
export class CheckTypeInterceptor implements NestInterceptor {
constructor() {}
async intercept(context: ExecutionContext, next: CallHandler) /*: Observable<any>*/ {
const httpContext = context.switchToHttp();
const req = httpContext.getRequest();
const bodyDto = req.body.bodyDto;
// Need Update below logic
if (bodyDto instanceof ClassA || bodyDto instanceof ClassB) {
return next.handle();
}
// Return empty set
return of([]);
}
}
#UseInterceptors(CheckTypeInterceptor)
export class ApiController {
...
}
Encountered a similar situation where I had to validate some union type request. The solution I ended up with was a custom pipe as Jay McDoniel suggested here. The logic would vary depending on the request body you are dealing with, but per the question in case the following may work
Custom pipe:
import { ArgumentMetadata, BadRequestException, Inject, Scope } from "#nestjs/common";
import { PipeTransform } from "#nestjs/common";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import { ClassADto } from '../repository/data-objects/class-a.dto';
import { ClassBDto } from '../repository/data-objects/class-b.dto';
export class CustomPipeName implements PipeTransform<any> {
async transform(value: any, { metatype, type }: ArgumentMetadata): Promise<any> {
if (type === 'body') {
const classA = plainToInstance(ClassADto, value);
const classB = plainToInstance(ClassBDto, value);
const classAValidationErrors = await validate(classA);
const classBValidationErrors = await validate(classB);
if (classAValidationErrors.length > 0 && classBValidationErrors.length > 0) {
throw new BadRequestException('some fancy info text');
}
}
return value;
}
}
Controller usage:
#Post('/somecall')
public async doSomething(
#Body(new CustomePipeName()) bodyDto: (ClassA | ClassB)
): Promise<any> {
// do something
}

Is it possible to pass a parameter to a nestjs guard?

I'm trying to come up with a somewhat reusable guard and it looks like I need to pass a string param to a guard. Is it achievable in nestjs?
It sounds like you are looking to use a mixin, a function that returns a class. I'm not sure what kind of parameter you're passing, but the idea is
export const RoleGuard = (role: string) => {
class RoleGuardMixin implements CanActivate {
canActivate(context: ExecutionContext) {
// do something with context and role
return true;
}
}
const guard = mixin(RoleGuardMixin);
return guard;
}
mixin as a function is imported from #nestjs/common and is a wrapper function that applies the #Injectable() decorator to the class
Now to use the guard, you need to do something like #UseGuards(RoleGuard('admin'))
It seems impossible to use mixin in Guard in NestJs. It will throw Exported variable 'RoleGuard' has or is using private name 'RoleGuardMixin'.
Actually, you may use setMetadata to pass in the parameters one by one, then get it from the Guard using reflector from from '#nestjs/core'.
#Injectable()
export class RoleGuard implements CanActivate {
constructor(
private reflector: Reflector,
) {}
canActivate(context: ExecutionContext) {
const roleName = this.reflector.get<string>('roleName', context.getHandler());
return true;
}
}
#Get()
#SetMetadata('roleName', 'developer')
async testRoleGuard() {
return true;
}
Or you may define a decorator to pass the parameter in.
export const RoleName = (roleName: string) => SetMetadata('roleName', roleName);
#Get()
#RoleName('developer')
async testRoleGuard() {
return true;
}
Usage:
#UseGuards(new AuthorizeGuard('read', 'users'))
Guard:
#Injectable()
export class AuthorizeGuard implements CanActivate {
constructor(private action, private subject) {}
canActivate(context: ExecutionContext): boolean {
//you can use this.action and this.subject
}
}
If anyone is struggling with testing the mixin approach #JayMcDoniel brilliantly pointed out, here's a sample setup from a guard I implemented using the mixin.
describe('PaymentGuard', () => {
let guard: CanActivate;
beforeEach(async () => {
const guardProvider = PaymentGuard(PaymentGuardHandlerToken.EXPECT_PLATFORM_ENABLED);
const module = await Test.createTestingModule({
providers: [
guardProvider,
{
provide: PaymentService,
useValue: MOCK_PAYMENT_SERVICE,
},
{
provide: EntityManager,
useValue: MOCK_ENTITY_MANAGER,
}
],
}).compile();
guard = module.get(guardProvider);
});
});
And then you just call the guard.canActivate within your test.

Use global nest module in decorator

I have a global logger module in nest, that logs to a cloud logging service. I am trying to create a class method decorator that adds logging functionality. But I am struggling how to inject the service of a global nest module inside a decorator, since all dependency injection mechanisms I found in the docs depend are class or class property based injection.
export function logDecorator() {
// I would like to inject a LoggerService that is a provider of a global logger module
let logger = ???
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = function(...args: any[]) {
logger.log(`${propertyKey} method called with args.`);
//attach original method implementation
const result = originalMethod.apply(this, args);
//log result of method
logger.log(`${propertyKey} method return value`);
};
};
}
UPDATE: Per reqest a simple example
Basic example would be to log calls to a service method using my custom logger (which in my case logs to a cloud service):
class MyService {
#logDecorator()
someMethod(name: string) {
// calls to this method as well as method return values would be logged to CloudWatch
return `Hello ${name}`
}
}
Another extended use case would be to catch some errors, then log them. I have a lot of this kind of logic that get reused across all my services.
Okay, found a solution. In case anyone else stumbles upon this. First please keep in mind how decorators work – they are class constructor based, not instance based.
In my case I wanted to have my logger service injected in the class instance. So the solution is to tell Nest in the decorator to inject the LoggerService into the instance of the class that contains the decorated method.
import { Inject } from '#nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = true) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
This gives the possibility to catch errors in a method, log them within the service context, and either re-throw them (so your controllers can handle user resp) or not. In my case I also had to implement some transaction-related logic here.
export class FoobarService implements OnModuleInit {
onModuleInit() {
this.test();
}
#logErrorDecorator()
test() {
throw new Error('Oh my');
}
}

What is the Nest.js way of creating static and instance functions for a model?

Does such a thing exist or do I follow standard Mongoose procedure?
I read the docs, I spent the whole day yesterday for this, but I could only find relative ones that placed the functions inside the service component. This is not effective as if I would like to use a static model function outside of the service component (say, a custom decorator), it wouldn't reach it as DI is private.
I would have created an Issue on Github for documentation request, but I feared I may have overlooked something.
Edit 2: Please do not change the title of the post. "Nest" is not a typo for "best". It is referring to a Node.js framework called Nest.js. (See post tags and referenced documentation link)
Edit: In the MongoDB section of the docs, there's this piece of code:
constructor(#InjectModel(CatSchema) private readonly catModel: Model<Cat>) {}
but specifically, this Model<Cat> part, imports Cat from an interface that extends Mongoose Document interface. Wouldn't it be better if this Cat interface was a class instead which was capable of functions (even after transpilation)?
I use the following approach:
When defining the schema, add static methods to the Mongoose schema:
UserSchema.methods.comparePassword = async function(candidatePassword: string) {
return await bcrypt.compare(candidatePassword, this.password);
};
Also include the method in the object's interface definition:
export interface User {
firstName: string;
...
comparePassword(candidatePassword: string): Promise<boolean>;
}
as well as the UserDocument interface
export interface UserDocument extends User, Document { }
So now my UsersService:
export class UsersService {
constructor(#InjectModel(Schemas.User) private readonly userRepository: Model<UserDocument>,
private readonly walletService: WalletsService,
#Inject(Modules.Logger) private readonly logger: Logger) {}
async findByEmail(email: string): Promise<UserDocument> {
return await this.userRepository.findOne({ email }).select('password');
}
...
}
And to tie it all together, when a user tries to log in, the Auth service retrieves a user object by id, and invokes that user object's instance method of comparePassword:
#Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) { }
async signIn({ email, password }: SignInDto): Promise<LoginResponse> {
const user = await this.usersService.findByEmail(email);
if (!user) { throw new UnauthorizedException('Invalid Username or Password'); }
if (await user.comparePassword(password)) {
const tokenPayload: JwtPayload = { userId: user.id };
const token = this.jwtService.sign(tokenPayload);
return ({ token, userId: user.id, status: LoginStatus.success });
} else {
throw new UnauthorizedException('Invalid Username or Password');
}
}
}
#InjectModel() is a helper decorator to inject registered component. You can always use a model class directly instead of injecting it through a constructor. Thanks to that you can use a model everywhere (but I'm not sure whether a custom decorator is a right choice). Also, Model<Cat> is redundant here. You can replace this type with anything else that fits your use-case, for example typeof X if you want to call static functions.

Resources