Middlewares
With this adapter you can create procedure middlewares,
These middlewares can be added to a router class, either globally for all procedures in that router or individually to specific procedures using the @UseMiddlewares()
decorator.
The @UseMiddlewares()
decorator accepts multiple middlewares, executing them in the provided order. For instance:
@UseMiddlewares(LoggingMiddleware, AuthMiddleware)
In this example, LoggingMiddleware
runs first, followed by AuthMiddleware
.
If both the router and the procedure have middlewares defined via @UseMiddlewares()
, the router-level middlewares will execute first, followed by the procedure-level middlewares, provided there are no duplicates.
This middleware approach in tRPC is similar to the Guards
and Middlewares
concept found in NestJS.
If you are not sure about the basic concepts of NestJS or tRPC middlewares, you can dive into those concepts in their official documentation.
Creating a Middleware
You can implement a middleware in a class with an @Injectable()
decorator. The class should implement the TRPCMiddleware
interface.
Let's start by implementing a simple logger middleware.
import { TRPCMiddleware, TRPCMiddlewareOptions } from 'nestjs-trpc';
import { Inject, Injectable, ConsoleLogger } from '@nestjs/common';
import type { Context } from "nestjs-trpc/types";
@Injectable()
export class LoggedMiddleware implements TRPCMiddleware<Context> {
constructror(
@Inject(ConsoleLogger) private readonly consoleLogger: ConsoleLogger
) {}
use(opts: TRPCMiddlewareOptions) {
const start = Date.now();
const { next, path, type } = opts;
const result = await next();
const durationMs = Date.now() - start;
const meta = { path, type, durationMs }
result.ok
? this.consoleLogger.log('OK request timing:', meta)
: this.consoleLogger.error('Non-OK request timing', meta);
return result;
}
}
Middleware registration
Similar to NestJS providers, we need to register the middleware with Nest so that it can perform the injection and type generation.
We do this by editing our module file and adding the middleware to the providers
array of the @Module()
decorator.
Applying middleware
To use the middleware in your procedure instead of the default publicProcedure
, pass the class to the @UseMiddlewares()
method decorator.
import { DatabaseService } from "./database.service.ts";
import { LoggedMiddleware } from "./logged.middleware.ts";
import { AuthMiddleware } from "./auth.middleware.ts";
import { Router, Query, UseMiddlewares } from 'nestjs-trpc';
import { Inject } from '@nestjs/common';
import { z } from 'zod';
const dogsSchema = z.object({
name: z.string(),
breed: z.enum(["Labrador", "Corgi", "Beagle", "Golden Retriver"])
});
@Router()
export class DogsRouter {
constructor(@Inject(DatabaseService) private databaseService: DatabaseService){}
@UseMiddlewares(LoggedMiddleware, AuthMiddleware)
@Query({ output: z.array(dogSchema) })
async findAll(): string {
const dogs = await this.databaseService.dogs.findMany();
return dogs;
}
}
Modified Context
With middlewares you can change and inject certain parameters in the context to be available to the procedure, this can be done by using the next()
method available from the opts
parameter.
Generated Context
When you modify the base context with a middleware, a new type will be generated with the middleware name and a Context
suffix, for example if you have an AuthMiddleware
, the generated type would be named AuthMiddlewareContext
.
Then you can import those types and use throughout your procedures from nestjs-trpc/types
, for example:
import type { AuthMiddlewareContext } from 'nestjs-trpc/types';
import { UserService } from "./user.service.ts";
import { AuthMiddleware } from "./auth.middleware.ts";
import { Router, Query, UseMiddlewares } from 'nestjs-trpc';
import { Inject } from '@nestjs/common';
import { z } from 'zod';
const userSchema = z.object({
name: z.string(),
email: z.string(),
avatar: z.string()
});
@Router()
export class UserRouter {
constructor(@Inject(UserService) private userService: UserService){}
@UseMiddlewares(AuthMiddleware)
@Query({ output: userSchema })
async getUserProfile(@Context() ctx: AuthMiddlewareContext): string {
const { userId } = ctx.auth;
return await this.userService.getUserById(userId);
}
}
Dependency injection
UseMiddlewares fully supports Dependency Injection. Just as with NestJS middlewares and guards, they are able to inject dependencies that are available within the same module. As usual, this is done through the constructor
.