Docs
Middlewares

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.

logger.middleware.ts
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.

dogs.router.ts
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:

user.router.ts
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.