Deine TypeScript-Typen, zur Laufzeit durchgesetzt

Ein TypeScript-Typ validiert und serialisiert jede Grenze deiner Anwendung: HTTP, RPC, Datenbank, Konfiguration und CLI. Wenn Code einen Typ subtil falsch verwendet, egal ob du oder ein Agent ihn geschrieben hat, schlägt er mit einem exakten Fehler an der Grenze fehl, anstatt fehlerhafte Daten durchzulassen.

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

Bist du ein LLM? Überspring die Pixel, wir haben Markdown extra für dich geschrieben: llms.txt (llms-full.txt)

Ein Typ, der ganze Stack

Der Typ ist der Validator, das JSON-Format, das Datenbankschema und die API-Dokumentation. Kein Zod-Schema, kein class-validator-DTO und kein Prisma-Modell, das synchron gehalten werden muss.

Strikt an jeder Grenze

Jede HTTP-Anfrage, jeder RPC-Aufruf, jede Datenbankzeile, jeder Konfigurationswert und jedes CLI-Argument wird zur Laufzeit gegen seinen Typ geprüft, mit dem exakten Pfad dessen, was falsch ist.

JIT-kompiliert

Validatoren, Serialisierer und Datenbank-Mapper werden für deine Typen in einfache JavaScript-Funktionen kompiliert. Strikte Prüfung, ohne dafür zu bezahlen.

Einfach TypeScript

Keine Schema-DSL, keine Decorators für Dependency Injection, keine Codegenerierung. Das TypeScript, das du bereits schreibst und das Sprachmodelle am besten schreiben.

Ein Typ für das ganze Backend

Die Klasse User unten ist gleichzeitig die Validierungsregel, das JSON-Format, die Datenbanktabelle und die API-Dokumentation. Der HTTP-Body wird validiert und konvertiert, bevor dein Code läuft; ungültige Eingaben werden mit einem 400 und den exakten Fehlern beantwortet.

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" }
  ]
}

Erste Schritte

Das Sicherheitsnetz für von Agenten geschriebenes TypeScript

Laufzeittypen verschaffen Deepkit eine einzigartige Position für Code, der von Agenten geschrieben wird. Jede Grenze einer Anwendung validiert und serialisiert zur Laufzeit gegen die TypeScript-Typen, sodass generierter Code genau an den Stellen geprüft wird, an denen Fehler teuer sind.

Fehler schlagen lautstark fehl, genau dort, wo sie passieren

Ein falscher Feldname oder ein String, wo eine Zahl hingehört, wird an der Grenze mit seinem exakten Pfad abgelehnt, anstatt in der Datenbank gespeichert und erst Wochen später entdeckt zu werden.

Nichts Neues, das man falsch machen kann

Es gibt neben deinen Typen keine zweite Schemasprache, die ein Modell subtil aus dem Takt bringen könnte. Wenn das TypeScript stimmt, stimmen auch Validierung, Datenbank und API.

Ein Stack, ein Satz an Konventionen

HTTP, RPC, ORM, DI, Konfiguration und CLI stammen aus einem Framework statt aus fünf zusammengeklebten Bibliotheken, sodass generierter Code in der gesamten Anwendung konsistent bleibt.

Schnell, weil es deine Typen kennt

Deepkit kompiliert Validatoren, Serialisierer und Datenbank-Mapper für deine Typen zur Laufzeit in einfache JavaScript-Funktionen. Zeit pro Operation, niedriger ist besser, gemessen gegen die meistgenutzten Bibliotheken.

Validierung mit Fehlermeldungen

Deepkit
183 ns
typia 15.2.0
1.0 µs
Zod 4.6.7
1.6 µ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: ein Dokument anhand der ID finden

Deepkit
275 µs
mongodb (driver) 7.7.0
435 µs
Mongoose 9.11.4
497 µs
MikroORM 7.2.5
632 µs

PostgreSQL: eine Zeile anhand der ID finden

Deepkit
198 µs
Drizzle 0.45.4
420 µs
Sequelize 6.37.8
423 µs
TypeORM 1.1.2
446 µs
Prisma 7.10.1
526 µs
MikroORM 7.2.5
589 µs

PostgreSQL: 1.000 Zeilen einfügen

Deepkit
37 ms
TypeORM 1.1.2
41 ms
Sequelize 6.37.8
42 ms
MikroORM 7.2.5
49 ms
Drizzle 0.45.4
61 ms
Prisma 7.10.1
95 ms

Alle Benchmarks

Laufzeittypen: Validierung und Serialisierung ohne Schemas

