I have an external library and I am creating a service so that I can extend my service to that class and use its context.
class testApi {
constructor(standardAxios, cachedAxios = null) {
this.axios = standardAxios;
this.cachedAxios = cachedAxios;
}
setAxios(standardAxios, cachedAxios = null) {
this.axios = standardAxios;
this.cachedAxios = cachedAxios;
return this;
}
}
module.exports = testApi;
this is the service I am creating and I can't access the axios context it has created
export class UsersService extends testApi {
constructor(#InjectModel(User.name) private userModel: Model<UserDocument>, #Inject(axios)) {
super();
}
async test(): Promise<any> {
this.axios
.get('https://jsonplaceholder.typicode.com/todos/1')
.then((e) => console.log(e));
}
}
Assuming_ you have a provider token for the variable axios, this isn't how you inject it. Look at your #InjectModel(): notice how it has a private userModel: Model<UserDocument> after it? That's how your tell Typescript what the class variable is. If you don't need a class member, that's fine, but you still need the constructor parameter to be defined. This means, at the very minimum you need #Inject(axios) axios: any. You can set the type, I believe it would be AxiosInstance instead of any. Now you can pass axios on to super.
Do note that to do this in the first place, you must have a custom provider like
{
provide: axios, // same variable as is being passed to `#Inject()`
useValue: axiosInstance // the actual instance to be passed
}
And this must be in the providers array of the module you're currently working in. This is called a custom provider.
These are some of the core concepts of NestJS, being able to inject via tokens and extending classes, so I suggest you read up on Typescript and go through the docs again to get a better understanding of what's available and how classes inherit from each other.
Related
I have many services that all need to know the tenant ID from the request (kept in JWT auth token). The request is either GRPC (jwt stored in MetaData) or Graphql (jwt stored in context.headers.authorization).
I would like to be able to force myself not to forget to pass this tenant id when using the services. Ideally I dont want to even have to constantly write the same code to get the info from the request and pass it through. However the only ways I've managed to do it was using:
#Inject(REQUEST) for grpc in the service constructor. This doesn't work for the graphql requests. The only other way I saw was to only return service methods AFTER providing the data, which looks ugly as hell:
class MyService {
private _actions: {
myMethod1() { ... }
}
withTenantDetails(details) {
this._details = details;
return this._actions;
}
}
If I can somehow get the execution context within MyService that would be a good option, and make this easy using:
const getTenantId = (context: ExecutionContext) => {
if (context.getType() === 'rpc') {
logger.debug('received rpc request');
const request = context.switchToRpc().getContext();
const token = request.context.get("x-authorization");
return {
token,
id: parseTokenTenantInfo(token)
};
}
else if (context.getType<GqlContextType>() === 'graphql') {
logger.debug('received graphql request');
const gqlContext = GqlExecutionContext.create(context);
const request = gqlContext.getContext().request;
const token = request.get('Authorization');
return {
token,
id: parseTokenTenantInfo(token)
};
}
else {
throw new Error(`Unknown context type receiving in tenant param decorator`)
}
}
But I can't find any way to get the executioncontext across to the service without also having to remember to pass it every time.
It's possible to inject Request into injectable service.
For that, the Service will be Scope.Request, and no more Singleton, so a new instance will be created for each request. It's an important consideration, to avoid creating too many resources for performance reason.
It's possible to explicit this scope with :
#Injectable({ scope: Scope.REQUEST })
app.service.ts :
#Injectable({ scope: Scope.REQUEST })
export class AppService {
tenantId: string;
constructor(#Inject(REQUEST) private request: Request) {
// because of #Inject(REQUEST),
// this service becomes REQUEST SCOPED
// and no more SINGLETON
// so this will be executed for each request
this.tenantId = getTenantIdFromRequest(this.request);
}
getData(): Data {
// some logic here
return {
tenantId: this.tenantId,
//...
};
}
}
// this is for example...
const getTenantIdFromRequest = (request: Request): string => {
return request?.header('tenant_id');
};
Note that, instead of decode a JWT token in order to retrieve TENANT_ID for each request, and maybe for other service (one per service), an other approach could be to decode JWT one single time, and then add it in Request object.
It could be done with a global Guard, same as authorization guard examples of official docs.
Here just a simple example : (could be merged with a Auth Guard)
#Injectable()
export class TenantIdGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
request['tenantId'] = getTenantIdFromRequest(request);
return true; // or any other validation
}
}
For GraphQL applications, we should inject CONTEXT in place of REQUEST :
constructor(#Inject(CONTEXT) private context) {}
You have to set either request inside context, or directly TENANT_ID inside context in order to retrieve it after inside service.
I have a repository:
export class MyRepository
extends
Repository<MyEntity>
{
constructor(
protected readonly _clientId: string
) {
super()
}
// ... methods
}
I need to pass the client id through which isnt known until request time. As such, the only way I know how to do it would be to create a factory which can create the repo at run time (it's in GRPC metadata).
#Injectable()
export class MyRepositoryFactory {
create(clientId: string) {
return new MyRepository(
clientId,
);
}
}
I register this as a provider and then in my controller I call:
const { clientId } = context;
const repository = this.myRepositoryFactory.create(clientId);
However I get the error
"Cannot read property 'findOne' of undefined"
when trying to do a basic typeorm call. I can see this is because instead the repository should be registered in the module imports like:
imports: [
TypeOrmModule.forFeature([ MyRepository, MyEntity ])
],
However this only works when injecting the repository directly, not in a factory. I have no idea how to either overcome this problem, or use a different way of creating the repository at run time with GRPC meta data passed through. Any help would be appreciated, thanks.
you cant call new and have the repository connected to TypeORM.
for that you need to make call to getCustomRepository(MyCustomRepository (or connectio.getCustomRepository) so it would be connected to the connectiong pool and everything TypeORM hides under the hood.
IMO creating a custom repository per request is not such a great idea, maybe you can create a Provider that has scope REQUEST (so the provider would be created per-request) allowing NestJS to inject the repository the 'standard' way, modify it's client attribute and then use it's methods that uses the internal repository methods.
something like
#Injectable({ scope: Scope.REQUEST })
export class RequestRepositoryHandler {
client_id: string
constructor(#InjectRepository(MyEntity) private repo: Repository<MyEntity>){}
// other methods
}
// usage
#Injectable()
export class SomeService {
constructor(private providerRepo: RequestRepositoryHandler){}
async method(client_id: string){
this.providerRepo.client_id = client_id; // the provider is per-request
// now do some work
}
}
I am trying to log the session user but its not working as i suspected. I was thinking i could add it in the middle ware but clearly this wont work. Is there a strategy or pattern i should be using to accomplish this.
The PassportAuthInterceptor log should have distinct users but its only using the last one set by the Middleware
I see them using it in the log4js documentation but clearly this doesnt seem to make sense the way i demonstrated below.
I also see that the have a AuthLibrary.currentUser() call, but im unsure how to accomplish this.
https://github.com/log4js-node/log4js-node/blob/master/docs/layouts.md#tokens
seeing as how this is NestJS, can i inject the user into a service somehow as that could solve my problem.
export class LoggerMiddleware implements NestMiddleware {
private readonly logger = new AppLoggerService(LoggerMiddleware.name);
public use(req, res, next: () => void) {
log4js.getLogger('default').addContext('user', user.authName)
next();
}
}
So this is kinda gross but it works, i created 2 loggers one global (normal) and one session (scope request). The thing is that any class that is used by passport login cannot reference the SessionLoggerService .. This will have me split classes as global and Session so the classes they call can use the correct logger.
https://github.com/nestjs/nest/issues/1870
#Injectable({scope: Scope.REQUEST})
export class SessionLoggerService implements LoggerService {
public static logger = undefined;
constructor(#Inject(REQUEST) private readonly req) {
SessionLoggerService.logger = log4js.getLogger('user');
const user = this.req && this.req.user ? this.req.user : null;
if (user) {
SessionLoggerService.logger.addContext('user', user.authName);
} else {
SessionLoggerService.logger.addContext('user', '');
}
}
....
}
I am developing a web service 'A' using Node.js, which communicate with an another service 'B'. A needs to get an access token from B at the beginning, and it needs to refresh it when the token expires.
I am not very familiar with Node.js, I need to store access token and refresh token comes from B and refresh them time to time, but I am not sure if there are global variables to do this. For example, in Flask I would have kept my tokens in app['tokens'] and could be update it.
Basically what I want to do is to have a B class and a global object of B, B will have accessToken and refreshToken fields, obj.sendRequest({requestInfo}) will send a request to B service by checking expiration time of tokens and refresh them if necessary.
What is the best practice to overcome such a problem in Node.js?
From your question I don't see if you are using TypeScript or not. I highly recommend it, but it is not required. I will give you my answer with a TypeScript example because it is easier to read and understand (I think). Adapting it to pure JS should be easy.
class ApiService {
private accessToken?: string
private accessTokenExpires?: Date
private async getAccessToken(): Promise<void> {
const apiResult = await GET_TOKEN_FROM_API
this.accessToken = apiResult.access_token
this.accessTokenExpires = apiResult.access_token_expires
}
public async GetItem(id: string): Promise<Item> {
if (!this.accessToken || this.accessTokenExpires < new Date()) {
await this.getAccessToken()
}
return GET_ITEM_FROM_API
}
}
export let itemService = new ItemService()
export function mockitemService(mock: any) {
itemService = mock
}
at the place where you want to use the class you can
import { itemService } from './services/item'
const item = itemService.getItem('123')
Using ServiceClasses like this makes easier to maintain the code later or to write tests with mocked data sources.
I am working on a multi-tenant application using NestJS and offering my API through their GraphQL module. I would like to know how I could tell NestJS to instantiate my providers per web request. According to their documentation providers are singleton by default but I could not find a way to register transient or per request providers.
Let me explain a specific use case for this. In my multi-tenant implementation, I have a database per customer and every time I receive a request in the backend I need to find out for which customer it is so I need to instantiate services with a connection to the right database.
Is this even possible using NestJS?
With the release of nest.js 6.0, injection scopes were added. With this, you can choose one of the following three scopes for your providers:
SINGLETON: Default behavior. One instance of your provider is used for the whole application
TRANSIENT: A dedicated instance of your provider is created for every provider that injects it.
REQUEST: For each request, a new provider is created. Caution: This behavior will bubble up in your dependency chain. Example: If UsersController (Singleton) injects UsersService (Singleton) that injects OtherService (Request), then both UsersController and UsersService will automatically become request-scoped.
Usage
Either add it to the #Injectable() decorator:
#Injectable({ scope: Scope.REQUEST })
export class UsersService {}
Or set it for custom providers in your module definition:
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
Outdated Answer
As you can see in this issue, nestjs does not yet offer a built-in solution for request-scoped providers. But it might do so in the near future:
Once async-hooks feature (it is still experimental in node 10) is
stable, we'll think about providing a built-in solution for
request-scoped instances.
I was struggling with similar issue, and one way to achieve this is to use node-request-context module as a global request register, that will give you the request context. So you will not have separate service instances, but you can ask this static register to give you request specific instance/connection.
https://github.com/guyguyon/node-request-context
Create simple context helper:
import { createNamespace, getNamespace } from 'node-request-context';
import * as uuid from 'uuid';
export class RequestContext {
public static readonly NAMESPACE = 'some-namespace';
public readonly id = uuid.v4();
constructor(public readonly conn: Connection) { }
static create(conn: Connection, next: Function) {
const context = new RequestContext(conn);
const namespace = getNamespace(RequestContext.NAMESPACE) || createNamespace(RequestContext.NAMESPACE);
namespace.run(() => {
namespace.set(RequestContext.name, context);
next();
});
}
static currentRequestContext(): RequestContext {
const namespace = getNamespace(RequestContext.NAMESPACE);
return namespace ? namespace.get(RequestContext.name) : null;
}
static getConnection(): Connection {
const context = RequestContext.currentRequestContext();
return context ? context.conn : null;
}
}
The conn instance parameter is your connection, feel free to put there other request specific dependencies. Also the id there is just for debugging, no real need to use uuid module as I did.
Create middleware wrapper (this allows you to use DI here):
#Injectable()
export class ContextMiddleware implements NestMiddleware {
constructor(private readonly connectionManager: ...) { }
resolve(...args: any[]): MiddlewareFunction {
return (req, res, next) => {
// create the request specific connection here, probably based on some auth header...
RequestContext.create(this.connectionManager.createConnection(), next);
};
}
}
Then register new middleware in your nest application:
const app = await NestFactory.create(AppModule, {});
app.use(app.get(RequestLoggerMiddleware).resolve());
And finally the profit part - get the request specific connection anywhere in your application:
const conn = RequestContext.getConnection();
NestJS has a build-in request-scope dependency injection mechanism
https://docs.nestjs.com/fundamentals/injection-scopes
but it has serious drawbacks according to the documentation:
Scope bubbles up the injection chain. A controller that depends on a request-scoped provider will, itself, be request-scoped.
Using request-scoped providers will have an impact on application performance. While Nest tries to cache as much metadata as possible, it will still have to create an instance of your class on each request. Hence, it will slow down your average response time and overall benchmarking result. Unless a provider must be request-scoped, it is strongly recommended that you use the default singleton scope.
Recently I have created request-scope implementation for NestJS free from bubbling up the injection chain and performance impact.
https://github.com/kugacz/nj-request-scope
To use it first you have to add import of RequestScopeModule in the module class decorator:
import { RequestScopeModule } from 'nj-request-scope';
#Module({
imports: [RequestScopeModule],
})
Next, there are two ways of request-scope injection:
Inject express request object into class constructor with NJRS_REQUEST token:
import { NJRS_REQUEST } from 'nj-request-scope';
[...]
constructor(#Inject(NJRS_REQUEST) private readonly request: Request) {}
Change class inject scope to request-scope with #RequestScope() decorator:
import { RequestScope } from 'nj-request-scope';
#Injectable()
#RequestScope()
export class RequestScopeService {
You can find example implementations in this repository: https://github.com/kugacz/nj-request-scope-example