Reputation:
I use NestJS framework, and want to apply several middlewares to a route in my app. Each middleware is a class implementing NestMiddleware interface. One of these middlewares is async, and is not consumed before the route handler is called. Is there a way to resolve the promise of this middleware before handling the route?
Async middleware (page-loader.middleware)
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class PageLoader implements NestMiddleware {
async use(req: any, res: any, next: () => void) {
try {
req.body.html = await req.body.fetcher.fetch();
} catch (error) {
throw new Error(error);
} finally {
next();
}
}
}
Controller (create-article.controller)
import { Controller, Post, Body } from '@nestjs/common';
import { SaveArticleService } from './save-article.service';
import { CreateArticleDto } from './create-article.dto';
@Controller()
export class CreateArticleController {
constructor(private readonly saveArticleService: SaveArticleService) {}
@Post('/create')
async create(@Body() createArticleDto: CreateArticleDto) {
return this.saveArticleService.save(createArticleDto);
}
}
Module (create-article.module)
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { CreateArticleController } from './create-article.controller';
import { SaveArticleService } from './save-article.service';
// Another (sync) middleware
import { ExtensionExtractor } from './extension-extractor.middleware';
// The async middleware
import { PageLoader } from './page-loader.middleware';
@Module({
controllers: [CreateArticleController],
providers: [SaveArticleService],
})
export class CreateArticleModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(ExtensionExtractor, PageLoader)
.forRoutes({ path: 'create', method: RequestMethod.POST});
}
}
I did not include a snippet of the service used in the controller, as it is not relevant to my question.
This question did not help me solve the issue, as the middleware structure is different. I am waiting for the middleware method to resolve its promise, and not waiting for an input that is reused inside the middleware.
These github issue answers are not relevant, as the NestJS API drastically changed.
Thanks in advance for your help!
Upvotes: 7
Views: 8320
Reputation: 70151
You can absolutely use asynchronous middleware with Nest; however, there is a problem with using the .forRoutes({path: 'path', method: method});
strategy.
I set up a quick middleware consumer to show how this works without using the RequestMethod.GET
.
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
function asyncTimeout(milliseconds: number): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('DONE'), milliseconds);
});
}
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService]
})
export class UserModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply((req, res, next) => {
console.log('Using forRoutes(path)');
console.log('syncronous middleware');
next();
},
(async (req, res, next) => {
console.log('Using forRoutes(path)');
const start = Date.now();
const done = await asyncTimeout(5000);
console.log(done);
console.log('Time taken:' + (Date.now() - start));
next();
})
)
.forRoutes('/')
.apply((req, res, next) => {
console.log('Using forRoutes({path, method})');
console.log('syncronous middleware');
next();
},
(async (req, res, next) => {
console.log('Using forRoutes({path, method})');
const start = Date.now();
const done = await asyncTimeout(5000);
console.log(done);
console.log('Time taken:' + (Date.now() - start));
next();
})
)
.forRoutes({path: '/', method: RequestMethod.GET});
}
}
import { Controller, Get } from '@nestjs/common';
import { UserSerivce } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get('/')
testFunction() {
return {greeting: 'hello'};
}
}
[2019-06-20 22:40:48.191] [INFO] | Listening at http://localhost:3333/api
Using forRoutes(path)
syncronous middleware
Using forRoutes(path)
DONE
Time taken:5002
[2019-06-20 22:40:57.346] [INFO] | [Nest] 30511 [Morgan] GET /api/user 200 5014.234 ms - 20
I've used the same middleware functions in both setups, but you can see that the asynchronous middleware responds as expected when using .forRoutes(path)
, while not when using .forRoutes({path, method})
(pardon my custom logger).
This should be brought up as an issue on GitHub with Kamil to get it resolved, but your setup otherwise works. Feel free to use any of my code here to set up a sample repo for the issue if you choose to open one.
Upvotes: 7