Der Type-Compiler macht die Typen deines TypeScript-Codes zur Laufzeit verfügbar, einschließlich Interfaces, Generics, Unions und Mapped Types wie Omit. Validiere, nutze Type Guards, caste und serialisiere mit den Typen, die du bereits hast, statt mit einem Zod-Schema oder class-validator-Decorators daneben.

Mehr erfahren
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-Router mit typisierten Parametern

Routenparameter, Query-Strings und Bodies werden anhand ihrer TypeScript-Typen konvertiert und validiert, Rückgabewerte mit ihren Typen serialisiert. Funktionale Routen oder Controller-Klassen, beide mit Dependency Injection.

Mehr erfahren
// 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();
    }
}

Dependency Injection ohne Decorators

Konstruktorparameter werden anhand ihres Typs aufgelöst, Interfaces eingeschlossen. Kein @Injectable(), keine Injection-Tokens, kein reflect-metadata. Module, Scopes und Konfiguration sind Teil desselben Containers, der in einfaches JavaScript kompiliert wird.

Mehr erfahren
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)],
});

TypeScript-ORM für PostgreSQL, MySQL, SQLite und MongoDB

Entitäten sind Klassen oder Interfaces mit Typannotationen. Tabellen, Indizes und Migrationen ergeben sich aus den Typen. Unit of Work, Identity Map, Relationen und eine typsichere Query-API, die echte Klasseninstanzen zurückgibt.

Mehr erfahren
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

Typsicheres RPC zwischen Server und Client

Rufe Methoden von Server-Controllern wie lokale Funktionen auf, über WebSockets oder TCP. Argumente und Ergebnisse werden mit ihren Typen validiert und serialisiert, einschließlich Klassen, Datumswerten und RxJS-Observables für Streams.

Mehr erfahren
// 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

Kommandozeile, Konfiguration und Module

Befehle beziehen ihre Argumente und Flags aus der Funktionssignatur. Die Konfiguration ist eine Klasse, wird aus Umgebungsvariablen geladen und beim Start validiert; Services erhalten genau den Teil davon, den sie benötigen.

Mehr erfahren
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();

Bibliotheken

Jeder Teil von Deepkit ist ein eigenes Paket. Nutze das gesamte Framework oder nur die Bibliotheken, die du brauchst.

Komposition

Beginne mit App für ein CLI-Tool oder einen Service und füge das Framework für einen vollständigen Server hinzu. Jede Bibliothek wird als Modul mit eigener Konfiguration und eigenen Services eingebunden, sodass ein Projekt von einer einzelnen Datei zu einer modularen Anwendung wächst, ohne sein Fundament zu ändern.

Infrastruktur

Kommunikation zwischen deinen Services und mit deinen Clients. Nachrichten werden binär kodiert und auf beiden Seiten mit ihren Typen validiert. Der Broker stellt allen Instanzen deiner App gemeinsame Queues, Caches und Locks bereit: im Speicher während der Entwicklung, auf einem Broker-Server oder Redis in Produktion, ohne deinen Code zu ändern.

Dateisystem

Eine API für Dateien, egal wo sie gespeichert sind. Entwickle auf der lokalen Festplatte, teste im Speicher und betreibe in Produktion auf S3 oder Google Cloud Storage, indem du nur den Adapter in deiner Konfiguration wechselst. Der Code, der Dateien liest und schreibt, bleibt gleich.

Datenbank

Deine Entitäten sind TypeScript-Klassen oder -Interfaces. Dasselbe Modell und dieselben Queries funktionieren mit PostgreSQL, MySQL, SQLite und MongoDB, sodass du auf SQLite testen, auf PostgreSQL deployen oder die Datenbank später wechseln kannst, ohne deine Datenschicht neu zu schreiben.

Grundlagen

Die Bausteine, aus denen Deepkit besteht. Jeder funktioniert eigenständig in jedem TypeScript-Projekt, ob Backend, Frontend oder Bibliothek, und sie passen zusammen, wenn du mehrere davon nutzt.

Werkzeuge

Werkzeuge, die auf den Typen aufbauen, die du bereits geschrieben hast: API-Dokumentation, Datenbank-Browser und Debugger entstehen aus deinem Code, nicht aus separaten Definitionen, die du aktuell halten musst.

Core

Low-Level-Bibliotheken, auf denen die anderen aufbauen, auch eigenständig nützlich, wenn du genau dieses Stück brauchst.

Laufzeit

Laufzeittypen benötigen den Type-Compiler in deinem Build. Er funktioniert mit dem TypeScript-Compiler, Vite und Bun, sodass du die Toolchain behältst, die du bereits nutzt.

Loslegen

Erstelle eine neue Deepkit-App mit HTTP, Datenbank und CLI mit einem einzigen Befehl.

npm init @deepkit/app@latest my-app

Erste SchritteAuf GitHub ansehen

Made in Germany