Articlestack.convex.dev·2026년 7월 8일·0

Types and Validators: A Convex Cookbook

Quick Summary

이 글은 Convex에서 스키마 validator, 함수 인자 검증, TypeScript 타입을 중복 없이 연결해 데이터 일관성과 개발 편의성을 높이는 방법을 요리책 예시로 설명한다.

Types and Validators: A Convex Cookbook 관련 대표 이미지

🖼️ 인포그래픽

Types and Validators: A Convex Cookbook 내용을 설명하는 본문 이미지

🖼️ 4컷 인포그래픽

Types and Validators: A Convex Cookbook 내용을 설명하는 본문 이미지

💡 한 줄 요약

이 글은 Convex에서 스키마 validator, 함수 인자 검증, TypeScript 타입을 중복 없이 연결해 데이터 일관성과 개발 편의성을 높이는 방법을 요리책 예시로 설명한다.

📌 핵심 요약

  • Convex는 end-to-end TypeScript, 스키마 강제, 런타임 인자 검증을 통해 버그를 조기에 잡고 안전하게 개발하도록 돕지만, validator와 타입을 반복 정의하면 코드가 금방 번거로워진다.
  • 요리책 앱의 recipes 테이블 예시를 통해 schema.ts, mutation 인자, 일반 TypeScript 타입에서 name, course, ingredients, steps 같은 구조가 반복 선언되는 문제가 드러난다.
  • Convex가 생성하는 DataModel의 Doc과 Id 타입을 사용하면 데이터베이스 문서와 문서 ID를 스키마에 맞는 타입으로 프론트엔드와 백엔드 전반에서 재사용할 수 있다.
  • 스키마의 필드 validator를 export하거나 schema.tables의 validator를 직접 introspection해 함수 인자 validator로 재사용하면, 스키마 변경과 함수 검증 로직이 어긋나는 문제를 줄일 수 있다.
  • Infer, WithoutSystemFields, schema introspection을 조합하면 validator에서 TypeScript 타입을 추출하고, 새 문서 생성 시 시스템 필드를 제외한 데이터 모양을 안전하게 다룰 수 있다.

🧩 주요 포인트

  1. Convex는 end-to-end TypeScript, 스키마 강제, 런타임 인자 검증을 통해 버그를 조기에 잡고 안전하게 개발하도록 돕지만, validator와 타입을 반복 정의하면 코드가 금방 번거로워진다.
  2. 요리책 앱의 recipes 테이블 예시를 통해 schema.ts, mutation 인자, 일반 TypeScript 타입에서 name, course, ingredients, steps 같은 구조가 반복 선언되는 문제가 드러난다.
  3. Convex가 생성하는 DataModel의 Doc과 Id 타입을 사용하면 데이터베이스 문서와 문서 ID를 스키마에 맞는 타입으로 프론트엔드와 백엔드 전반에서 재사용할 수 있다.
  4. 스키마의 필드 validator를 export하거나 schema.tables의 validator를 직접 introspection해 함수 인자 validator로 재사용하면, 스키마 변경과 함수 검증 로직이 어긋나는 문제를 줄일 수 있다.
  5. Infer, WithoutSystemFields, schema introspection을 조합하면 validator에서 TypeScript 타입을 추출하고, 새 문서 생성 시 시스템 필드를 제외한 데이터 모양을 안전하게 다룰 수 있다.

🧠 상세 정리

1. Convex 타입 경험의 장점과 반복 정의 문제

글은 Convex 개발자가 타입을 제대로 활용할 때 개발 경험이 크게 좋아진다는 설명으로 시작한다. Convex는 작성 중에는 end-to-end TypeScript를 제공하고, 실행 시점에는 스키마 강제와 인자 검증으로 데이터 일관성과 보안을 확보한다. 이러한 장점은 버그를 일찍 발견하게 해 주지만, 몇 가지 패턴을 모르면 실제 코드 작성은 반복적이고 장황해질 수 있다. 특히 같은 데이터 구조를 스키마, 함수 인자 validator, 별도 TypeScript 타입으로 여러 번 선언하게 되면 유지보수 부담이 커진다는 점이 글의 출발점이다.

