런타임 타입: 스키마 없는 유효성 검사와 직렬화
타입 컴파일러는 인터페이스, 제네릭, 유니언 및 Omit과 같은 매핑된 타입을 포함하여 TypeScript 코드의 타입을 런타임에 사용할 수 있게 해줍니다. 별도의 Zod 스키마나 class-validator 데코레이터 대신, 이미 가지고 있는 타입으로 유효성 검사, 타입 가드, 캐스트 및 직렬화를 수행하세요.
자세히 알아보기LLM이신가요? 픽셀은 건너뛰세요. 여러분을 위해 마크다운을 준비했습니다: llms.txt (llms-full.txt)
타입이 곧 검증기이자 JSON 형식, 데이터베이스 스키마, API 문서입니다. 동기화를 유지해야 할 Zod 스키마, class-validator DTO 또는 Prisma 모델이 없습니다.
모든 HTTP 요청, RPC 호출, 데이터베이스 행, 설정 값 및 CLI 인자는 런타임에 해당 타입에 대해 검사되며, 무엇이 잘못되었는지 정확한 경로를 알려줍니다.
검증기, 직렬화기 및 데이터베이스 매퍼는 여러분의 타입에 맞게 일반 JavaScript 함수로 컴파일됩니다. 성능 비용 없이 엄격한 검사를 제공합니다.
스키마 DSL도, 의존성 주입용 데코레이터도, 코드 생성도 없습니다. 여러분이 이미 작성하고 있고, 언어 모델이 가장 잘 작성하는 바로 그 TypeScript입니다.
아래의 User 클래스는 유효성 검사 규칙이자 JSON 형식, 데이터베이스 테이블, API 문서를 동시에 담당합니다. HTTP 본문은 코드가 실행되기 전에 검증 및 변환되며, 잘못된 입력에는 400과 정확한 오류로 응답합니다.
import { App } from '@deepkit/app'; import { FrameworkModule } from '@deepkit/framework'; import { HttpBody, HttpQuery, HttpRouterRegistry } from '@deepkit/http'; import { Database } from '@deepkit/orm'; import { SQLiteDatabaseAdapter } from '@deepkit/sqlite'; import { AutoIncrement, Email, Maximum, MinLength, PrimaryKey, Unique } from '@deepkit/type'; // validation, serialization, database table and API docs in one type class User { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date(); username: string & MinLength<3> & Unique = ''; email: Email = ''; } class AppDatabase extends Database { constructor() { super(new SQLiteDatabaseAdapter('app.sqlite'), [User]); } } const app = new App({ providers: [AppDatabase], imports: [new FrameworkModule({ migrateOnStartup: true })], }); const router = app.get(HttpRouterRegistry); // POST /users {"username": "peter", "email": "[email protected]"} router.post('/users', async (user: HttpBody<User>, database: AppDatabase) => { await database.persist(user); return user; }); // GET /users?limit=10 router.get('/users', (database: AppDatabase, limit: HttpQuery<number> & Maximum<100> = 10) => { return database.query(User).limit(limit).find(); }); app.run();
$ curl -X POST localhost:8080/users -d '{"username": "pe", "email": "nope"}' HTTP/1.1 400 Bad Request { "message": "Validation error:\nusername(minLength): Min length is 3 caused by value \"pe\",\nemail(pattern): Pattern ^\\S+@\\S+$ does not match caused by value \"nope\"", "errors": [ { "path": "username", "code": "minLength", "message": "Min length is 3", "value": "pe" }, { "path": "email", "code": "pattern", "message": "Pattern ^\\S+@\\S+$ does not match", "value": "nope" } ] }
런타임 타입 덕분에 Deepkit은 에이전트가 작성한 코드에 대해 독보적인 위치를 차지합니다. 애플리케이션의 모든 경계가 런타임에 TypeScript 타입에 따라 검증하고 직렬화하므로, 생성된 코드는 실수의 대가가 큰 곳에서 검사됩니다.
잘못된 필드 이름이나 숫자가 들어가야 할 곳의 문자열은 데이터베이스에 저장되어 몇 주 후에 발견되는 것이 아니라, 정확한 경로와 함께 경계에서 거부됩니다.
모델이 미묘하게 동기화를 놓칠 수 있는, 타입과 별도의 두 번째 스키마 언어가 없습니다. TypeScript가 올바르면 유효성 검사, 데이터베이스, API도 올바릅니다.
HTTP, RPC, ORM, DI, 설정 및 CLI가 다섯 개의 라이브러리를 이어 붙인 것이 아니라 하나의 프레임워크에서 제공되므로, 생성된 코드가 전체 애플리케이션에서 일관성을 유지합니다.
TypeScript 백엔드 프레임워크TypeScript RPC 프레임워크TypeScript CLI 프레임워크TypeScript 의존성 주입TypeScript ORM
Deepkit은 런타임에 여러분의 타입에 맞는 검증기, 직렬화기 및 데이터베이스 매퍼를 일반 JavaScript 함수로 컴파일합니다. 작업당 소요 시간이며 낮을수록 좋고, 가장 많이 사용되는 라이브러리와 비교하여 측정했습니다.
타입 컴파일러는 인터페이스, 제네릭, 유니언 및 Omit과 같은 매핑된 타입을 포함하여 TypeScript 코드의 타입을 런타임에 사용할 수 있게 해줍니다. 별도의 Zod 스키마나 class-validator 데코레이터 대신, 이미 가지고 있는 타입으로 유효성 검사, 타입 가드, 캐스트 및 직렬화를 수행하세요.
자세히 알아보기interface Order { id: number & Positive; items: { sku: string & MinLength<3>; quantity: number }[]; shipped?: Date; } if (is<Order>(data)) { data.items; // type guard, data is an Order here } validate<Order>(data); // [{ path: 'items.0.sku', code: 'minLength', ... }] cast<Order>({ id: '1', items: [], shipped: '2026-10-31' }); // { id: 1, items: [], shipped: Date } typeOf<Order>(); // all type information at runtime
라우트 매개변수, 쿼리 문자열 및 본문은 TypeScript 타입에 따라 변환 및 검증되며, 반환 값은 해당 타입으로 직렬화됩니다. 함수형 라우트와 컨트롤러 클래스 모두 의존성 주입을 지원합니다.
자세히 알아보기// GET /users/34 router.get('/users/:id', (id: number & Positive, db: AppDatabase) => db.query(User).filter({ id }).findOne(), ); // or as controller class class UserController { constructor(private db: AppDatabase) {} // GET /users?limit=10 @http.GET('/users') list(limit: HttpQuery<number> & Maximum<100> = 10) { return this.db.query(User).limit(limit).find(); } }
생성자 매개변수는 인터페이스를 포함하여 타입으로 해결됩니다. @Injectable()도, 주입 토큰도, reflect-metadata도 필요 없습니다. 모듈, 스코프 및 설정은 일반 JavaScript로 컴파일되는 동일한 컨테이너의 일부입니다.
자세히 알아보기interface Mailer { send(to: string, text: string): Promise<void>; } class SmtpMailer implements Mailer { async send(to: string, text: string) { /* ... */ } } class Signup { // resolved by its type, an interface constructor(private mailer: Mailer) {} async register(email: string) { await this.mailer.send(email, 'Welcome!'); } } const app = new App({ providers: [Signup, provide<Mailer>(SmtpMailer)], });
엔티티는 타입 어노테이션이 있는 클래스 또는 인터페이스입니다. 테이블, 인덱스 및 마이그레이션은 타입에서 도출됩니다. 작업 단위(Unit of Work), 식별자 맵(Identity Map), 관계, 그리고 실제 클래스 인스턴스를 반환하는 타입 안전한 쿼리 API를 제공합니다.
자세히 알아보기class User { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date(); constructor( public username: string & MinLength<3> & Unique, public active: boolean = true, ) {} } const database = new Database( new PostgresDatabaseAdapter({ database: 'app' }), [User], ); await database.migrate(); await database.persist(new User('peter')); const users = await database.query(User) .filter({ active: true, created: { $gt: lastWeek } }) .orderBy('created', 'desc') .limit(10) .find(); // User[], real instances
WebSocket 또는 TCP를 통해 서버 컨트롤러의 메서드를 로컬 함수처럼 호출하세요. 인자와 결과는 클래스, 날짜 및 스트림용 RxJS Observable을 포함하여 해당 타입으로 검증 및 직렬화됩니다.
자세히 알아보기// server @rpc.controller('/users') class UserController { constructor(private db: AppDatabase) {} @rpc.action() async find(username: string): Promise<User | undefined> { return await this.db.query(User) .filter({ username }) .findOneOrUndefined(); } } // client, in the browser or another server const client = new RpcWebSocketClient('ws://localhost:8080'); const users = client.controller<UserController>('/users'); const user = await users.find('peter'); // User | undefined
명령은 함수 시그니처에서 인자와 플래그를 가져옵니다. 설정은 환경 변수에서 로드되고 시작 시 검증되는 클래스이며, 서비스는 필요한 부분만 정확히 전달받습니다.
자세히 알아보기class AppConfig { databaseUrl: string = 'postgres://localhost/app'; pageSize: number & Positive = 20; } class UserList { // only the part of the configuration it needs constructor(private pageSize: AppConfig['pageSize']) {} } const app = new App({ config: AppConfig, providers: [UserList], }); // $ app user:create peter --admin app.command('user:create', ( username: string & MinLength<3>, admin: boolean & Flag = false, ) => { // ... }); // APP_PAGE_SIZE=50 sets pageSize, -1 fails at startup app.loadConfigFromEnv().run();
Deepkit의 모든 부분은 독립된 패키지입니다. 전체 프레임워크를 사용하거나 필요한 라이브러리만 사용하세요.
CLI 도구나 서비스는 App으로 시작하고, 완전한 서버가 필요하면 Framework를 추가하세요. 모든 라이브러리는 자체 설정과 서비스를 갖춘 모듈로 연결되므로, 프로젝트는 기반을 바꾸지 않고도 단일 파일에서 모듈식 애플리케이션으로 성장할 수 있습니다.
서비스 간 및 클라이언트와의 통신. 메시지는 바이너리로 인코딩되며 양쪽 끝에서 타입으로 검증됩니다. 브로커는 앱의 모든 인스턴스에 공유 큐, 캐시 및 락을 제공합니다: 개발 중에는 메모리에서, 프로덕션에서는 브로커 서버나 Redis에서, 코드 변경 없이 동작합니다.
파일이 어디에 저장되어 있든 하나의 API로 다룹니다. 설정에서 어댑터만 바꾸면 로컬 디스크에서 개발하고, 메모리에서 테스트하고, 프로덕션에서는 S3나 Google Cloud Storage에서 실행할 수 있습니다. 파일을 읽고 쓰는 코드는 그대로 유지됩니다.
엔티티는 TypeScript 클래스 또는 인터페이스입니다. 동일한 모델과 동일한 쿼리가 PostgreSQL, MySQL, SQLite 및 MongoDB에서 작동하므로, 데이터 계층을 다시 작성하지 않고도 SQLite에서 테스트하고 PostgreSQL에 배포하거나 나중에 데이터베이스를 변경할 수 있습니다.
Deepkit을 구성하는 빌딩 블록입니다. 각각은 백엔드, 프론트엔드 또는 라이브러리 등 어떤 TypeScript 프로젝트에서도 독립적으로 작동하며, 여러 개를 함께 사용하면 서로 잘 맞물립니다.
이미 작성한 타입을 기반으로 하는 도구: API 문서, 데이터베이스 브라우저 및 디버거는 계속 최신 상태로 유지해야 하는 별도의 정의가 아니라 코드에서 생성됩니다.
다른 라이브러리의 기반이 되는 저수준 라이브러리로, 바로 그 기능이 필요할 때 단독으로도 유용합니다.
런타임 타입을 사용하려면 빌드에 타입 컴파일러가 필요합니다. TypeScript 컴파일러, Vite 및 Bun과 함께 작동하므로 이미 사용 중인 툴체인을 그대로 유지할 수 있습니다.
하나의 명령으로 HTTP, 데이터베이스 및 CLI를 갖춘 새 Deepkit 앱을 만드세요.