Nestjs: Retrieve the request / context from a Decorator - node.js

I am working on a NestJS project,
I'm trying to get the executionContext accessible in a logger to filter the logs by request.
I have one logger instance per injectable, and I would like to keep this behavior (So the scope of the injectable is default).
To do this, I'm trying to create a decorator that gets the context from the request and passes it to the child services (as in the logger), to finally get the context in the logger...
I'm not sure to be clear... For now, here is my code:
export const Loggable = () => (constructor: Function) => {
for (const propertyName of Reflect.ownKeys(constructor.prototype)) {
let descriptor = Reflect.getOwnPropertyDescriptor(constructor.prototype, propertyName);
const isMethod = descriptor.value instanceof Function;
if (!isMethod)
continue;
const originalMethod = descriptor.value;
const routeArgsMetada = Reflect.getMetadata(ROUTE_ARGS_METADATA, constructor, propertyName as string);
descriptor.value = function (...args: any[]) {
const result = originalMethod.apply(this, args);
//TODO : retrieve the request / contextExecution
//TODO : pass the request / contextExecution to children functions...
return result;
};
Reflect.defineProperty(constructor.prototype, propertyName, descriptor);
Reflect.defineMetadata(ROUTE_ARGS_METADATA, routeArgsMetada, constructor, propertyName as string);
}
};
This #Loggable() decorator would be attached to all injectable classes that need to log or throw execution context
Is that possible ? If not why ?
PS: I'm wondering, how could the #Guard annotation get the context? and how could the #Req annotations get the request?
https://github.com/nestjs/nest/tree/master/packages/common/decorators/http
https://github.com/nestjs/nest/blob/master/packages/common/decorators/core/use-guards.decorator.ts

How #Req does get the Request?
Download source of NestJS from here: https://github.com/nestjs/nest
and look for 'RouteParamtypes.REQUEST' in TS files. You will find them here:
route-params.decorator.ts
route-params-factory.ts
As you can see decorators generally don't do too much. They just add some metadata to classes, methods, and arguments. All the rest do the framework.
Here #Req only creates a special parameter decorator during startup which is processed by RouteParamsFactory before calling a method.
export const Request: () => ParameterDecorator = createRouteParamDecorator(
RouteParamtypes.REQUEST,
);
So Request is not retrieved by the #Req decorator itself. It only asks the NestJS framework to fill the annotated method parameter with reference of Request before calling the method.
BTW I also struggling with the same problem as you. I also was looking for a solution on how to access ExecutionContext from decorators. But decorators can access only annotated targets (classes, handlers, arguments, ...)
I think ExecutionContext only can be accessed directly from:
pipes
guards
interceptors
or from argument decorators this way:
https://docs.nestjs.com/custom-decorators#param-decorators
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
NOTE: You can find source of createParamDecorator() in create-route-param-metadata.decorator.ts .

Related

Vue3 stubbed component trigger event with params and get return value

I'm testing my component with jest.
Inside it I have a custom component I stub:
function mountComponent(propsData, data) {
const wrapper = mount(Upload, {
props: propsData,
global: {
stubs: {
myCustomComponent: true,
},
plugins: [router],
},
data,
});
return wrapper;
}
my usage of the custom componenet is:
<my-custom-component
#upload="uploadMethod"
></my-custom-component>
I saw I can trigger the method uploadMethod by:
const upload = wrapper.find('component-stub');
upload.trigger('uploadMethod');
but my method - uploadmethod has both parameters and return value
my question is how can I set the parameters and how can I get the return value?
my question is how can I set the parameters...
Exactly like calling uploadMethod(...args) directly, except you add the event name as first parameter:
const args = [param1, param2, param3];
const upload = wrapper.find('component-stub');
upload.trigger('uploadMethod', ...args);
... and how can I get the return value?
You don't, because the subcomponent doesn't, either. You expect() that whatever should have happened in the parent component when uploadMethod is called actually happened.

Implement custom logic after the graphql query has been parsed