2. 요리책 앱 예시로 드러나는 중복

본문은 cookbook 앱을 만든다고 가정하고 recipes 테이블을 예시로 든다. 이 테이블에는 name, course, ingredients, steps 필드가 있고, course는 appetizer, main, dessert 중 하나로 제한된다. 처음에는 convex/values의 v 객체를 사용해 schema.ts에서 각 필드 validator를 정의하고, addRecipe mutation에서도 거의 같은 validator를 다시 작성한다. 이어 일반 TypeScript 함수에서는 Course와 Recipe 타입을 별도로 선언하는 모습까지 보여 주며, 동일한 구조가 여러 위치에서 반복되는 현실적인 불편을 부각한다.

3. Doc과 Id로 DataModel에서 타입 꺼내 쓰기

첫 번째 해결책은 Convex 코드 생성이 만들어 주는 DataModel 타입을 활용하는 것이다. 스키마를 정의하거나 기존 데이터에서 생성하면 Convex는 각 테이블 문서에 대응하는 Doc 타입과 테이블별 문서 ID에 대응하는 Id 타입을 제공한다. 예를 들어 React 컴포넌트에서 RecipePreview의 recipe prop을 Doc 기반으로 지정하면 recipes 테이블의 문서 구조와 맞는 데이터만 다루게 된다. RecipeDetails처럼 id를 받는 컴포넌트에서는 Id 타입을 사용해 특정 테이블의 유효한 문서 ID라는 의미를 타입에 반영할 수 있다.

4. v.id와 Id 타입의 대응

글은 TypeScript 타입의 Id와 런타임 validator인 v.id가 서로 대응된다는 점도 보여 준다. 프론트엔드나 일반 TypeScript 코드에서는 Id 타입을 사용해 특정 테이블의 문서 ID라는 정적 타입을 표현하고, Convex query의 args에서는 v.id를 사용해 런타임 인자를 검증한다. getById query 예시에서는 id 인자를 v.id로 검증한 뒤 ctx.db.get으로 해당 문서를 조회한다. 이 구조는 타입 수준의 안전성과 실행 시점 검증이 서로 맞물려 동작한다는 Convex의 핵심 흐름을 잘 보여 준다.

5. 스키마 validator를 export해 stale 상태 방지

다음 문제는 함수 인자 validator가 스키마와 같은 제약을 가져야 할 때 발생한다. listByCourse query 예시에서는 course 인자가 스키마의 course 필드와 같은 union validator를 사용해야 하지만, 이를 함수 안에 다시 쓰면 스키마를 바꿀 때마다 함수 쪽도 직접 갱신해야 한다. 글은 이를 좋지 않은 냄새로 표현하며, validator가 stale 상태로 남을 위험을 지적한다. 해결책은 schema.ts에서 courseValidator 같은 필드 validator를 먼저 정의해 export하고, 테이블 정의와 Convex 함수 인자 검증에서 같은 객체를 재사용하는 것이다.

6. schema 객체를 통한 validator 재사용과 introspection

별도의 field validator를 export하는 방식 대신, 스키마 객체를 통합적으로 유지하고 schema.tables.recipes.validator를 통해 validator에 접근하는 방법도 제시된다. 이 방식에서는 recipes 테이블 전체 validator를 가져오고, 그 fields 속성에서 course 필드 validator를 꺼내 listByCourse의 args에 사용할 수 있다. 글은 validator가 단순 검증 도구일 뿐 아니라 type-safe하게 내부 정보를 들여다볼 수 있는 구조라고 설명한다. 예를 들어 id validator에는 kind와 tableName이 있고, object validator에는 fields가 있으며, literal, array, record, union validator도 각각 value, element, key, members 같은 정보를 가진다.

7. Infer로 validator에서 TypeScript 타입 추출

