Apply middleware to NestJs route method - module

I have my NestJs module set up as:
#Module({
controllers: [MyController],
})
export class MyModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(MyMiddleware)
.forRoutes('/myRoute');
}
}
How do I apply MyMiddleware to only GET /myRoute but not POST /myRoute?

As the docs show something like this should do:
#Module({
controllers: [MyController],
})
export class MyModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(MyMiddleware)
.forRoutes({ path: '/myRoute', method: RequestMethod.GET });
}
}

Related

How can I dynamically inject different providers with Nest.js?

I'm facing the following issue. I have a service used by a controller. The service (in the snippets below QueueService) injects a provider imported from a package. I aim to reuse the QueueService across the controller methods, but I also need to dynamically specify which provider QueueService should use.
My question is, how can I achieve this behaviour?
import { PubsubService } from '#myorg/queue'
#Module({
imports: [
ConfigModule.forRoot({
SHARED_RESOURCES_PROJECT_ID: Joi.string().required()
})
})
],
controllers: [AppController],
providers: [
{
provide: 'PUBSUB',
useValue: new PubsubService()
},
{
provide: 'INTEGRATION_PUBSUB',
useValue: new PubsubService({ projectId: process.env.SHARED_RESOURCES_PROJECT_ID })
}
]
})
export class AppModule {}
#Controller()
export class AppController {
constructor(private queueService: QueueService) {}
#Post()
async create() {
...
// here I want to use queueService with `PUBSUB` injected
return this.queueService.sendMessage(...)
}
#Patch()
async update() {
...
// here I want to use queueService with `INTEGRATION_PUBSUB` injected
return this.queueService.sendMessage(...)
}
}
#Injectable()
export class QueueService {
constructor(
// how can I dynamically change `#Inject('PUBSUB')` to `#Inject('INTEGRATION_PUBSUB')`?
#Inject('PUBSUB') private readonly pubsubService: PubsubService
) {}
async sendMessage(payload): Promise<void> {
return this.pubsubService.sendMessage(payload)
}
}
dynamic inject is not possible after object(in this case controller) created . so you have two option
1- create two QueueService (one for PUBSUB and another for INTEGRATION_PUBSUB) and inject both to controller. use those in your controller functions. (i recommend this)
2- inject both PUBSUB and INTEGRATION_PUBSUB into QueueService and pass another param in sendMessage function . so check this param to choose between PUBSUB and INTEGRATION_PUBSUB

How to exclude all routes except one in NestJs?

Let's say that I have controller with two routes:
#Controller('events')
export class EventController {
#Get('my')
async getMyEvents() {
return "A"
}
#Get(':eventId')
async getEvent(#Param('eventId', ParseUUIDPipe) eventId: string) {
return "B"
}
}
and I need to exclude all routes except one which have param:
export class EventModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.exclude({path: 'api/events/:eventId', method: RequestMethod.GET})
.forRoutes(EventController)
}
}
but it doesn't work, it also exclude route api/events/my, so how to avoid that ?
add the routes in exclude, you want to exclude and the rest in forRoutes
export class EventModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.exclude("*")
.forRoutes({path: 'api/events/:eventId', method: RequestMethod.GET})
}
}

How to install Express middleware (express-openapi-validator) in NestJS?

I am writing a NestJS application. Now I want to install the Express middleware express-openapi-validator.
However, I can't get it to work. There is a description for how to install the express-openapi-validator in express, but it always results in errors.
For example
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(middleware({apiSpec "./bff-api.yaml"}))
.forRoutes(OrganizationController)
}
}
results in
error TS2345: Argument of type 'OpenApiRequestHandler[]' is not assignable to parameter of type 'Function | Type<any>'.
Type 'OpenApiRequestHandler[]' is missing the following properties from type 'Type<any>': apply, call, bind, prototype, and 4 more.
How can I install this middleware in NestJS?
I added a NestJS example to express-openapi-validator (static link for posterity).
The AppModule looks basically identical, although you don't need to iterate over the middlewares:
#Module({
imports: [PingModule],
providers: [{ provide: APP_FILTER, useClass: OpenApiExceptionFilter }],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
...OpenApiValidator.middleware({
apiSpec: join(__dirname, './api.yaml'),
}),
)
.forRoutes('*');
}
}
I also added an exception filter to convert the error from express-openapi-validator to a proper response; otherwise I would always get a 500 error. You could also use this approach to convert the error into a custom error format.
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { Response } from 'express';
import { error } from 'express-openapi-validator';
#Catch(...Object.values(error))
export class OpenApiExceptionFilter implements ExceptionFilter {
catch(error: ValidationError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response.status(error.status).json(error);
}
}
interface ValidationError {
status: number;
message: string;
errors: Array<{
path: string;
message: string;
error_code?: string;
}>;
path?: string;
name: string;
}
I have now got it working:
configure(consumer: MiddlewareConsumer) {
middleware({
apiSpec: `${__dirname}/../api-doc/bff-api.yaml`
}).forEach(value => consumer.apply(value).forRoutes(OrganizationController))
}

