Hi,
How to access current http context inside component method?
As an example i would like to access current session user inside "typeorm" EventSubscriber class to automatically set user who inserted/update/deleted record.
Thanks for nice framework!
Hi @darioxtx,
Can you say something more about what you wanna achieve? In most cases, you only have to pass the request
object to the component method.
Hi @kamilmysliwiec,
Each request have user object inside and i want to access that data in component and use current user object to filter data from database based on logged in user ex.:
@Controller('users')
@UseGuards(RolesGuard)
export class UsersController {
constructor(private readonly usersService: UsersService) { }
@Get('/profile')
@Roles(RolesEnum.Owner)
profile() {
return this.usersService.getCurrentUserProfile();
}
}
@Component()
export class UsersService {
constructor(@Inject('DbConnection') private dbConnection: Connection,
@Inject('CurrentContext') private context: CurrentContext){
}
async getCurrentUserProfile() {
let repo = this.dbConnection.getRepository(Profile);
let profile = await repo.find({
userId: context.req.user.id
});
return profile;
}
}
An other place where I want to get user is EventSubscriber
here i want to know witch user created or updated or deleted entity:
import {EventSubscriber, EntitySubscriberInterface} from "typeorm";
@EventSubscriber()
export class EverythingSubscriber implements EntitySubscriberInterface<any> {
constructor(@Inject('CurrentContext') private context: CurrentContext){ }
beforeInsert(event: InsertEvent<any>) {
event.entity.createdBy = context.req.user.id || context.req.user.username;
}
}
Thanks
Hi @darioxtx,
To achieve some kind of 'request context' you can try to use this library https://www.npmjs.com/package/request-context
But referring to your issue, I'm afraid that it might be not such easy. The instances of the typeorm
event subscribers are created in the core of this ORM. As far as I know, to set up the subscribers, you have to include classes (or directories where they are) inside the options object. It means that typeorm and nest would use different instances of this class. If there's a possibility to pass instances instead of types to typeorm, it'd much easier to make it work together.
Thank you for your answer. I will share my experience solving this issue.
Hi @darioxtx,
you can also try using Zone.js as middleware and see how it goes.
@kamilmysliwiec, maybe Zone.js can be integrated in the framework? The zone will provide this request context if each request handler is run in a new zone. Then that can be injected in the components as RequestContext.
BTW, request-context is implemented on top of the Node.js Domain API which is deprecated for quite some time.
@saskodh thanks for sharing info. I will try Zone.js.
@darioxtx did you find any solution?
@cdiaz yes my solution is to use Zone.js. it seems promising and is under angular team control.
But i faced issue using async/await with target ES2017. with target ES2016 works good.
I can share code later if anyone interested.
Great, I'm interested to see your code to know how to implement CurrentUser context.
thanks
Hi,
Here is my Zone.js integration with nest.
first I import zone.js
to server.ts
like this
import 'zone.js';
import "zone.js/dist/zone-node.js";
import "zone.js/dist/long-stack-trace-zone.js";
Then I created NestMiddleware
import {Middleware, NestMiddleware} from '@nestjs/common';
import {RequestContext} from "../models/request-context";
@Middleware()
export class RequestContextMiddleware implements NestMiddleware {
constructor() {}
resolve() {
return(req, res, next) => {
var requestContext = new RequestContext(req, res);
Zone.current.fork({
name: RequestContext.name,
properties: {
[RequestContext.name]: requestContext
}
}).fork(Zone['longStackTraceZoneSpec']).run(async () => {
await next();
} );
}
}
}
RequestContext
looks like this
import {CurrentUser} from "../../modules/auth/models/current-user";
export class RequestContext {
public readonly id: Number;
public request: Request;
public response: Response;
constructor(request: Request, response: Response) {
this.id = Math.random();
this.request = request;
this.response = response;
}
public static currentRequestContext(): RequestContext {
return Zone.current.get(RequestContext.name);
}
public static currentRequest(): Request {
let requestContext = RequestContext.currentRequestContext();
if(requestContext) {
return requestContext.request;
}
return null;
}
public static currentUser(): CurrentUser {
let requestContext = RequestContext.currentRequestContext();
if(requestContext) {
return requestContext.request['user'];
}
return null;
}
}
RequestContextMiddleware
is used in main ApplicationModude
@Module({
modules: [AuthModule, UsersModule, DatabaseModule]
})
export class ApplicationModule {
configure(consumer: MiddlewaresConsumer) {
consumer
.apply(RequestContextMiddleware)
.forRoutes({path: "*"})
}
}
Current RequestContext
can be accessed everywhere in your code
import {EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent} from "typeorm";
import {RequestContext} from "../../common/models/request-context";
@EventSubscriber()
export class EverythingSubscriber implements EntitySubscriberInterface<any> {
beforeInsert(event: InsertEvent<any>) {
let currentUser = RequestContext.currentUser();
if(!currentUser) {
throw new Error("Unknown user context");
}
event.entity.updatedBy = currentUser.username;
event.entity.createdBy = currentUser.username;
event.entity.createdOn = new Date();
event.entity.updatedOn = new Date();
console.log(`user: `, currentUser.username);
console.log(`BEFORE ENTITY INSERTED: `, event.entity);
}
}
I think that's it, also i like exception trace that provide Zone.js
. It's really easy to find where your code crashed.
@saskodh thanks for right directions.
Great work @darioxtx !
Some weeks ago i tried zone.js in node to get the current user (and some other information). But zone.js often caused crashes when the app becomes complex.
Therefore i upgraded to node 8.4 and used cls-hooked.
The latest version of cls-hooked
uses async_hooks API when run in Node >= 8.2.1
The implementation works in a very similar way.
There is also a type definition file for cls-hooked
@JustGreg, this is excellent, thanks for sharing, I wasn't aware that this API was added in Node.js.
I've also used Zone.js experimentally in Node.js project and even though I didn't noticed any issues back then I still felt uncomfortable. Zone.js in order to preserve the context does monkey patching of all async APIs that are in the browser and in Node. That means something could very easily break in the next upgrade. I would definitely go with this API instead of Zone.js even though it's in experimental phase.
@kamilmysliwiec, this is excellent opportunity for adding new features that depend on the 'thread-local' storage like declarative transaction management. While it's in experimental phase Nest can leave the decision to the user whether he will enable this feature or not. What are your thoughts?
Hi everyone,
A lot of great stuff, that's awesome 馃帀
@saskodh since this API is still in experimental phase (stability: 1) I'm not gonna integrate it with nest in any way now. What I'm trying to do is to make sure that learning curve is not too big, so I see this feature as a 3rd-party module rather than the 'core thing'.
Just for information:
There is also a node.js zone spec which is currently stage-0. Maybe it becomes a nativ node.js feature in the next release. http://node.green/#ESNEXT-strawman--stage-0--zones
Another npm package (based on typescript) which uses async_hookes is https://github.com/gms1/node-async-context
I'm trying to access a service from a typeorm EventSubscriber.
Is there a simpler way to access a service without using Zone.js. I don't need the Request Context. I just need to be able to send updates to a service. It's not immediately clear how I'd do this with zone.js
@donaldinho
Maybe you can try for using the Execution Context?
Just export a app: INestApplication
instance, then use const serviceInstance = app.select(SomeModule).get(SomeService)
to get the service instance?
Is that what you need?
Can we just modify the target
of tsconfig.json
, to Zone.js work on Nest 5.x?
@darioxtx I am trying to implement your solution (thanks for sharing!) However inside my controller let requestContext = RequestContext.currentRequest();
and let currentUser = RequestContext.currentUser();
stays null
.
Inside the middleware I noticed that let requestContext = RequestContext.currentRequestContext();
gives null and:
public static currentRequestContext(): RequestContext {
console.log('zone name', RequestContext.name);
console.log('zone get', Zone.current.get(RequestContext.name));
return Zone.current.get(RequestContext.name);
}
gives:
zone name RequestContext
zone get undefined
I don't have a server.ts
only main.ts
maybe this is the issue, since for what I understand server.ts
belongs to microservices.
I am compiling using es2017
Any thoughts on what I might do wrong here?
Hi @basvdijk,
I don't use zone.js any more because it does not work with es2017 and all async/await
features. So now I use cls-hooked
library witch use node.js async hooks.
import { HttpException, HttpStatus } from "@nestjs/common";
import * as cls from 'cls-hooked';
import { IncomingMessage } from "http";
import { CurrentUser } from "../../modules/models/current-user.model";
export class RequestContext {
public static nsid = 'some_random_guid';
public readonly id: Number;
public request: IncomingMessage;
public response: Response;
constructor(request: IncomingMessage, response: Response) {
this.id = Math.random();
this.request = request;
this.response = response;
}
public static currentRequestContext(): RequestContext {
const session = cls.getNamespace(RequestContext.nsid);
if (session && session.active) {
return session.get(RequestContext.name);
}
return null;
}
public static currentRequest(): IncomingMessage {
let requestContext = RequestContext.currentRequestContext();
if (requestContext) {
return requestContext.request;
}
return null;
}
public static currentUser(throwError?: boolean): CurrentUser {
let requestContext = RequestContext.currentRequestContext();
if (requestContext) {
const user: any = requestContext.request['user'];
if (user) {
return new CurrentUser(user);
}
}
if (throwError) {
throw new HttpException("Unauthorized", HttpStatus.UNAUTHORIZED);
}
return null;
}
}
and Middleware looks like this
import { Injectable, NestMiddleware } from '@nestjs/common';
import * as cls from 'cls-hooked';
import { RequestContext } from "../models/request-context";
@Injectable()
export class RequestContextMiddleware implements NestMiddleware {
resolve() {
return (req, res, next) => {
const requestContext = new RequestContext(req, res);
const session = cls.getNamespace(RequestContext.nsid) || cls.createNamespace(RequestContext.nsid);
session.run(async () => {
session.set(RequestContext.name, requestContext);
next();
})
}
}
}
I hope this will help you :)
@darioxtx Thanks I'll check it out. I was wondering if it smart to still use cls-hooked
, since since the last commit was from 26 Jul 2017.
@darioxtx Applied your solution to my project and works like a charm 馃憤
This thread helped a lot, but after updating npm modules the solution with zone.js fell apart.
I found another solution working with pg, node > 10 and async/await:
node-request-context did the magic again with the following code:
Middleware function:
import {RequestContext} from "./request.context";
import { getNamespace, createNamespace } from 'node-request-context';
export function RequestContextMiddleware(req, res, next) {
let rc = new RequestContext(req, res);
const namespace = getNamespace('myapp.mynamespace') || createNamespace('myapp.mynamespace');
namespace.run(() => {
namespace.set('tid', rc);
next();
});
};
RequestContext
import { AppUser } from "../../entities/appuser/appuser.entity";
import { getNamespace } from 'node-request-context';
export class RequestContext {
public readonly id: Number;
public request: Request;
public response: Response;
constructor(request: Request, response: Response) {
this.id = Math.random();
this.request = request;
this.response = response;
}
public static currentRequestContext(): RequestContext {
let namespace = getNamespace('myapp.mynamespace');
let rc = namespace.get('tid');
return rc;
}
public static currentRequest(): Request {
let requestContext = RequestContext.currentRequestContext();
return requestContext.request;
}
public static currentUser(): AppUser {
let requestContext = RequestContext.currentRequestContext();
return requestContext.request['user'];
}
}
Middleware called globally
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
import { RequestContextMiddleware } from './auth/zone/request.context.middleware';
import { frontenddispatcher } from './frontend.middleware2';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.use(RequestContextMiddleware, frontenddispatcher);
await app.listen(3000);
}
bootstrap();
Usage in EventSubscriber
import {EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent} from "typeorm";
import { RequestContext } from "../auth/zone/request.context";
@EventSubscriber()
export class EverythingSubscriber implements EntitySubscriberInterface {
beforeInsert(event: InsertEvent<any>) {
let currentUser = RequestContext.currentUser();
console.log(JSON.stringify(currentUser))
if(!currentUser || !currentUser.email) {
throw new Error("Unknown user context");
}
event.entity.updatedBy = currentUser.email;
event.entity.createdBy = currentUser.email;
event.entity.createdOn = new Date();
event.entity.updatedOn = new Date();
console.log(`user: `, currentUser.email);
console.log(`BEFORE ENTITY INSERTED: `, event.entity);
}
beforeUpdate(event: UpdateEvent<any>) {
let currentUser = RequestContext.currentUser();
console.log(JSON.stringify(currentUser))
if(!currentUser || !currentUser.email) {
throw new Error("Unknown user context");
}
event.entity.updatedBy = currentUser.email;
event.entity.updatedOn = new Date();
console.log(`user: `, currentUser.email);
console.log(`BEFORE ENTITY UPDATED: `, event.entity);
}
}
@heyplusyou thanks for this example. Since node-request-context
is based on async_hooks
, which are at this moment in experimental phase, I was wondering if you are using this in production environment. How is the stability of this solution?
@zWaR Not with a customer in production but internally within my company.
Is there any solution that's not based on async_hooks
?
Soon in the incoming Nest 6 release https://github.com/nestjs/nest/pull/1486
Thanks @kamilmysliwiec
This thread helped a lot, but after updating npm modules the solution with zone.js fell apart.
I found another solution working with pg, node > 10 and async/await:
node-request-context did the magic again with the following code:
Middleware function:
import {RequestContext} from "./request.context"; import { getNamespace, createNamespace } from 'node-request-context'; export function RequestContextMiddleware(req, res, next) { let rc = new RequestContext(req, res); const namespace = getNamespace('myapp.mynamespace') || createNamespace('myapp.mynamespace'); namespace.run(() => { namespace.set('tid', rc); next(); }); };
RequestContext
import { AppUser } from "../../entities/appuser/appuser.entity"; import { getNamespace } from 'node-request-context'; export class RequestContext { public readonly id: Number; public request: Request; public response: Response; constructor(request: Request, response: Response) { this.id = Math.random(); this.request = request; this.response = response; } public static currentRequestContext(): RequestContext { let namespace = getNamespace('myapp.mynamespace'); let rc = namespace.get('tid'); return rc; } public static currentRequest(): Request { let requestContext = RequestContext.currentRequestContext(); return requestContext.request; } public static currentUser(): AppUser { let requestContext = RequestContext.currentRequestContext(); return requestContext.request['user']; } }
Middleware called globally
import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './app.module'; import { RequestContextMiddleware } from './auth/zone/request.context.middleware'; import { frontenddispatcher } from './frontend.middleware2'; async function bootstrap() { const app = await NestFactory.create(ApplicationModule); app.use(RequestContextMiddleware, frontenddispatcher); await app.listen(3000); } bootstrap();
Usage in EventSubscriber
import {EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent} from "typeorm"; import { RequestContext } from "../auth/zone/request.context"; @EventSubscriber() export class EverythingSubscriber implements EntitySubscriberInterface { beforeInsert(event: InsertEvent<any>) { let currentUser = RequestContext.currentUser(); console.log(JSON.stringify(currentUser)) if(!currentUser || !currentUser.email) { throw new Error("Unknown user context"); } event.entity.updatedBy = currentUser.email; event.entity.createdBy = currentUser.email; event.entity.createdOn = new Date(); event.entity.updatedOn = new Date(); console.log(`user: `, currentUser.email); console.log(`BEFORE ENTITY INSERTED: `, event.entity); } beforeUpdate(event: UpdateEvent<any>) { let currentUser = RequestContext.currentUser(); console.log(JSON.stringify(currentUser)) if(!currentUser || !currentUser.email) { throw new Error("Unknown user context"); } event.entity.updatedBy = currentUser.email; event.entity.updatedOn = new Date(); console.log(`user: `, currentUser.email); console.log(`BEFORE ENTITY UPDATED: `, event.entity); } }
How would I do this in Nest 6 without async_hooks?
You should be able to achieve that by injecting the request as @Inject(REQUEST)
@adrien2p Can you provide a small example please?
I want to use Typeorm's Listener's to update createdBy
when inserting a new entity and get the user from the request (req.user)
You just have to use the inject decorator to inject the request, for that you need to make your provider request scoped. from the injected request you only need to do this.request.user to access the current user on the request. But don鈥檛 forget to attach the user through a guard. I am out of the office and on the phone, hard to write an example ^^ for the request scoped i invite you to have a look on this link https://docs.nestjs.com/fundamentals/injection-scopes
I am still stuck on this topic, can you help regarding this issue?
I "sold" NestJS to our company to use it but this stops us from boosting it like "This is the way we do!"
https://stackoverflow.com/questions/57070997/request-scoped-logger
Instead of using it for an ORM I need the current request everytime someone calls "logger.log"...
@mambax you have to set your logger scope to Scope.Request
in @Injectable()
decorator. Otherwise, we won't be able to actually inject REQUEST
because the instance will be created once (during the application bootstrap)
Hello! If I add @Injectable({ scope: Scope.REQUEST })
decorator to my EntitySubscriberInterface, the beforeInsert event is not fired.
@Injectable({ scope: Scope.REQUEST })
@EventSubscriber()
export class CustomEntitySubscriber implements EntitySubscriberInterface {
constructor(
@Inject(REQUEST) private readonly request: Request,
@InjectConnection() readonly connection: Connection,
) {
connection.subscribers.push(this);
}
beforeInsert(event: InsertEvent<any>) {
// called before insert
const a = this;
if (event.entity.firstname) {
event.entity.firstname = `*****${event.entity.firstname}`;
}
const req = this.request;
}
}
Instead with this structure the event is fired, but I need request
@Injectable()
@EventSubscriber()
export class CustomEntitySubscriber implements EntitySubscriberInterface {
constructor(
@InjectConnection() readonly connection: Connection,
) {
connection.subscribers.push(this);
}
beforeInsert(event: InsertEvent<any>) {
// called before insert
const a = this;
if (event.entity.firstname) {
event.entity.firstname = `*****${event.entity.firstname}`;
}
}
}
Any suggestion? Thank you
I know this issue is closed, but I got here by Google Search and also the last comment is pretty recent _(16 days ago)_.
@ants88 I do not have a suggestion, but what I am experiencing right now is: if service in the Subscriber
(using the NestJS way, not the TypeORM way) dependency tree (so not only services directly used in the Subscriber
's constructor, but also anywhere deeper) is marked with @Injectable({ scope: Scope.REQUEST })
, the Subscriber
's constructor is not called. There is no error or warning reported.
@kamilmysliwiec if I understand what you mention in your comment it almost looks like a bug to me. Should I open a new issue and add a test case for this?
Since I need this badly too, I will try the other options suggested in this thread and update you with my findings.
@kamilmysliwiec what do you think about introducing RequestHolder
service, which will return actual Request
object? In this case we can use Request
in SINGLETON
-scoped services.
Symfony framework works in such way: https://symfony.com/blog/new-in-symfony-2-4-the-request-stack
so........How to access current http context inside "typeorm" EventSubscriber class??????????
Just FYI right now I am using https://github.com/jeff-lewis/cls-hooked to pass the context I need (like @CurrentUser() user
or EntityManager
) like in async find(user: AppUser) : Promise<Array<T>> {
return run( user, getManager(), async () => await getManager().getRepository(this.getCtor()).find() );
}
.
Would be interested to know how this can be done better.
Soon in the incoming Nest 6 release #1486
Is this feature released in nest 6 release. If yes can we have a example how to do get request context in typeorm event subscribe class
Is it already integrated?
Most helpful comment
Hi,
Here is my Zone.js integration with nest.
first I import
zone.js
toserver.ts
like thisThen I created
NestMiddleware
RequestContext
looks like thisRequestContextMiddleware
is used in mainApplicationModude
Current
RequestContext
can be accessed everywhere in your codeI think that's it, also i like exception trace that provide
Zone.js
. It's really easy to find where your code crashed.@saskodh thanks for right directions.