Is there a way to implement custom logic right after the graphql query has been parsed, but before any of the resolvers have executed?
Given this query schema
type Query {
products(...): ProductConnection!
productByHandle(handle: String!): Product
}
How can I accomplish the task of logging the info object for the products and productByHandle queries, before their resolvers have had a chance to execute?
I'm basically looking to "hook up" to an imaginary event like query:parsed, but it doesn't appear to exist. I'm using the express-graphql package.
Props to #xadm for figuring this out.
express-graphql package accepts a custom execute function, which is the function that gets called after the query has been parsed. Its return value is what gets returned from the /graphql endpoint.
import { graphHTTP } from 'express-graphql'
import { execute } from 'graphql'
app.use('/graphql', graphHTTP((req, res) => {
return {
...,
async customExecuteFn(ExecutionArgs) {
// The `info` object is available on ExecutionArgs
// { data: {...}, errors: [...] }
const result = await execute(ExecutionArgs)
return result
}
}
}))
I will still leave this here, as it might be useful for something more specific, but you should probably use the code above.
// This returns an object, whose keys are the query names and the values are the definitions ( name, resolve etc )
const queryFields = graphqlSchema.getQueryType().getFields()
// They can then be iterated, and the original `resolve` method can be monkey-patched
for (const queryName in queryFields) {
const queryInfo = queryFields[queryName]
// Grab a copy of the original method
const originalResolve = queryInfo.resolve
// Overwrite the original `resolve` method
queryInfo.resolve = function patchedResolve(src, args, context, info) {
// Your custom logic goes here
console.log(info);
// Call the original `resolve` method, preserving the context and
// passing in the arguments
return originalResolve.apply(this, arguments)
}
}

NestJS - async operation inside error filter

In our NestJS-app we've set up a custom error filter, that catches a certain type of error. For those errors we need to perform a request to elasticsearch in order to log the corresponding error information. Since the elasticsearch request is async I've defined the catch method async:
#Catch(MyExceptionType)
#Injectable()
export class MyExceptionFilter implements ExceptionFilter {
constructor(private readonly elasticsearchService: ElasticsearchService) { }
async catch(exception: MyExceptionType, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest<MyRequestModel>();
const response = ctx.getResponse<MyResponseModel>();
const elasticSearchPayload = PayloadBuilder.of(request, exception);
await this.elasticsearchService.report(elasticSearchPayload);
// ...
response.status(exception.getStatus()).json({...});
}
}
Now - so far this works fine, but I'm wondering if this is actually ok to do, as the ExceptionFilter interface strictly declares catch to be a synchronous method.
Could we run into trouble doing this?
ExceptionFilters are to define your error handling logic. I don't think it should be an issue having it async, Nest just won't wait for the logic to finish, however, it shouldn't invoke any other exception handlers due to how it's custom filter code is written.

NestJS handle service exceptions

I'm working on a NestJS app where my services are not always called by a controller or any http request at all. Rather some services are called by a cron schedule to periodically fetch data.
What would be the best way to handle errors in this scenario? I implemented a "catch-all" exception filter, but when my service is called "internally" (not by a controller/request), there error does not get caught and I have an uncaught promise error.
See my question here: Use global nest module in decorator
This decorator catches errors of a class method and logs them. The logging part is not necessary, you could implement your own error handling logic.
import { Inject } from '#nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = false) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
With this decorator you can simply add the logErrorDecorator() to your service class methods

ES6 class jest mocking

I have an ES6 class which I need to mock it's methods. Following the documentation i made a manual mock of this, and got the constructor to both be called and asserted.
My function that consumes this class is just a basic function that runs one of the class methods.
test.js
const mockConnect = jest.fn();
const mockAccess = jest.fn();
jest.mock('../../src/connection');
const connection = require('../../src/connection').default;
connection.mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true),
};
});
caller_function();
expect(connection).toHaveBeenCalled(); // works properly as the constructor is called
expect(connection).toHaveBeenCalledWith('something'); // works
expect(mockAccess).toHaveBeenCalled(); // says it was not called when it should have
caller_function.js
import connection from 'connection';
const conn = new connection('something');
export function caller_function() {
conn.access(); // returns undefined when mock says it should return true
}
This is happening because you're using mockImplementation() instead of a manual mock or the factory parameter to jest.mock(), and your mocked object is being created during the module loading process, since the constructor call is not inside of any function. What's happening is:
The call to jest.mock('../../src/connection') runs and sets connection to be an automatic mock.
The conn object is created using the automatic mock. Therefore its access method returns undefined.
The call to mockImplementation() happens, changing the connection mock. However, since the conn object has already been created, it doesn't get the custom implementation.
Moving the constructor call into caller_function is one way to fix it:
export function caller_function() {
const conn = new connection('something');
conn.access();
}
You could also use the factory parameter to jest.mock(), specifying the implementation there, instead of calling mockImplementation(). That way you won't have to change your implementation code:
const mockConnect = jest.fn();
const mockAccess = jest.fn();
import connection from '../../src/connection';
jest.mock('./so-import', () => {
return jest.fn().mockImplementation(() => {
return {
connect: mockConnect,
access: mockAccess.mockReturnValue(true)
};
});
});
...
BTW the convention for ES6 class names is to begin with an uppercase letter. I was temporarily confused by the lowercase name connection.
Did you try doing connection.mockClear(); before you write a mockImplementation for the methods?
Also please refer to this https://jestjs.io/docs/en/es6-class-mocks

Resources