Nest provides several utility classes that help make it easy to write applications that function across multiple application contexts (e.g., Nest HTTP server-based, microservices and WebSockets application contexts). These utilities provide information about the current execution context which can be used to build generic guards, filters, and interceptors that can work across a broad set of controllers, methods, and execution contexts.
We cover two such classes in this chapter:
ArgumentsHost class provides methods for retrieving the arguments being passed to a handler. It allows choosing the appropriate context (e.g., HTTP, RPC (microservice), or WebSockets) to retrieve the arguments from. The framework provides an instance of
ArgumentsHost, typically referenced as a
host parameter, in places where you may want to access it. For example, the
catch() method of an exception filter is called with an
ArgumentsHost simply acts as an abstraction over a handler's arguments. For example, for HTTP server applications (when
@nestjs/platform-express is being used), the
host object encapsulates Express's
[request, response, next] array, where
request is the request object,
response is the response object, and
next is a function that controls the application's request-response cycle. On the other hand, for GraphQL applications, the
host object contains the
[root, args, context, info] array.
When building generic guards, filters, and interceptors which are meant to run across multiple application contexts, we need a way to determine the type of application that our method is currently running in. Do this with the
getType() method of
info Hint The
GqlContextTypeis imported from the
With the application type available, we can write more generic components, as shown below.
To retrieve the array of arguments being passed to the handler, one approach is to use the host object's
You can pluck a particular argument by index using the
In these examples we retrieved the request and response objects by index, which is not typically recommended as it couples the application to a particular execution context. Instead, you can make your code more robust and reusable by using one of the
host object's utility methods to switch to the appropriate application context for your application. The context switch utility methods are shown below.
Let's rewrite the previous example using the
switchToHttp() method. The
host.switchToHttp() helper call returns an
HttpArgumentsHost object that is appropriate for the HTTP application context. The
HttpArgumentsHost object has two useful methods we can use to extract the desired objects. We also use the Express type assertions in this case to return native Express typed objects:
RpcArgumentsHost have methods to return appropriate objects in the microservices and WebSockets contexts. Here are the methods for
Following are the methods for
ArgumentsHost, providing additional details about the current execution process. Like
ArgumentsHost, Nest provides an instance of
ExecutionContext in places you may need it, such as in the
canActivate() method of a guard and the
intercept() method of an interceptor. It provides the following methods:
getHandler() method returns a reference to the handler about to be invoked. The
getClass() method returns the type of the
Controller class which this particular handler belongs to. For example, in an HTTP context, if the currently processed request is a
POST request, bound to the
create() method on the
getHandler() returns a reference to the
create() method and
getClass() returns the
CatsController type (not instance).
The ability to access references to both the current class and handler method provides great flexibility. Most importantly, it gives us the opportunity to access the metadata set through the
@SetMetadata() decorator from within guards or interceptors. We cover this use case below.
Nest provides the ability to attach custom metadata to route handlers through the
@SetMetadata() decorator. We can then access this metadata from within our class to make certain decisions.
info Hint The
@SetMetadata()decorator is imported from the
With the construction above, we attached the
roles metadata (
roles is a metadata key and
['admin'] is the associated value) to the
create() method. While this works, it's not good practice to use
@SetMetadata() directly in your routes. Instead, create your own decorators, as shown below:
This approach is much cleaner and more readable, and is strongly typed. Now that we have a custom
@Roles() decorator, we can use it to decorate the
To access the route's role(s) (custom metadata), we'll use the
Reflector helper class, which is provided out of the box by the framework and exposed from the
Reflector can be injected into a class in the normal way:
info Hint The
Reflectorclass is imported from the
Now, to read the handler metadata, use the
Reflector#get method allows us to easily access the metadata by passing in two arguments: a metadata key and a context (decorator target) to retrieve the metadata from. In this example, the specified key is
'roles' (refer back to the
roles.decorator.ts file above and the
SetMetadata() call made there). The context is provided by the call to
context.getHandler(), which results in extracting the metadata for the currently processed route handler. Remember,
getHandler() gives us a reference to the route handler function.
Alternatively, we may organize our controller by applying metadata at the controller level, applying to all routes in the controller class.
In this case, to extract controller metadata, we pass
context.getClass() as the second argument (to provide the controller class as the context for metadata extraction) instead of
Given the ability to provide metadata at multiple levels, you may need to extract and merge metadata from several contexts. The
Reflector class provides two utility methods used to help with this. These methods extract both controller and method metadata at once, and combine them in different ways.
Consider the following scenario, where you've supplied
'roles' metadata at both levels.
If your intent is to specify
'user' as the default role, and override it selectively for certain methods, you would probably use the
A guard with this code, running in the context of the
create() method, with the above metadata, would result in
To get metadata for both and merge it (this method merges both arrays and objects), use the
This would result in
For both of these merge methods, you pass the metadata key as the first argument, and an array of metadata target contexts (i.e., calls to the
getClass()) methods) as the second argument.