skills/ trpc/trpc

error-handling

Throw typed errors with TRPCError and error codes (NOT_FOUND, UNAUTHORIZED, BAD_REQUEST, INTERNAL_SERVER_ERROR), configure errorFormatter for client-side Zod error display, handle errors globally with onError callback, map tRPC errors to HTTP status codes with getHTTPStatusCodeFromError().

0
Installs
—
Rating
—
Success rate
1
Files scanned
Scan passedbackend
Source on GitHub

Security scan

Scan passed

No risky patterns were found in the scanned files.

1 files scannedscanner v1.2.0Oct 11, 2026

Content sha256 788a66f2af1e0346… — run codexguild_scan_skills after installing to verify your local copy.

Static analysis is a first line of defense, not a guarantee. Read the source

SKILL.md

exact scanned copy

tRPC -- Error Handling

Setup

// server/trpc.ts
import { initTRPC } from '@trpc/server';
import { ZodError } from 'zod';

const t = initTRPC.create({
  errorFormatter({ shape, error }) {
    return {
      ...shape,
      data: {
        ...shape.data,
        zodError:
          error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
            ? error.cause.flatten()
            : null,
      },
    };
  },
});

export const router = t.router;
export const publicProcedure = t.procedure;

Core Patterns

Throwing typed errors from procedures

import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { publicProcedure, router } from './trpc';

export const appRouter = router({
  userById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(({ input }) => {
      const user = getUserFromDb(input.id);
      if (!user) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: `User with id ${input.id} not found`,
        });
      }
      return user;
    }),
});

function getUserFromDb(id: string) {
  if (id === '1') return { id: '1', name: 'Katt' };
  return null;
}

Wrapping original errors with cause

import { TRPCError } from '@trpc/server';
import { publicProcedure, router } from './trpc';

export const appRouter = router({
  riskyOperation: publicProcedure.mutation(async () => {
    try {
      return await externalService();
    } catch (err) {
      throw new TRPCError({
        code: 'INTERNAL_SERVER_ERROR',
        message: 'An unexpected error occurred, please try again later.',
        cause: err,
      });
    }
  }),
});

async function externalService() {
  throw new Error('connection refused');
}

Pass the original error as cause to retain the stack trace for debugging.

Global error handling with onError

import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './appRouter';

const server = createHTTPServer({
  router: appRouter,
  onError(opts) {
    const { error, type, path, input, ctx, req } = opts;
    console.error('Error:', error);
    if (error.code === 'INTERNAL_SERVER_ERROR') {
      // send to bug reporting service
    }
  },
});

server.listen(3000);

Extracting HTTP status from TRPCError

import { TRPCError } from '@trpc/server';
import { getHTTPStatusCodeFromError } from '@trpc/server/http';

function handleError(error: unknown) {
  if (error instanceof TRPCError) {
    const httpCode = getHTTPStatusCodeFromError(error);
    console.log(httpCode); // e.g., 400, 401, 404, 500
  }
}

Common Mistakes

[HIGH] Throwing plain Error instead of TRPCError

Wrong:

import { publicProcedure } from './trpc';

const proc = publicProcedure.query(() => {
  throw new Error('Not found');
  // client receives 500 INTERNAL_SERVER_ERROR
});

Correct:

import { TRPCError } from '@trpc/server';
import { publicProcedure } from './trpc';

const proc = publicProcedure.query(() => {
  throw new TRPCError({
    code: 'NOT_FOUND',
    message: 'User not found',
  });
  // client receives 404 NOT_FOUND
});

Plain Error objects are caught and wrapped as INTERNAL_SERVER_ERROR (500); use TRPCError with a specific code for proper HTTP status mapping.

Source: www/docs/server/error-handling.md

[MEDIUM] Expecting stack traces in production

Wrong:

import { initTRPC } from '@trpc/server';

// No explicit isDev setting
const t = initTRPC.create();
// Stack traces may or may not appear depending on NODE_ENV

Correct:

import { initTRPC } from '@trpc/server';

const t = initTRPC.create({
  isDev: process.env.NODE_ENV === 'development',
});

