MeWrite Docs

tRPC: Procedure not found or input validation error

tRPCでプロシージャが見つからない、または入力バリデーションエラーが発生した場合の対処法

概要

tRPCでプロシージャの呼び出しに失敗した場合のエラーと解決策です。

エラーメッセージ

TRPCClientError: No "query" procedure at path "user.getById"
TRPCError: INTERNAL_SERVER_ERROR
TRPCClientError: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "undefined",
    "path": ["id"],
    "message": "Required"
  }
]

解決策

1. 基本的なルーター定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// server/trpc.ts
import { initTRPC } from '@trpc/server'
import { z } from 'zod'

const t = initTRPC.create()

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

// server/routers/user.ts
export const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      return await db.user.findUnique({ where: { id: input.id } })
    }),

  create: publicProcedure
    .input(z.object({
      email: z.string().email(),
      name: z.string().min(1),
    }))
    .mutation(async ({ input }) => {
      return await db.user.create({ data: input })
    }),
})

2. ルーターのマージ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// server/routers/_app.ts
import { router } from '../trpc'
import { userRouter } from './user'
import { postRouter } from './post'

export const appRouter = router({
  user: userRouter,
  post: postRouter,
})

export type AppRouter = typeof appRouter

3. クライアントの設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// utils/trpc.ts
import { createTRPCReact } from '@trpc/react-query'
import type { AppRouter } from '../server/routers/_app'

export const trpc = createTRPCReact<AppRouter>()

// 使用例
function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error } = trpc.user.getById.useQuery({ id: userId })

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return <div>{data?.name}</div>
}

4. 認証付きプロシージャ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// server/trpc.ts
import { TRPCError } from '@trpc/server'

const t = initTRPC.context<Context>().create()

export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
  if (!ctx.session?.user) {
    throw new TRPCError({
      code: 'UNAUTHORIZED',
      message: 'You must be logged in',
    })
  }

  return next({
    ctx: {
      ...ctx,
      user: ctx.session.user,
    },
  })
})

// 使用
export const userRouter = router({
  getMe: protectedProcedure.query(({ ctx }) => {
    return ctx.user  // 型安全
  }),
})

5. エラーハンドリング

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// サーバーサイド
import { TRPCError } from '@trpc/server'

export const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input }) => {
      const user = await db.user.findUnique({ where: { id: input.id } })

      if (!user) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: `User with id ${input.id} not found`,
        })
      }

      return user
    }),
})

// クライアントサイド
const utils = trpc.useUtils()

const mutation = trpc.user.create.useMutation({
  onSuccess: () => {
    utils.user.getAll.invalidate()
  },
  onError: (error) => {
    if (error.data?.code === 'CONFLICT') {
      alert('Email already exists')
    }
  },
})

6. 入力バリデーションのデバッグ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Zodスキーマを詳細に定義
const createUserInput = z.object({
  email: z.string().email('Invalid email format'),
  name: z.string().min(1, 'Name is required').max(100, 'Name too long'),
  age: z.number().int().positive().optional(),
})

// クライアントで事前バリデーション
const result = createUserInput.safeParse(formData)
if (!result.success) {
  console.log(result.error.flatten())
  return
}

mutation.mutate(result.data)

7. ミドルウェアの連鎖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const loggerMiddleware = t.middleware(async ({ path, type, next }) => {
  const start = Date.now()
  const result = await next()
  const duration = Date.now() - start
  console.log(`${type} ${path} - ${duration}ms`)
  return result
})

const rateLimitMiddleware = t.middleware(async ({ ctx, next }) => {
  const ip = ctx.req.ip
  const isRateLimited = await checkRateLimit(ip)
  if (isRateLimited) {
    throw new TRPCError({ code: 'TOO_MANY_REQUESTS' })
  }
  return next()
})

export const publicProcedure = t.procedure
  .use(loggerMiddleware)
  .use(rateLimitMiddleware)

8. Next.js App Routerとの統合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/server/routers/_app'
import { createContext } from '@/server/context'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext,
  })

export { handler as GET, handler as POST }

関連エラー

関連エラー

最終更新: 2025-12-22