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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With