orpc-implementation-sops
oRPC Implementation SOPs
You are an expert TypeScript backend and frontend engineer. When implementing oRPC, you MUST strictly follow this Contract-First development pattern and the guardrails below.
1. Contract Definition (API First)
CRITICAL RULE: API paths and resource names MUST ALWAYS use singular form (e.g., /api/user, NEVER /api/users). Nested contract keys MUST also be singular (e.g., user: UserContract, NEVER users: UserContract).
import { oc, type } from '@orpc/contract';
import { z } from 'zod';
import { ResourceSchema, ListQueryInputSchema, createListResponse } from 'common/contract';
// MUST define Entity Schema FIRST
export const UserSchema = ResourceSchema.extend({
name: z.string().describe('用户名'),
email: z.string().email().describe('邮箱'),
});
// Then define Contract
export const UserContract = {
list: oc
.input(ListQueryInputSchema)
.output(createListResponse(UserSchema))
.route({ method: 'GET', path: '/api/user' }),
get: oc
.input(type<{}>())
.output(UserSchema)
.route({ method: 'GET', path: '/api/user/:id' }),
create: oc
.input(z.object({ name: z.string(), email: z.string().email() }))
.output(UserSchema)
.route({ method: 'POST', path: '/api/user' }),
};
// Nested Contract — singular keys
export const AppContract = {
user: UserContract,
health: oc.output(z.object({ ok: z.boolean() })).route({ method: 'GET', path: '/health' }),
};
2. Server Implementation
Errors MUST be thrown using ORPCError with standard codes.
import { implement, ORPCError } from '@orpc/server';
import { handleRPCContract } from 'common/orpc/server';
export function createUserContractImpl() {
const os = implement(UserContract);
return {
list: os.list.handler(async ({ input }) => {
const data = await db.user.findMany({ take: input.limit ?? 20 });
return { total: data.length, data };
}),
get: os.get.handler(async ({ input }) => {
const user = await db.user.findUnique({ where: { id: input.id } });
if (!user) throw new ORPCError('NOT_FOUND', { message: 'User not found' });
return user;
}),
};
}
3. Client & React Query Integration
import { createRpcContractClient } from 'common/orpc';
import { createORPCReactQueryUtils } from '@orpc/react-query';
const client = createRpcContractClient<typeof AppContract>({
baseUrl: 'http://localhost:3000',
getApiKey: () => getAccessToken(),
});
export const orpc = createORPCReactQueryUtils(client);
// Usage in React Component
const { data } = useQuery(orpc.user.list.queryOptions({ input: { limit: 20 } }));
4. Schema & Error Handling Constraints
When generating or modifying Zod schemas and errors, you MUST adhere to these rules:
- Documentation: MUST append
.describe('...')to string fields for frontend tooltip generation. - Dates: MUST use
z.coerce.date()to handle automatic type coercion from strings. - Optionality: MUST use
.nullish()over.optional()or.nullable()for broader compatibility unless strictly specified. - Bypassing Validation: Use
oc.output(type<ComplexType>())only when standard Zod validation is too heavy. - File Uploads: MUST use
z.instanceof(File)for Blob/File typing. - Type Export: MUST export inferred types:
export type User = z.infer<typeof UserSchema>; - Error Codes: MUST use standard HTTP-mapped codes only:
NOT_FOUND,BAD_REQUEST,UNAUTHORIZED,FORBIDDEN,CONFLICT,INTERNAL_SERVER_ERROR.
5. Protocol Selection
| Feature | RPC (/api/rpc) |
OpenAPI (/api) |
|---|---|---|
| Routing | Single endpoint | RESTful |
| Batch | Yes | Yes |
| OpenAPI | No | Yes |
| Perf | Higher | Standard |
Rule: Frontend uses RPC protocol. External/third-party API uses OpenAPI protocol.
6. Advanced Patterns
For complex operations such as modeling external REST APIs, Hono integration, File/Blob uploads, SSE/Streaming, or AsyncIterator conversion, DO NOT guess. You MUST read the detailed reference first: references/orpc-pattern.md
More from wenerme/ai
glab-cli
Use when interacting with GitLab via the glab CLI: creating/reviewing merge requests, managing issues, monitoring CI/CD pipelines, making API calls, or performing any GitLab operation from the terminal. Triggers on glab, gitlab cli, merge request, MR create, pipeline status, ci lint.
32mikro-orm-v6-to-v7
Use when upgrading @mikro-orm packages from v6 to v7, fixing v7 runtime/type errors (decorator SyntaxError, persistAndFlush removed, nativeInsert not found), adapting knex to kysely or better-sqlite to new SQLite drivers, running MikroORM in Edge/Bun/node:sqlite environments, or choosing between defineEntity vs decorator entity definitions. Triggers on "mikro-orm v7", "persistAndFlush", "@mikro-orm/decorators", "@mikro-orm/sql", "defineEntity", "bun:sqlite mikro-orm".
31zustand-mutative-pattern
Use when implementing React state management with Zustand, including context-scoped stores, mutative updates, or actions namespace patterns
25tmux-session-manager
Use when executing commands, running builds, starting services, or monitoring logs in a visible tmux pane
25wode-db-schema-pattern
Use when designing, creating, or modifying PostgreSQL table schemas in the Wode project, including ID strategy, multi-tenant isolation, or naming conventions
24chrome-devtools
Uses Chrome DevTools via MCP for efficient debugging, troubleshooting and browser automation. Use when debugging web pages, automating browser interactions, analyzing performance, or inspecting network requests. This skill does not apply to `--slim` mode (MCP configuration).
24