Define procedures

Learn how to extend your API by defining procedures.

A procedure is a function that is exposed to the client, it can be one of:

  • a Query - used to fetch data.
  • a Mutation - used to modify data.
  • a Subscription - used to listen to data changes.

Base procedures

Reusable base procedures is a pattern that allows you to define a set of procedures that can be reused across multiple services.

The starterkit ships with a set of base procedures that you can use to get started.

Public procedure

A public procedure is available to all clients and doesn't require authentication.

// packages/api/modules/posts/posts.router.ts
import { z } from 'zod'
import { createTRPCRouter, publicProcedure, TRPCError } from '#trpc'
import { getLatestPosts } from './posts.service'
export const postsRouter = createTRPCRouter({
latestPosts: publicProcedure.input(
z.object({
offset: z.number().default(0),
limit: z.number().default(10),
})
).query(async ({ input }) {
try {
return await getLatestPosts(input)
} catch (error: unknown) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to fetch latest posts',
cause: error,
})
}
})
})

Protected procedure

A protected procedure requires a user to be authenticated.

You can access the session and user object in the ctx argument.

// packages/api/modules/posts/posts.router.ts
import { z } from 'zod'
import { createTRPCRouter, protectedProcedure, TRPCError } from '#trpc'
import { addComment } from './posts.service'
export const postsRouter = createTRPCRouter({
addComment: protectedProcedure.input(
z.object({
postId: z.string(),
content: z.string(),
})
).mutation(async ({ ctx, input }) {
try {
return await addComment({
userId: ctx.session.user.id,
...input,
})
} catch (error: unknown) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to add comment',
cause: error,
})
}
})
})

Workspace procedure

A workspace procedure requires the user to be authenticated and belong to the supplied workspace.

The workspaceId is passed in the input object. Both workspace and workspaceMember are available in the ctx object.

The workspaceMember object contains the role of the user in the workspace.

// packages/api/modules/posts/posts.router.ts
import { z } from 'zod'
import { createTRPCRouter, workspaceProcedure, TRPCError } from '#trpc'
import { createPost } from './posts.service'
export const postsRouter = createTRPCRouter({
createPost: workspaceProcedure.input(
z.object({
content: z.string(),
})
).mutation(async ({ ctx, input }) {
try {
if (!['admin', 'editor'].includes(ctx.workspaceMember.role)) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'You do not have permission to create posts',
})
}
return await createPost({
workspaceId: ctx.workspace.id,
authorId: ctx.session.user.id,
...input,
})
} catch (error: unknown) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to create post',
cause: error,
})
}
})
})

Admin procedure

An admin procedure requires the user to be authenticated and have the admin role.

This procedure extends the workspaceProcedure and supplies the same ctx object.

// packages/api/modules/posts/posts.router.ts
import { z } from 'zod'
import { createTRPCRouter, adminProcedure, TRPCError } from '#trpc'
import { deletePost } from './posts.service'
export const postsRouter = createTRPCRouter({
deletePost: adminProcedure.input(
z.object({
id: z.string(),
})
).mutation(async ({ ctx, input }) {
try {
return await deletePost({
workspaceId: ctx.workspace.id,
id: input.id,
})
} catch (error: unknown) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to delete post',
cause: error,
})
}
})
})

Next steps

  • Read more about tRPC procedures
  • Fetching data

Was this helpful?