drizzle-zod
drizzle-zod
는 **Drizzle ORM **을 위한 플러그인으로, Drizzle ORM 스키마에서 Zod 스키마를 생성할 수 있게 해줍니다.
의존성 설치
npm i drizzle-zod
yarn add drizzle-zod
pnpm add drizzle-zod
bun add drizzle-zod
IMPORTANT
이 문서는 drizzle-zod@0.6.0
이상 버전을 기준으로 작성되었습니다.
Drizzle ORM v0.36.0 이상과 Zod v3.0.0 이상 버전도 함께 설치해야 합니다.
데이터 선택 스키마 정의
데이터베이스에서 쿼리한 데이터의 형태를 정의합니다. 이를 통해 API 응답을 검증할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-zod' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userSelectSchema = createSelectSchema (users);
const rows = await db .select ({ id : users .id , name : users .name }) .from (users) .limit ( 1 );
const parsed : { id : number ; name : string ; age : number } = userSelectSchema .parse (rows[ 0 ]); // 오류: 위 쿼리에서 `age`가 반환되지 않음
const rows = await db .select () .from (users) .limit ( 1 );
const parsed : { id : number ; name : string ; age : number } = userSelectSchema .parse (rows[ 0 ]); // 성공적으로 파싱됨
뷰와 열거형도 지원됩니다.
import { pgEnum } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-zod' ;
const roles = pgEnum ( 'roles' , [ 'admin' , 'basic' ]);
const rolesSchema = createSelectSchema (roles);
const parsed : 'admin' | 'basic' = rolesSchema .parse ( ... );
const usersView = pgView ( 'users_view' ) .as ((qb) => qb .select () .from (users) .where ( gt ( users .age , 18 )));
const usersViewSchema = createSelectSchema (usersView);
const parsed : { id : number ; name : string ; age : number } = usersViewSchema .parse ( ... );
데이터 삽입 스키마 정의
데이터베이스에 삽입할 데이터의 구조를 정의합니다. 이 스키마는 API 요청을 검증하는 데 사용할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createInsertSchema } from 'drizzle-zod' ;
// 사용자 테이블 정의
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () , // 자동 생성되는 기본 키
name : text () .notNull () , // 필수 이름 필드
age : integer () .notNull () // 필수 나이 필드
});
// 삽입 스키마 생성
const userInsertSchema = createInsertSchema (users);
// 잘못된 데이터 예제
const user = { name : 'John' };
const parsed : { name : string , age : number } = userInsertSchema .parse (user); // 오류: `age`가 정의되지 않음
// 올바른 데이터 예제
const user = { name : 'Jane' , age : 30 };
const parsed : { name : string , age : number } = userInsertSchema .parse (user); // 성공적으로 파싱됨
await db .insert (users) .values (parsed); // 데이터베이스에 삽입
이 코드는 drizzle-orm
과 drizzle-zod
를 사용하여 데이터베이스에 삽입할 데이터의 구조를 정의하고 검증하는 방법을 보여줍니다. createInsertSchema
함수를 통해 생성된 스키마는 API 요청의 유효성을 검사하는 데 유용합니다.
스키마 업데이트
데이터베이스에서 업데이트할 데이터의 형태를 정의합니다. API 요청을 검증하는 데 사용할 수 있습니다.
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createUpdateSchema } from 'drizzle-zod' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const userUpdateSchema = createUpdateSchema (users);
const user = { id : 5 , name : 'John' };
const parsed : { name ?: string | undefined , age ?: number | undefined } = userUpdateSchema .parse (user); // 오류: `id`는 자동 생성 컬럼이므로 업데이트할 수 없음
const user = { age : 35 };
const parsed : { name ?: string | undefined , age ?: number | undefined } = userUpdateSchema .parse (user); // 성공적으로 파싱됨
await db .update (users) .set (parsed) .where ( eq ( users .name , 'Jane' ));
스키마 세부 조정
각 create schema
함수는 필드의 스키마를 확장하거나 수정, 완전히 덮어쓸 수 있는 추가적인 선택적 매개변수를 받습니다. 콜백 함수를 정의하면 스키마를 확장하거나 수정할 수 있고, Zod 스키마를 제공하면 완전히 덮어쓸 수 있습니다.
import { pgTable , text , integer , json } from 'drizzle-orm/pg-core' ;
import { createSelectSchema } from 'drizzle-zod' ;
import { z } from 'zod' ;
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
bio : text () ,
preferences : json ()
});
const userSelectSchema = createSelectSchema (users , {
name : (schema) => schema .max ( 20 ) , // 스키마 확장
bio : (schema) => schema .max ( 1000 ) , // 스키마 확장 후 nullable/optional로 변경
preferences : z .object ({ theme : z .string () }) // 필드 덮어쓰기 (nullability 포함)
});
const parsed : {
id : number ;
name : string ,
bio ?: string | undefined ;
preferences : {
theme : string ;
};
} = userSelectSchema .parse ( ... );
위 예제에서 userSelectSchema
는 users
테이블의 스키마를 기반으로 생성됩니다. name
필드는 최대 길이를 20으로 제한하고, bio
필드는 최대 길이를 1000으로 제한한 후 nullable로 설정합니다. preferences
필드는 완전히 새로운 Zod 스키마로 덮어쓰여지며, theme
문자열을 포함하는 객체로 정의됩니다.
팩토리 함수
더 복잡한 사용 사례를 위해 createSchemaFactory
함수를 사용할 수 있습니다.
사용 예제: 확장된 Zod 인스턴스 사용하기
import { pgTable , text , integer } from 'drizzle-orm/pg-core' ;
import { createSchemaFactory } from 'drizzle-zod' ;
import { z } from '@hono/zod-openapi' ; // 확장된 Zod 인스턴스
const users = pgTable ( 'users' , {
id : integer () .generatedAlwaysAsIdentity () .primaryKey () ,
name : text () .notNull () ,
age : integer () .notNull ()
});
const { createInsertSchema } = createSchemaFactory ({ zodInstance : z });
const userInsertSchema = createInsertSchema (users , {
// 이제 확장된 인스턴스를 사용할 수 있습니다
name : (schema) => schema .openapi ({ example : 'John' })
});
데이터 타입 참조
pg .boolean ();
mysql .boolean ();
sqlite .integer ({ mode : 'boolean' });
// 스키마
z .boolean ();
pg .date ({ mode : 'date' });
pg .timestamp ({ mode : 'date' });
mysql .date ({ mode : 'date' });
mysql .datetime ({ mode : 'date' });
mysql .timestamp ({ mode : 'date' });
sqlite .integer ({ mode : 'timestamp' });
sqlite .integer ({ mode : 'timestamp_ms' });
// 스키마
z .date ();
pg .date ({ mode : 'string' });
pg .timestamp ({ mode : 'string' });
pg .cidr ();
pg .inet ();
pg .interval ();
pg .macaddr ();
pg .macaddr8 ();
pg .numeric ();
pg .text ();
pg .sparsevec ();
pg .time ();
mysql .binary ();
mysql .date ({ mode : 'string' });
mysql .datetime ({ mode : 'string' });
mysql .decimal ();
mysql .time ();
mysql .timestamp ({ mode : 'string' });
mysql .varbinary ();
sqlite .numeric ();
sqlite .text ({ mode : 'text' });
// 스키마
z .string ();
pg .bit ({ dimensions : ... });
// 스키마
z .string () .regex ( / ^ [01] +$ / ) .max (dimensions);
pg .uuid ();
// 스키마
z .string () .uuid ();
pg .char ({ length : ... });
mysql .char ({ length : ... });
// 스키마
z .string () .length (length);
pg .varchar ({ length : ... });
mysql .varchar ({ length : ... });
sqlite .text ({ mode : 'text' , length : ... });
// 스키마
z .string () .max (length);
mysql .tinytext ();
// 스키마
z .string () .max ( 255 ); // 8비트 정수 상한
mysql .text ();
// 스키마
z .string () .max ( 65_535 ); // 16비트 정수 상한
mysql .mediumtext ();
// 스키마
z .string () .max ( 16_777_215 ); // 24비트 정수 상한
mysql .longtext ();
// 스키마
z .string () .max ( 4_294_967_295 ); // 32비트 정수 상한
pg .text ({ enum : ... });
pg .char ({ enum : ... });
pg .varchar ({ enum : ... });
mysql .tinytext ({ enum : ... });
mysql .mediumtext ({ enum : ... });
mysql .text ({ enum : ... });
mysql .longtext ({ enum : ... });
mysql .char ({ enum : ... });
mysql .varchar ({ enum : ... });
mysql .mysqlEnum ( ... , ... );
sqlite .text ({ mode : 'text' , enum : ... });
// 스키마
z .enum (enum);
mysql .tinyint ();
// 스키마
z .number () .min ( - 128 ) .max ( 127 ) .int (); // 8비트 정수 하한 및 상한
mysql .tinyint ({ unsigned : true });
// 스키마
z .number () .min ( 0 ) .max ( 255 ) .int (); // 부호 없는 8비트 정수 하한 및 상한
pg .smallint ();
pg .smallserial ();
mysql .smallint ();
// 스키마
z .number () .min ( - 32_768 ) .max ( 32_767 ) .int (); // 16비트 정수 하한 및 상한
mysql .smallint ({ unsigned : true });
// 스키마
z .number () .min ( 0 ) .max ( 65_535 ) .int (); // 부호 없는 16비트 정수 하한 및 상한
pg .real ();
mysql .float ();
// 스키마
z .number () .min ( - 8_388_608 ) .max ( 8_388_607 ); // 24비트 정수 하한 및 상한
mysql .mediumint ();
// 스키마
z .number () .min ( - 8_388_608 ) .max ( 8_388_607 ) .int (); // 24비트 정수 하한 및 상한
mysql .float ({ unsigned : true });
// 스키마
z .number () .min ( 0 ) .max ( 16_777_215 ); // 부호 없는 24비트 정수 상한
mysql .mediumint ({ unsigned : true });
// 스키마
z .number () .min ( 0 ) .max ( 16_777_215 ) .int (); // 부호 없는 24비트 정수 상한
pg .integer ();
pg .serial ();
mysql .int ();
// 스키마
z .number () .min ( - 2_147_483_648 ) .max ( 2_147_483_647 ) .int (); // 32비트 정수 하한 및 상한
mysql .int ({ unsigned : true });
// 스키마
z .number () .min ( 0 ) .max ( 4_294_967_295 ) .int (); // 부호 없는 32비트 정수 상한
pg .doublePrecision ();
mysql .double ();
mysql .real ();
sqlite .real ();
// 스키마
z .number () .min ( - 140_737_488_355_328 ) .max ( 140_737_488_355_327 ); // 48비트 정수 하한 및 상한
mysql .double ({ unsigned : true });
// 스키마
z .number () .min ( 0 ) .max ( 281_474_976_710_655 ); // 부호 없는 48비트 정수 상한
pg .bigint ({ mode : 'number' });
pg .bigserial ({ mode : 'number' });
mysql .bigint ({ mode : 'number' });
mysql .bigserial ({ mode : 'number' });
sqlite .integer ({ mode : 'number' });
// 스키마
z .number () .min ( - 9_007_199_254_740_991 ) .max ( 9_007_199_254_740_991 ) .int (); // 자바스크립트 최소 및 최대 안전 정수
mysql .serial ();
// 스키마
z .number () .min ( 0 ) .max ( 9_007_199_254_740_991 ) .int (); // 자바스크립트 최대 안전 정수
pg .bigint ({ mode : 'bigint' });
pg .bigserial ({ mode : 'bigint' });
mysql .bigint ({ mode : 'bigint' });
sqlite .blob ({ mode : 'bigint' });
// 스키마
z .bigint () .min ( - 9_223_372_036_854_775_808 n ) .max ( 9_223_372_036_854_775_807 n ); // 64비트 정수 하한 및 상한
mysql .bigint ({ mode : 'bigint' , unsigned : true });
// 스키마
z .bigint () .min ( 0 ) .max ( 18_446_744_073_709_551_615 n ); // 부호 없는 64비트 정수 상한
mysql .year ();
// 스키마
z .number () .min ( 1_901 ) .max ( 2_155 ) .int ();
pg .geometry ({ type : 'point' , mode : 'tuple' });
pg .point ({ mode : 'tuple' });
// 스키마
z .tuple ([ z .number () , z .number ()]);
pg .geometry ({ type : 'point' , mode : 'xy' });
pg .point ({ mode : 'xy' });
// 스키마
z .object ({ x : z .number () , y : z .number () });
pg .halfvec ({ dimensions : ... });
pg .vector ({ dimensions : ... });
// 스키마
z .array ( z .number ()) .length (dimensions);
pg .line ({ mode : 'abc' });
// 스키마
z .object ({ a : z .number () , b : z .number () , c : z .number () });
pg .line ({ mode : 'tuple' });
// 스키마
z .tuple ([ z .number () , z .number () , z .number ()]);
pg .json ();
pg .jsonb ();
mysql .json ();
sqlite .blob ({ mode : 'json' });
sqlite .text ({ mode : 'json' });
// 스키마
const self = z .lazy (() => z .union ([ z .union ([ z .string () , z .number () , z .boolean () , z .null ()]) , z .array (self) , z .record (self)]));
sqlite .blob ({ mode : 'buffer' });
// 스키마
z .custom ((v) => v instanceof Buffer );
pg .dataType () .array ( ... );
// 스키마
z .array (baseDataTypeSchema) .length (size);