TypeScriptの型を、ランタイムで強制

1つのTypeScript型が、HTTP、RPC、データベース、設定、CLIといったアプリケーションのあらゆる境界でバリデーションとシリアライズを行います。あなたが書いたコードでもエージェントが書いたコードでも、型がわずかに間違っていれば、不正なデータを通過させることなく、境界で正確なエラーとして失敗します。

Discordnpm
npm init @deepkit/app@latest my-app

あなたはLLMですか?ピクセルは飛ばしてください。あなたのためにMarkdownを用意しました: llms.txt (llms-full.txt)

1つの型で、スタック全体を

型がそのままバリデーター、JSONフォーマット、データベーススキーマ、APIドキュメントになります。同期を保つべきZodスキーマ、class-validatorのDTO、Prismaモデルは必要ありません。

あらゆる境界で厳格に

すべてのHTTPリクエスト、RPC呼び出し、データベースの行、設定値、CLI引数は、ランタイムでその型に対してチェックされ、何が間違っているかを正確なパスで示します。

JITコンパイル

バリデーター、シリアライザー、データベースマッパーは、あなたの型に合わせてプレーンなJavaScript関数にコンパイルされます。コストをかけずに厳格なチェックを実現します。

ただのTypeScript

スキーマDSLも、依存性注入のためのデコレーターも、コード生成もありません。あなたが普段書いているTypeScript、そして言語モデルが最も得意とするTypeScriptそのものです。

バックエンド全体に1つの型

以下のクラス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" }
  ]
}

はじめに

エージェントが書いたTypeScriptのためのセーフティネット

ランタイム型により、Deepkitはエージェントが書いたコードに対して独自の立ち位置にあります。アプリケーションのあらゆる境界がランタイムでTypeScriptの型に対してバリデーションとシリアライズを行うため、生成されたコードは、ミスが高くつく箇所でチェックされます。

ミスは発生した場所で、はっきりと失敗する

間違ったフィールド名や、数値であるべき場所の文字列は、正確なパスとともに境界で拒否されます。データベースに保存されて数週間後に発覚することはありません。

新たに間違える要素がない

型とは別に、モデルがわずかに同期を崩してしまうような第二のスキーマ言語は存在しません。TypeScriptが正しければ、バリデーション、データベース、APIも正しくなります。

1つのスタック、1つの規約

HTTP、RPC、ORM、DI、設定、CLIは、5つのライブラリを寄せ集めるのではなく1つのフレームワークから提供されるため、生成されたコードはアプリケーション全体で一貫性を保ちます。

型を知っているから速い

Deepkitは、あなたの型に合わせたバリデーター、シリアライザー、データベースマッパーをランタイムでプレーンなJavaScript関数にコンパイルします。1操作あたりの時間(低いほど良い)を、最もよく使われているライブラリと比較して計測しています。

エラーメッセージ付きのバリデーション

Deepkit
184 ns
typia 15.2.0
992 ns
Zod 4.6.7
1.4 µs
Valibot 1.5.0
1.9 µs
TypeBox 0.34.52
8.4 µs
ArkType 2.2.9
20 µs
class-validator 0.15.1
27 µs

MongoDB: IDで1件のドキュメントを検索

Deepkit
299 µs
mongodb (driver) 7.7.0
428 µs
Mongoose 9.11.4
492 µs
MikroORM 7.2.5
638 µs

PostgreSQL: IDで1行を検索

Deepkit
377 µs
Sequelize 6.37.8
424 µs
Drizzle 0.45.4
431 µs
TypeORM 1.1.2
446 µs
Prisma 7.10.1
533 µs
MikroORM 7.2.5
603 µs

PostgreSQL: 1,000行を挿入

Deepkit
62 ms
Sequelize 6.37.8
64 ms
TypeORM 1.1.2
73 ms
MikroORM 7.2.5
75 ms
Drizzle 0.45.4
85 ms
Prisma 7.10.1
126 ms

すべてのベンチマーク

ランタイム型:スキーマ不要のバリデーションとシリアライズ

型コンパイラは、インターフェース、ジェネリクス、ユニオン、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

型付きパラメーターを備えたHTTPルーター

ルートパラメーター、クエリ文字列、ボディはそれぞれの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)],
});

PostgreSQL、MySQL、SQLite、MongoDB対応のTypeScript ORM

エンティティは型アノテーションを持つクラスまたはインターフェースです。テーブル、インデックス、マイグレーションは型から導かれます。Unit of Work、アイデンティティマップ、リレーション、そして実際のクラスインスタンスを返す型安全なクエリ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

サーバーとクライアント間の型安全なRPC

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上で動作し、コードを変更する必要はありません。

ファイルシステム

ファイルの保存場所にかかわらず、1つのAPIで扱えます。設定でアダプターを切り替えるだけで、ローカルディスクで開発し、メモリ上でテストし、本番ではS3やGoogle Cloud Storageで動作させられます。ファイルを読み書きするコードは変わりません。

データベース

エンティティはTypeScriptのクラスまたはインターフェースです。同じモデルと同じクエリがPostgreSQL、MySQL、SQLite、MongoDBで動作するため、SQLiteでテストし、PostgreSQLにデプロイし、あるいは後からデータベースを変更しても、データレイヤーを書き直す必要はありません。

基礎

Deepkitを構成するビルディングブロック。それぞれがバックエンド、フロントエンド、ライブラリなど、あらゆるTypeScriptプロジェクトで単独で動作し、複数を組み合わせて使うとうまく連携します。

ツール

すでに書いた型を基盤とするツール:APIドキュメント、データベースブラウザー、デバッガーは、最新の状態に保つ必要がある別の定義からではなく、あなたのコードから生成されます。

コア

他のライブラリの土台となる低レベルライブラリ。まさにその部分が必要なときには単独でも役立ちます。

ランタイム

ランタイム型には、ビルドに型コンパイラが必要です。TypeScriptコンパイラ、Vite、Bunで動作するため、既存のツールチェーンをそのまま使い続けられます。

はじめる

HTTP、データベース、CLIを備えた新しいDeepkitアプリを1つのコマンドで作成できます。

npm init @deepkit/app@latest my-app

はじめにGitHub で表示

Made in Germany