Skip to main content

14.3 - Defining tRPC Context and Middleware

Context in tRPC

The context in tRPC is a way to share common data or functionality across all procedures and routers in your API. This can include things like authentication information, database connections, or other shared resources. The context is set up when creating your tRPC router and is available in all your procedures.

Creating Context

You define the context function when you initialize your tRPC router. Here’s an example using Drizzle ORM:

// server/trpc/context.ts
import { Drizzle } from "drizzle-orm";
import { inferAsyncReturnType, initTRPC } from "@trpc/server";
import { NextApiRequest, NextApiResponse } from "next";

const drizzle = new Drizzle(/* Your Drizzle configuration here */);

export const createContext = ({
req,
res,
}: {
req: NextApiRequest;
res: NextApiResponse;
}) => ({
drizzle,
user: req.user, // Example of adding user from request, if authenticated
});

export type Context = inferAsyncReturnType<typeof createContext>;

In this example:

  • drizzle: A client instance is added to the context to be used in procedures for database operations.
  • user: Example of including user information from the request, useful for authentication and authorization.

Using Context in Procedures

In your tRPC procedures, you can now access the context:

// server/trpc/routers/userRouter.ts
import { createTRPCRouter } from "../trpc";
import { z } from "zod";

export const userRouter = createTRPCRouter({
getUser: {
input: z.object({ id: z.string() }),
resolve: async ({ input, ctx }) => {
return ctx.drizzle.user.findUnique({
where: { id: input.id },
});
},
},
});

In this example:

  • ctx.drizzle: Accesses the Drizzle client from the context to interact with the database.

Middleware in tRPC

Middleware in tRPC allows you to run code before or after a procedure is executed. This is useful for tasks like authentication, logging, and validation.

Defining Middleware

Here’s an example of a simple middleware that checks for authentication:

// server/trpc/middleware/authMiddleware.ts
import { TRPCError } from "@trpc/server";

export const isAuthenticated = (procedure) => {
return procedure.use(async ({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You must be logged in to access this resource.",
});
}
return next();
});
};

In this example:

  • isAuthenticated: Middleware function that checks if the user is authenticated. If not, it throws an error.

Applying Middleware to Procedures

You can apply middleware to specific procedures or to an entire router:

// server/trpc/routers/protectedRouter.ts
import { createTRPCRouter } from "../trpc";
import { isAuthenticated } from "../middleware/authMiddleware";
import { z } from "zod";

export const protectedRouter = createTRPCRouter({
getProtectedData: isAuthenticated({
input: z.object({ id: z.string() }),
resolve: async ({ input, ctx }) => {
return ctx.drizzle.protectedData.findUnique({
where: { id: input.id },
});
},
}),
});

In this example:

  • isAuthenticated: Applied to the getProtectedData procedure to ensure that only authenticated users can access it.