# tRPC Server Routers

Writes server tRPC routers, procedures and API endpoints.

- Repository: https://github.com/lobehub/lobehub (83676 stars)
- Category: Engineering
- License: see repository
- Last updated: 2026-11-01
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add lobehub/lobehub --skill trpc-router -g -a claude-code -y`
Codex: `npx skills add lobehub/lobehub --skill trpc-router -g -a codex -y`
Cursor: `npx skills add lobehub/lobehub --skill trpc-router -g -a cursor -y`
Gemini CLI: `npx skills add lobehub/lobehub --skill trpc-router -g -a gemini-cli -y`

## Description

Use for server TRPC routers, procedures and API endpoints in apps/server/src/routers.

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# TRPC Router Guide

## File Location

- Routers: `apps/server/src/routers/lambda/<domain>.ts`
- Helpers: `apps/server/src/routers/lambda/_helpers/`
- Schemas: `apps/server/src/routers/lambda/_schema/`

## Router Structure

### Imports

```typescript
import { TRPCError } from '@trpc/server';
import { z } from 'zod';

import { SomeModel } from '@/database/models/some';
import { authedProcedure, router } from '@/libs/trpc/lambda';
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
```

### Middleware: Inject Models into ctx

**Always use middleware to inject models into `ctx`** instead of creating `new Model(ctx.serverDB, ctx.userId)` inside every procedure.

```typescript
const domainProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
  const { ctx } = opts;
  return opts.next({
    ctx: {
      fooModel: new FooModel(ctx.serverDB, ctx.userId),
      barModel: new BarModel(ctx.serverDB, ctx.userId),
    },
  });
});
```

Then use `ctx.fooModel` in procedures:

```typescript
// Good
const model = ctx.fooModel;

// Bad - don't create models inside procedures
const model = new FooModel(ctx.serverDB, ctx.userId);
```

**Exception**: When a model needs a different `userId` (e.g., watchdog iterating over multiple users' tasks), create it inline.

### Procedure Pattern

```typescript
export const fooRouter = router({
  // Query
  find: domainProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
    try {
      const item = await ctx.fooModel.findById(input.id);
      if (!item) throw new TRPCError({ code: 'NOT_FOUND', message: 'Not found' });
      return { data: item, success: true };
    } catch (error) {
      if (error instanceof TRPCError) throw error;
      console.error('[foo:find]', error);
      throw new TRPCError({
        cause: error,
        code: 'INTERNAL_SERVER_ERROR',
        message: 'Failed to find item',
      });
    }
  }),

The rest: https://github.com/lobehub/lobehub/blob/HEAD/.agents/skills/trpc-router/SKILL.md
