Skip to content

How to access current http context inside component method #173

@darioxtx

Description

@darioxtx

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!

Activity

kamilmysliwiec

kamilmysliwiec commented on Oct 1, 2017

@kamilmysliwiec
Member

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.

darioxtx

darioxtx commented on Oct 2, 2017

@darioxtx
Author

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

kamilmysliwiec

kamilmysliwiec commented on Oct 2, 2017

@kamilmysliwiec
Member

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.

darioxtx

darioxtx commented on Oct 2, 2017

@darioxtx
Author

Thank you for your answer. I will share my experience solving this issue.

saskodh

saskodh commented on Oct 2, 2017

@saskodh

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.

darioxtx

darioxtx commented on Oct 2, 2017

@darioxtx
Author

@saskodh thanks for sharing info. I will try Zone.js.

cdiaz

cdiaz commented on Oct 4, 2017

@cdiaz

@darioxtx did you find any solution?

darioxtx

darioxtx commented on Oct 4, 2017

@darioxtx
Author

@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.

cdiaz

cdiaz commented on Oct 4, 2017

@cdiaz

Great, I'm interested to see your code to know how to implement CurrentUser context.
thanks

darioxtx

darioxtx commented on Oct 5, 2017

@darioxtx
Author

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.

jselesan

jselesan commented on Oct 5, 2017

@jselesan

Great work @darioxtx !

JustGreg

JustGreg commented on Oct 7, 2017

@JustGreg

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

saskodh

saskodh commented on Oct 7, 2017

@saskodh

@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?

kamilmysliwiec

kamilmysliwiec commented on Oct 31, 2017

@kamilmysliwiec
Member

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'.

21 remaining items

adrien2p

adrien2p commented on Apr 18, 2019

@adrien2p

You should be able to achieve that by injecting the request as @Inject(REQUEST)

yogevlahyani

yogevlahyani commented on Apr 18, 2019

@yogevlahyani

@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)

adrien2p

adrien2p commented on Apr 18, 2019

@adrien2p

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’t 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

mambax

mambax commented on Jul 17, 2019

@mambax

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"...

kamilmysliwiec

kamilmysliwiec commented on Jul 17, 2019

@kamilmysliwiec
Member

@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)

ants88

ants88 commented on Jul 30, 2019

@ants88

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

davidpodhola

davidpodhola commented on Aug 15, 2019

@davidpodhola

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.

megazoll

megazoll commented on Sep 4, 2019

@megazoll

@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

iliuyt

iliuyt commented on Oct 8, 2019

@iliuyt

so........How to access current http context inside "typeorm" EventSubscriber class??????????

davidpodhola

davidpodhola commented on Nov 8, 2019

@davidpodhola

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.

devendrainfotech

devendrainfotech commented on Dec 8, 2019

@devendrainfotech

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

joelcoronah

joelcoronah commented on Feb 10, 2020

@joelcoronah

Is it already integrated?

locked as resolved and limited conversation to collaborators on Feb 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @megazoll@davidpodhola@basvdijk@donaldinho@jselesan

        Issue actions

          How to access current http context inside component method · Issue #173 · nestjs/nest