Stack traces are included only when isDev is true (default: NODE_ENV !== "production"); set isDev explicitly for deterministic behavior across runtimes.

Source: www/docs/server/error-handling.md

[HIGH] Not handling Zod errors in errorFormatter

Wrong:

import { initTRPC } from '@trpc/server';

// No errorFormatter -- client gets generic "Input validation failed"
const t = initTRPC.create();

Correct:

import { initTRPC } from '@trpc/server';
import { ZodError } from 'zod';

const t = initTRPC.create({
  errorFormatter({ shape, error }) {
    return {
      ...shape,
      data: {
        ...shape.data,
        zodError:
          error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
            ? error.cause.flatten()
            : null,
      },
    };
  },
});

Without a custom errorFormatter, the client receives a generic message without field-level validation details from Zod.

Source: www/docs/server/error-formatting.md

Error Code Reference

CodeHTTPUse when
BAD_REQUEST400Invalid input
UNAUTHORIZED401Missing or invalid auth credentials
FORBIDDEN403Authenticated but not authorized
NOT_FOUND404Resource does not exist
CONFLICT409Request conflicts with current state
UNPROCESSABLE_CONTENT422Valid syntax but semantic error
TOO_MANY_REQUESTS429Rate limit exceeded
INTERNAL_SERVER_ERROR500Unexpected server error

See Also

  • server-setup -- initTRPC configuration including isDev
  • validators -- input validation that triggers BAD_REQUEST errors
  • middlewares -- auth middleware throwing UNAUTHORIZED
  • server-side-calls -- catching TRPCError in server-side callers

Files

1
6.2 KB

Agent reviews

0

No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.

More from trpc/trpc8

adapter-aws-lambda

Deploy tRPC on AWS Lambda with awsLambdaRequestHandler() from @trpc/server/adapters/aws-lambda for API Gateway v1 (REST, APIGatewayProxyEvent) and v2 (HTTP, APIGatewayProxyEventV2), and Lambda Function URLs. Enable response streaming with awsLambdaStreamingRequestHandler() wrapped in awslambda.strea

Scan passed 0
adapter-express

Mount tRPC as Express middleware with createExpressMiddleware() from @trpc/server/adapters/express. Access Express req/res in createContext via CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...). Avoid global express.json() conflicting with tRPC body parsing for FormData.

Scan passed 0
adapter-fastify

Mount tRPC as a Fastify plugin with fastifyTRPCPlugin from @trpc/server/adapters/fastify. Configure prefix, trpcOptions (router, createContext, onError). Enable WebSocket subscriptions with useWSS and @fastify/websocket. Set routerOptions.maxParamLength for batch requests. Requires Fastify v5+. Fast

Scan passed 0
adapter-fetch

Deploy tRPC on WinterCG-compliant edge runtimes with fetchRequestHandler() from @trpc/server/adapters/fetch. Supports Cloudflare Workers, Deno Deploy, Vercel Edge Runtime, Astro, Remix, SolidStart. FetchCreateContextFnOptions provides req (Request) and resHeaders (Headers) for context creation. The

Scan passed 0
adapter-standalone

Mount tRPC on Node.js built-in HTTP server with createHTTPServer() from @trpc/server/adapters/standalone, createHTTPHandler() for custom http.createServer, createHTTP2Handler() for HTTP/2 with TLS. Configure basePath to slice URL prefix, CORS via the cors npm package passed as middleware option. Cre

Scan passed 0
auth

Implement JWT/cookie authentication and authorization in tRPC using createContext for user extraction, t.middleware with opts.next({ ctx }) for context narrowing to non-null user, protectedProcedure base pattern, client-side Authorization headers via httpBatchLink headers(), WebSocket connectionPara

Scan passed 0
caching

Set HTTP cache headers on tRPC query responses via responseMeta callback for CDN and browser caching. Configure Cache-Control, s-maxage, stale-while-revalidate. Handle caching with batching and authenticated requests. Avoid caching mutations, errors, and authenticated responses.

Scan passed 0
client-setup

Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typin

Scan passed 0

Related backend skillsscan passed