Documentation
Documentation
Introduction

Getting Started

Getting started
Getting StartedInstallationQuick StartProject Structure

Configuration

Configuration
ConfigurationEnvironment ConfigurationEdge ConfigDatabaseAuth SecretStripeFirebaseStorageGoogle Maps And Cloud Service AccountOAuth ProvidersEmail DeliverySentryFeature Flags

Architecture

Architecture
Architecture OverviewTech StackoRPC MiddlewareDesign Principles

Patterns

Patterns
Code Patterns & ConventionsFeature ModulesError HandlingType Safety

Database

Database
DatabaseSetupSchema DefinitionDatabase OperationsMigrationsCaching
Data Tables

API

oRPCProceduresRoutersoRPC Proxy Setup
APIsOpenAPIREST Endpoints

Auth & Access

AuthenticationConfigurationOAuth ProvidersRolesSession Management
AuthorizationUser RolesPermissions

Routing & i18n

RoutingDeclarative RoutingNavigation
InternationalizationTranslationsLocale Routing

Components & UI

ComponentsButtonsFormsNavigationDialogs
StylesTailwind CSSThemingTypography

Storage

Storage
StorageConfigurationUsageBuckets
Stripe Billing

Extra

Caching

Templates

Templates
Template GuidesCreate New FeatureCreate New PageCreate Database TableCreate oRPC RouterAdd Translations

Development

Development
DevelopmentCommandsAI AgentsBest Practices
Pulling Updates

Create oRPC Router

Step-by-step template for adding a new oRPC router

Prerequisites

Decide these first:

  1. Router name (<feature>sRouter, e.g. projectsRouter).
  2. Procedure set (list, getById, upsert, delete, etc.).
  3. Access model (publicProcedure, authProcedure, or roleProcedure).

1. Create the Router File

Create src/rpc/procedures/routers/<feature>.ts:

import {
  <feature>DeleteSchema,
  <feature>GetSchema,
  <feature>UpsertSchema,
} from "@/features/<feature>/schema";
import {
  authProcedure,
  createRPCRouter,
} from "@/rpc/procedures/rpc";
import { type ActionResponse } from "@/lib/utils/schema-utils";

export const <feature>sRouter = createRPCRouter({
  list: authProcedure
    .meta({ rateLimit: "QUERY" })
    .query(async ({ ctx }) => {
      const items = await ctx.db.<feature>s.getByOwnerId(ctx.user.id);

      return {
        success: true,
        payload: items,
      } satisfies ActionResponse;
    }),

  getById: authProcedure
    .meta({ rateLimit: "QUERY" })
    .input(<feature>GetSchema)
    .query(async ({ ctx, input }) => {
      const item = await ctx.db.<feature>s.get(input.id);

      return {
        success: true,
        payload: item,
      } satisfies ActionResponse;
    }),

  upsert: authProcedure
    .meta({ rateLimit: "MUTATION" })
    .input(<feature>UpsertSchema)
    .mutation(async ({ ctx, input }) => {
      const { id, ...data } = input;

      if (id) {
        const updated = await ctx.db.<feature>s.update(id, data);
        return {
          success: true,
          message: ctx.t("toasts.saved"),
          payload: updated,
        } satisfies ActionResponse;
      }

      const created = await ctx.db.<feature>s.create({
        ...data,
        ownerId: ctx.user.id,
      });

      return {
        success: true,
        message: ctx.t("toasts.saved"),
        payload: created,
      } satisfies ActionResponse;
    }),

  delete: authProcedure
    .meta({ rateLimit: "MUTATION" })
    .input(<feature>DeleteSchema)
    .mutation(async ({ ctx, input }) => {
      await ctx.db.<feature>s.remove(input.id);

      return {
        success: true,
        message: ctx.t("toasts.deleted"),
      } satisfies ActionResponse;
    }),
});

2. Register in Root Router

Update src/rpc/procedures/root.ts:

import { <feature>sRouter } from "@/rpc/procedures/routers/<feature>";

export const appRouter = createRPCRouter({
  // ...existing
  <feature>s: <feature>sRouter,
});

3. Add OpenAPI Exposure (Optional)

If the procedure must be REST-accessible, add .route(...) and explicit schemas:

import { actionResponseSchema } from "@/lib/utils/schema-utils";
import { z } from "zod";

list: authProcedure
  .meta({ rateLimit: "QUERY", acceptApiKey: true })
  .route({
    method: "GET",
    path: "/<feature>s",
    tags: ["<feature>s"],
    summary: "List <feature>s",
  })
  .input(z.object({}).optional())
  .output(actionResponseSchema(z.array(<feature>ApiSchema)))
  .query(async ({ ctx }) => {
    const items = await ctx.db.<feature>s.getByOwnerId(ctx.user.id);
    return { success: true, payload: items };
  });

4. Rate Limit Keys

  • "QUERY" for reads
  • "MUTATION" for writes
  • "AUTH" for auth operations
  • "ADMIN" for admin operations
  • false to disable

5. Post-Creation Checklist

  • Router file created in src/rpc/procedures/routers/
  • Router registered in src/rpc/procedures/root.ts
  • Inputs validated with Zod
  • Procedure return type follows ActionResponse
  • Rate limits set with .meta({ rateLimit: ... })
  • Optional .route(...) added for REST exposure

Related Docs

  • oRPC Procedures
  • OpenAPI
  • New Feature Template

On this page

Prerequisites
1. Create the Router File
2. Register in Root Router
3. Add OpenAPI Exposure (Optional)
4. Rate Limit Keys
5. Post-Creation Checklist
Related Docs