Skip to content

Instantly share code, notes, and snippets.

@bengry
Created December 26, 2019 07:32
Show Gist options
  • Save bengry/924a9b93c25d8a98bffdfc0a847f0dbe to your computer and use it in GitHub Desktop.
Save bengry/924a9b93c25d8a98bffdfc0a847f0dbe to your computer and use it in GitHub Desktop.
Nest.js request context workaround
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
import { APP_INTERCEPTOR } from "@nestjs/core";
import { RequestContextMiddleware } from "./request-context/request-context.middleware";
import { RequestContextModule } from "./request-context/request-context.module";
@Module({
imports: [
ConfigModule.load(path.resolve(__dirname, "config", "**/!(*.d).{ts,js}")),
WebappUsersModule,
RequestContextModule,
LoggerModule,
],
...
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(RequestContextMiddleware).forRoutes("*");
}
}
import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response } from "express";
import { RequestContext } from "./request-context.model";
/**
* This is needed to side-step Nest.js, which doesn't support getting the current execution context (i.e. Request) that's
* not from the Controller handles directly (and passing it down explicitly). This means that things like a Logger can't
* use DI to get the current user (if any).
*
* This solution is taken from https://github.com/nestjs/nest/issues/699#issuecomment-405868782.
*/
@Injectable()
export class RequestContextMiddleware implements NestMiddleware<Request, Response> {
use(req: Request, res: Response, next: () => void) {
const requestContext = new RequestContext(req, res);
RequestContext.cls.setContext(requestContext);
next();
}
}
import { ContinuationLocalStorage } from "asyncctx";
import { Request, Response } from "express";
export class RequestContext {
static cls = new ContinuationLocalStorage<RequestContext>();
static get currentContext() {
return this.cls.getContext();
}
readonly requestId: number;
constructor(public readonly req: Request, public readonly res: Response) {
this.requestId = Date.now();
}
}
import { Module } from "@nestjs/common";
import { RequestContextMiddleware } from "./request-context.middleware";
import { RequestContextService } from "./request-context.service";
@Module({
providers: [RequestContextMiddleware, RequestContextService],
exports: [RequestContextMiddleware, RequestContextService],
})
export class RequestContextModule {}
import { Injectable } from "@nestjs/common";
import { IUserDocument } from "../modules/webapp-users/interfaces";
import { RequestContext } from "./request-context.model";
@Injectable()
export class RequestContextService {
get currentUser(): IUserDocument | null {
const requestContext = this.currentRequest;
return (requestContext && requestContext.req.user) || null;
}
get currentRequestId(): number | null {
const requestContext = this.currentRequest;
return (requestContext && requestContext.requestId) || null;
}
private get currentRequest() {
const requestContext = RequestContext.currentContext;
return requestContext || null;
}
}
// This can be done anywhere. In my case I wanted to get the current user and requestId
import { Injectable } from "@nestjs/common";
import { IUserDocument } from "../modules/webapp-users/interfaces";
import { RequestContext } from "./request-context.model";
@Injectable()
export class RequestContextService {
get currentUser(): IUserDocument | null {
const requestContext = this.currentRequest;
return (requestContext && requestContext.req.user) || null;
}
get currentRequestId(): number | null {
const requestContext = this.currentRequest;
return (requestContext && requestContext.requestId) || null;
}
private get currentRequest() {
const requestContext = RequestContext.currentContext; // this is the actual usage.
return requestContext || null;
}
}
@zlk89
Copy link

zlk89 commented Apr 18, 2020

This won't work if multiple requests come in at the same time, and your code has any asynchronized processing.
On further reading, this seems to be a working code. Thanks for sharing it!
Reference: https://medium.com/@siddiqr67/async-hooks-the-coolest-way-to-save-execution-context-676d8af57f73

@noluckjustskill
Copy link

noluckjustskill commented Aug 15, 2020

Thanks for your solution!
But I can not realize it in my project, because in request-context.service requestContext is undefined: https://github.com/noluckjustskill/nestjs-graphql-multitenants/blob/5d32ff42b7c3862c77304c8582afde74a58eabb5/src/tenants/tenants.service.ts#L23

Because currentId of static instance of ContinuationLocalStorage are difference in middleware and in service

P.S node.js version is 14.8.0

@kieranongh
Copy link

request-context.service.ts and some-service.ts appear to be the same files

Just wondering what an example of some-service.ts would look like

@ahmetonurslmz
Copy link

@kieranongh Hi! I just want to help you that problem. You can inject your request-context service to subscriber or service (concise place that you want) and use it directly.

@ScreamZ
Copy link

ScreamZ commented Oct 14, 2022

This won't work if multiple requests come in at the same time, and your code has any asynchronized processing. On further reading, this seems to be a working code. Thanks for sharing it! Reference: https://medium.com/@siddiqr67/async-hooks-the-coolest-way-to-save-execution-context-676d8af57f73

Hell yeah I got the exact same consideration…

I give more intel from your article:

Data can be shared between async context thanks to asyncctx library which is using async a experimental feature of node at this time I'm writing.

Now you can replace asyncctx with the new (Node12) API async local storage

They allow storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.

This snippet can be replace by this library that use the "new" API : https://github.com/abonifacio/nestjs-request-context

@NewEXE
Copy link

NewEXE commented Jul 5, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment