🔍 검색 키워드: tRPC 타입 에러, tRPC input validation 실패, tRPC ZodError, tRPC 클라이언트 타입 추론, tRPC 런타임 검증
tRPC 타입 에러: Input Validation 실패와 해결방법
증상
tRPC 클라이언트에서 정확하게 타입을 맞춰 데이터를 전송했는데도 서버에서 타입 검증 에러가 발생합니다.
Error: [UNPROCESSABLE_CONTENT]: Input validation failed
├─ expected number, received string (code: invalid_type)
└─ at "age"
또는 클라이언트에서 데이터를 보낼 때 타입스크립트 컴파일 에러:
Argument of type '{ name: string; age: string }' is not assignable
to parameter of type '{ name: string; age: number }'
원인
tRPC는 타입스크립트의 타입 검사와 런타임 검증(보통 Zod 스키마)을 별도로 수행합니다. 세 가지 주요 원인이 있습니다:
- Zod 스키마와 TS 타입이 불일치
서버에서z.number()로 정의했지만, 클라이언트 데이터는 문자열로 전달 - 타입 강제(coerce)가 없음
API 요청에서 쿼리 파라미터나 폼 데이터는 항상 문자열이지만, 스키마에서 변환하지 않음 - 선택적 필드와 기본값 처리 오류
.optional()또는.default()누락으로 undefined/null 검증 실패
해결방법
해결책 1: Zod 스키마에서 타입 강제(Coerce)
import { z } from 'zod';
import { router, publicProcedure } from '@trpc/server';
const userSchema = z.object({
name: z.string().min(1, "이름은 필수입니다"),
age: z.coerce.number().min(0, "나이는 0 이상이어야 합니다"),
email: z.string().email().optional(),
});
export const appRouter = router({
createUser: publicProcedure
.input(userSchema)
.mutation(async ({ input }) => {
// input.age는 이제 자동으로 number 타입 보장
return { success: true, age: input.age };
}),
});
해결책 2: 쿼리 파라미터 검증 시 .default() 사용
export const appRouter = router({
listUsers: publicProcedure
.input(
z.object({
limit: z.coerce.number().default(10).min(1).max(100),
skip: z.coerce.number().default(0).min(0),
sortBy: z.enum(['name', 'age', 'createdAt']).default('createdAt'),
})
)
.query(async ({ input }) => {
// input.limit, skip, sortBy 모두 안전하게 기본값 적용됨
return { data: [], total: 0 };
}),
});
해결책 3: 클라이언트에서 타입 안전성 확보
// 클라이언트
const trpc = createTRPCReact();
export function UserForm() {
const createUser = trpc.createUser.useMutation();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// 명시적 타입 변환
const payload = {
name: formData.get('name') as string,
age: parseInt(formData.get('age') as string), // 문자열 → 숫자
email: (formData.get('email') as string) || undefined,
};
// 이제 tRPC이 타입 검증 & 런타임 검증 수행
createUser.mutate(payload);
};
return (
);
}
정리표
| 문제 상황 | 원인 | 해결방법 |
| 쿼리 파라미터가 문자열인데 number 검증 실패 | Zod에서 강제 변환 안 함 | z.coerce.number() 사용 |
| 폼 데이터 전송 시 "필드 없음" 에러 | 선택적 필드에 .optional() 미적용 |
스키마에 .optional() 또는 .default() 추가 |
| TS 컴파일은 통과했는데 런타임 에러 | 타입과 Zod 스키마 불일치 | 스키마에서 satisfies z.ZodType<YourType> 검증 |
| API 응답 타입이 예상과 다름 | 뮤테이션 반환값 타입 정의 누락 | .mutation() 뒤에 .output(schema) 체인 추가 |
tRPC는 타입스크립트 안전성과 런타임 검증을 결합한 강력한 도구입니다. Zod 스키마를 "소스 오브 트루스"로 생각하고, 클라이언트 타입은 자동으로 유추되도록 하면 대부분의 타입 에러를 사전에 방지할 수 있습니다.