Your TypeScript Types, Enforced at Runtime

One TypeScript type validates and serializes every boundary of your application: HTTP, RPC, database, configuration and CLI. When code gets a type subtly wrong, whether you or an agent wrote it, it fails with an exact error at the boundary instead of passing bad data through.

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

Are you an LLM? Skip the pixels, we wrote markdown just for you: llms.txt (llms-full.txt)

One type, the whole stack

The type is the validator, the JSON format, the database schema and the API documentation. No Zod schema, class-validator DTO or Prisma model to keep in sync.

Strict at every boundary

Every HTTP request, RPC call, database row, config value and CLI argument is checked against its type at runtime, with the exact path of what is wrong.

JIT-compiled

Validators, serializers and database mappers are compiled into plain JavaScript functions for your types. Strict checking without paying for it.

Just TypeScript

No schema DSL, no decorators for dependency injection, no code generation. The TypeScript you already write, and the language models write best.

One type for the whole backend

The class User below is the validation rule, the JSON format, the database table and the API documentation at the same time. The HTTP body is validated and converted before your code runs, invalid input is answered with a 400 and the exact errors.

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

Getting Started

The safety net for agent-written TypeScript

Runtime types put Deepkit in a unique position for code written by agents. Every boundary of an application validates and serializes against the TypeScript types at runtime, so generated code is checked in the places where mistakes are expensive.

Mistakes fail loudly, where they happen

A wrong field name or a string where a number belongs is rejected at the boundary with its exact path, not stored in the database and discovered weeks later.

Nothing new to get wrong

There is no second schema language next to your types for a model to get subtly out of sync. If the TypeScript is right, validation, database and API are right.

One stack, one set of conventions

HTTP, RPC, ORM, DI, configuration and CLI come from one framework instead of five libraries glued together, so generated code stays consistent across the whole application.

Fast because it knows your types

Deepkit compiles validators, serializers and database mappers for your types at runtime into plain JavaScript functions. Time per operation, lower is better, measured against the most used libraries.

Validation with error messages

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: find one document by id

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

PostgreSQL: find one row by id

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: insert 1,000 rows

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

All benchmarks

Runtime types: validation and serialization without schemas

The type compiler makes the types of your TypeScript code available at runtime, including interfaces, generics, unions and mapped types like Omit. Validate, type guard, cast and serialize with the types you already have, instead of a Zod schema or class-validator decorators next to them.

Learn more
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 with typed parameters

Route parameters, query strings and bodies are converted and validated from their TypeScript types, return values are serialized with theirs. Functional routes or controller classes, both with dependency injection.

Learn more
// 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 without decorators

Constructor parameters are resolved by their type, interfaces included. No @Injectable(), no injection tokens, no reflect-metadata. Modules, scopes and configuration are part of the same container, which is compiled to plain JavaScript.

Learn more
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 for PostgreSQL, MySQL, SQLite and MongoDB

Entities are classes or interfaces with type annotations. Tables, indexes and migrations follow from the types. Unit of work, identity map, relations and a type-safe query API that returns real class instances.

Learn more
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

Type-safe RPC between server and client

Call methods of server controllers like local functions, over WebSockets or TCP. Arguments and results are validated and serialized with their types, including classes, dates and RxJS Observables for streams.

Learn more
// 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

Command line, configuration and modules

Commands take their arguments and flags from the function signature. Configuration is a class, loaded from environment variables and validated at startup; services receive exactly the part of it they need.

Learn more
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();

Libraries

Every part of Deepkit is its own package. Use the whole framework or only the libraries you need.

Composition

Start with App for a CLI tool or a service and add the Framework for a complete server. Every library plugs in as a module with its own configuration and services, so a project grows from a single file to a modular application without changing its foundation.

Infrastructure

Communication between your services and with your clients. Messages are binary encoded and validated with their types on both ends. The broker gives all instances of your app shared queues, caches and locks: in memory during development, on a broker server or Redis in production, without changing your code.

Filesystem

One API for files, wherever they are stored. Develop on the local disk, test in memory and run on S3 or Google Cloud Storage in production by switching only the adapter in your configuration. The code that reads and writes files stays the same.

Database

Your entities are TypeScript classes or interfaces. The same model and the same queries work with PostgreSQL, MySQL, SQLite and MongoDB, so you can test on SQLite, deploy on PostgreSQL, or change the database later without rewriting your data layer.

Fundamentals

The building blocks Deepkit is made of. Each one works on its own in any TypeScript project, a backend, a frontend or a library, and they fit together when you use more of them.

Tools

Tools that build on the types you already wrote: API documentation, database browser and debugger come from your code, not from separate definitions you have to keep up to date.

Core

Low-level libraries the others are built on, useful on their own when you need exactly that piece.

Runtime

Runtime types need the type compiler in your build. It works with the TypeScript compiler, Vite and Bun, so you keep the toolchain you already use.

Get started

Create a new Deepkit app with HTTP, database and CLI in one command.

npm init @deepkit/app@latest my-app

Getting StartedView on GitHub

Made in Germany