NestJs version for modules

I would like to add version for my modules, but i don't know how can I do this. I tried to create a common module.ts but the same name of services killed each us. I tried different module.ts for the versions, it was better but the services with same names didn't work.
This is my last structure:
test-module
1.0
controllers
test.controller.ts
services
test.service.ts
test.module.ts
1.1
controllers
test.controller.ts
services
test.service.ts
test.module.ts
This is my test service(s) for the versions:
import * as _ from 'lodash';
import { Injectable } from '#nestjs/common';
#Injectable()
export class TestService {
public test() {
return '1.0'; // and 1.1 in 1.1 directory
}
}
This is my module.ts:
import { Module, Logger } from '#nestjs/common';
import { TestModule as DorotTwo } from 'test-module/1.1/test.module';
import { TestModule as DorotOne } from 'test-module/1.0/test.module'
#Module({
controllers: [ProtobufController],
providers: [],
imports: [
DorotTwo,
DorotOne,
],
})
export class ProjectModule {
constructor() {
Logger.log('App initialized');
}
}
This is a simple test Controller in the project who want use the modules. A tried import TestService from 1.0 or 1.1 but the test function's response is always 1.0 because that is the last element in the import.
#Controller()
export class ProtobufController {
constructor(private readonly testService: TestService) {
console.log(this.testService.test()); // Always 1.0
}
.....
It is working if I use full different names for services for example (eg: UserAuthenticationService10, RegisterAuthenticationService10), but this is horrible and if i forget rename it in new version, it will overwrite.
Is exists an example where I can read how can I create this versioned module?
Would using custom providers be a satisfying solution for you?
Example:
// 1.0
#Module({
providers: [
{ provide: 'TestService_1.0', useClass: TestService }
]
})
export class TestModule {}
// 1.1
#Module({
providers: [
{ provide: 'TestService_1.1', useClass: TestService }
]
})
export class TestModule {}
// Then
#Controller()
export class ProtobufController {
constructor(
#Inject('TestService_1.0') private readonly testService_10,
#Inject('TestService_1.1') private readonly testService_11
) {
console.log(this.testService_10.test());
console.log(this.testService_11.test());
}
}
I obviously haven't tested this and you should adapt it to your usecase. I suggest you to have a look at https://docs.nestjs.com/fundamentals/custom-providers.

Set value in service from AuthGuard before service is initiated with Angular2 and Firebase

I'm trying to get the user ID from the currently logged in user to customize the data loaded from my DataService.
My goal is that:
The AuthGuard should be called before the InboxComponent is loaded
This AuthGuard should set the user variable in the AuthService
I should then be able to use the authService.user in the DataService
However, the console.log(this.authService.user) yields undefined although the console.log(authState) in the AuthGuard correctly logs the user information.
Any clues on what could be wrong?
Authentication Service
export class AuthService {
public user: any;
constructor(private af: AngularFire, public auth$: FirebaseAuth) {
console.log("Auth service state:", this.authState);
}
AuthGuard
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): Observable<boolean> {
return this.authService.auth$
.take(1)
.map(authState => {
console.log(authState)
this.authService.setUser(authState);
return !!authState })
.do(authenticated => {
if (!authenticated) {
this.router.navigate(['/error']);
}
});
}
}
Dataservice
export class DataService {
private userPath: string;
constructor(private af: AngularFire, private authService: AuthService) {
console.log(this.authService.user);
}
Router
const routes: Routes = [
{ path: 'inbox', component: InboxComponent, canActivate: [AuthGuard] }
App Module
#NgModule({
declarations: [
InboxComponent
],
imports: [
EmailsModule
],
providers: [
AuthService,
AuthGuard
],
bootstrap: [AppComponent]
})
export class AppModule { }
I finally resolved this. I had a call to my DataService in my app.component.ts file to get some data in my navigation. This is loaded outside the router-module and thus called before the AuthService is called. This in turn caused the DataService before the AuthGuard was called.