Next-start
api

Creating a Protected Procedure

Learn how to create a protected procedure in your tRPC API

Understanding Protected Procedures

In many applications, you'll need certain API procedures to be accessible only to authenticated users. These are called "protected procedures." Unlike public procedures, which are open to anyone, protected procedures require the user to be logged in and, optionally, have specific permissions.

Protected Procedures in tRPC

Description: Understand how protected procedures work in your tRPC API to restrict access to authenticated users only.

What Are Protected Procedures?

Protected procedures in tRPC are API endpoints that require user authentication. Unlike public procedures, which anyone can access, protected procedures ensure that only logged-in users can interact with them. This is useful for securing sensitive data or actions in your Next.js application.

Your starter template already includes a fully functional protectedProcedure. This guide explains how it’s set up and how you can use it in your own routers.


How It Works

The setup for protected procedures is pre-configured in the starter template. Here’s a breakdown of the key components:

1. Extended tRPC Context

The tRPC context is extended to include the authenticated user’s ID. This happens in trpc/init.ts:

trpc/init.ts
export const createTRPCContext = async () => {
  const session = await auth.api.getSession({ headers: await headers() });
  return { userId: session?.user?.id };
};
  • What it does: Fetches the user’s session and adds their userId to the context.
  • Why it matters: This makes user information available to all procedures.

2. Authentication Middleware

A middleware called isAuthed checks if a user is authenticated:

const isAuthed = t.middleware(async ({ ctx, next }) => {
  if (!ctx.userId) throw new TRPCError({ code: "UNAUTHORIZED" });
  const [user] = await db
    .select()
    .from(userTable)
    .where(eq(userTable.id, ctx.userId));
  if (!user) throw new TRPCError({ code: "UNAUTHORIZED" });
  return next({ ctx: { ...ctx, user } });
});
  • What it does: Ensures the userId exists and the user is valid in the database.
  • Result: Adds the full user object to the context for use in procedures.

3. Protected Procedure Definition

The protectedProcedure is created by applying the isAuthed middleware:

trpc/init.ts
export const protectedProcedure = t.procedure.use(isAuthed);
  • What it does: Combines the base procedure with the authentication check.
  • Usage: Use this instead of publicProcedure for protected endpoints.

4. Example in Action

The starter template includes an example in modules/users/server/procedures.ts:

modules/users/server/procedures.ts
export const userRouter = router({
  getUser: protectedProcedure
    .input(
      z.object({
        id: z.string(),
      }),
    )
    .query(async ({ ctx }) => {
      const { userId: id } = ctx;
 
      if (!id) {
        throw new TRPCError({ code: "UNAUTHORIZED" });
      }
 
      const [userData] = await db.select().from(user).where(eq(user.id, id));
 
      return userData;
    }),
});
  • What it does: Fetches user data by ID, but only for authenticated users.
  • Key feature: ctx.user contains the authenticated user’s data.

Using Protected Procedures

Since protectedProcedure is already set up, you can use it directly in your routers. Here’s how to create a new protected endpoint:

Example: Update User Profile

export const userRouter = router({
  updateProfile: protectedProcedure
    .input(z.object({ name: z.string(), email: z.string() }))
    .mutation(async ({ ctx, input }) => {
      await db
        .update(userTable)
        .set({ name: input.name, email: input.email })
        .where(eq(userTable.id, ctx.user.id));
      return { success: true };
    }),
});
  • Steps:

    1. Use protectedProcedure instead of publicProcedure.
    2. Access the authenticated user via ctx.user.
    3. Add your logic (e.g., update the database).
  • Result: Only the authenticated user can update their own profile.

Tips & Notes

  • Pre-Configured: The protectedProcedure is ready to use in the starter template—check trpc/init.ts for details.
  • Customization: Modify the isAuthed middleware in trpc/init.ts if you need different authentication logic.
  • Public Endpoints: Use publicProcedure for routes that don’t require authentication.

New to tRPC? The starter template’s example in modules/users/server/procedures.ts is a great place to see protected procedures in action!

How is this guide?

On this page