백엔드 validator를 재사용하더라도 프론트엔드 UI나 일반 TypeScript 코드가 같은 타입을 사용해야 문제가 완전히 줄어든다. 이를 위해 convex/values는 Infer 타입을 제공하며, validator에서 TypeScript 타입을 추출할 수 있게 한다. courseValidator를 정의한 뒤 Course 타입을 Infer로 만들면, 별도의 문자열 union 타입을 수동으로 선언하지 않아도 된다. Menu 컴포넌트 예시에서는 useState에 Course 타입을 적용하고, 허용되지 않은 값인 side dish를 설정하려 하면 TypeScript 오류가 발생한다는 방식으로 이 이점을 설명한다.

8. 시스템 필드 제외와 새 문서 생성 데이터

Convex의 생성된 Doc 타입에는 모든 문서에 자동으로 붙는 시스템 필드인 _id와 _creationTime이 포함된다. 그러나 새 문서를 생성할 때는 보통 이런 시스템 필드를 클라이언트가 넘기면 안 되므로, 글은 convex/server의 WithoutSystemFields 제네릭 타입을 소개한다. SaveRecipeButton 예시에서는 recipeData를 시스템 필드가 빠진 recipes 문서 형태로 지정해 create mutation에 전달한다. 이어 schema.ts에서 recipeFields 객체를 export하고 이를 테이블 정의와 addRecipe mutation args에 함께 사용하면, 새 레시피 입력 데이터의 validator도 스키마와 중복 없이 일치시킬 수 있다고 설명한다.

9. schema introspection 사용 시 시스템 필드 주의점

마지막 부분은 schema.tables.recipes.validator처럼 스키마에서 validator를 직접 가져올 때의 중요한 차이를 다룬다. 해당 validator에는 Convex가 자동으로 추가하는 시스템 필드가 포함되어 있지 않다. 따라서 addRecipe처럼 새 문서를 insert하는 mutation의 args로 사용할 때는 오히려 적합하며, 별도로 recipeFields를 export하지 않고도 테이블의 사용자 정의 필드 모양을 그대로 활용할 수 있다. 본문은 시스템 필드를 추가하고 싶은 경우에 대한 언급으로 이어지지만, 제공된 원문 범위에서는 그 전체 구현이 완성되어 제시되지는 않는다.

🧾 핵심 주장 / 시사점

  • 핵심은 TypeScript 타입과 Convex validator를 별개로 관리하지 않고, 스키마를 중심으로 한 단일 출처에 최대한 가깝게 묶는 것이다.
  • Doc, Id, Infer, WithoutSystemFields는 각각 문서 타입, 문서 ID, validator 기반 타입 추출, 생성 입력 타입이라는 서로 다른 중복 제거 지점을 담당한다.
  • schema introspection은 강력하지만, 가져온 validator에 시스템 필드가 포함되는지 여부처럼 실제 데이터 흐름에 영향을 주는 세부 차이를 이해하고 사용해야 한다.

✅ 액션 아이템

  • 스키마 파일의 validator를 export하거나 schema.tables 인텐스펙션 결과를 mutation 인자 validator로 재사용해 타입·검증 중복 선언을 줄인다.
  • 요리책 예시의 name, course, ingredients, steps 필드처럼 반복되는 구조를 schema, mutation 인자, 일반 타입 정의에서 하나의 타입 원천으로 통합한다.
  • Convex DataModel Doc/Id를 중심으로 Infer, WithoutSystemFields, 새 문서 입력 타입을 묶어 프론트엔드와 백엔드 전반에서 동일한 데이터 모양을 사용한다.

❓ 열린 질문

  • schema.tables 기반 인자 validator 재사용 시 스키마 변경이 반영되지 않는 지점은 어디에 생길 가능성이 가장 큰가?
  • validator 재정의 축소가 런타임 인자 검증과 TypeScript 정적 타입 체크 간의 경계 오차를 정말 모두 제거할 수 있을까?
  • Doc/Id 타입을 전 영역에서 재사용할 때 시스템 필드 제외 기준은 어떤 규칙으로 일관되게 유지할 수 있을까?

관련 문서

공통 태그와 주제 흐름을 기준으로 같이 보면 좋은 문서를 이어서 제안합니다.