LIBRARY

ORM

@deepkit/orm

The fastest TypeScript ORM for MongoDB, MySQL, PostgreSQL, SQLite.

With data-mapper, active-record, UnitOfWork, identity map, migrations, relations support, and much more.

Use plain TypeScript — no code-generation, schema, or config files required.

read documentation

Playground

Interactive TypeScript playground with SQLite database — in your browser.

Powerful database browser

Edit your database content from your models, test queries, view relations, or seed your database directly in the browser using Deepkit ORM Browser.

content editing
content editing

TypeScript

Just write your ORM entities as regular TypeScript classes or interfaces, and annotate properties with database specific features. It's that simple.

import { PrimaryKey, AutoIncrement, Email, Index, MinLength, Unique } from '@deepkit/type';

class User {
    id: number & PrimaryKey & AutoIncrement = 0;
    createdAt: Date = new Date;

    lastName?: string;
    firstName?: string;

    email?: string & Email & Index;

    constructor(
       public username: string & MinLength<3> & Unique
    ) {}
}

Interfaces

Database entities can also be defined in a interfaces or computed types without the need to use an actual class.

import { PrimaryKey, AutoIncrement, Email, Index, MinLength, Unique } from '@deepkit/type';

interface User {
    id: number & PrimaryKey;
    createdAt: Date;
    lastName?: string;
    firstName?: string;
    email?: string & Email & Index;
    username: string & MinLength<3> & Unique;
}

Query API

The query API allows you to fetch and manipulate data in a type-safe manner.

Filtering uses an Mongo-Like query interface, that works for every database (even SQL) the same.

The API is designed to build cross-database queries that work on every database the same.

const result = await database.query(User)
  .orderBy('createdAt')
  .limit(5)
  .patchMany({credit: {$inc: 100}});
//reports affected records, e.g. [1, 2, 3, 4, 5]
result.primaryKeys;
//also returns the new credit value
result.returning.credit; //e.g. [100, 200, 100, 300, 100]

const result = await database.query(User)
  .filter({disabled: true})
  .deleteMany();
//reports affected records, e.g. [1, 2, 3, 4, 5]
result.primaryKeys;
const user = await database.query(User)
  .filter({username: 'Peter')
  .findOne(); //returns User class instance

const users = await database.query(User)
  .orderBy('lastLogin')
  .limit(10)
  .find(); //returns array of User class instance

const users = await database.query(User)
  //Mongo-Like queries for cross-databases queries
  // lastLogin > 10 ($gt = greater than)
  .filter({lastLogin: {$gt: 10})
  .limit(10)
  .find();

//aggregation queries: Get user count in each group
const users = await database.query(User)
  .groupBy('group')
  .withCount('id', 'users')
  .find(); // [{group: 'teamA', users: 5}]

Relations

Define your relations in terms of object references, and let Deepkit ORM handle the rest.

const books = await database.query(Book)
  .joinWith('author')
  .find();
for (const book of books) {
  //access the eagerly loaded relation
  book.author.username;
}

const books = await database.query(Book)
  .useInnerJoinWith('author')
    //add additional filter to the join
    .filter({username: 'Peter'})
    .end()
  .find();
import { PrimaryKey, Reference, BackReference, MinLength } from '@deepkit/type';

class Author {
    id: number & PrimaryKey = 0;
    createdAt: Date = new Date;

    lastName?: string;
    firstName?: string;

    books: Book[] & BackReference = [];

    constructor(
        public username: string & MinLength<3>
    ) {}
}

class Book {
    id: number & PrimaryKey = 0;
    createdAt: Date = new Date;

    description: string = '';

    constructor(
        public title: string & MinLength<3>,
        public author: Author & Reference,
    ) {}
}

Unit of Work

The easiest way to work with your entities. Handle ten-thousands of instances at the same time and let the UoW insert or update them in the most efficient way possible.

The identity map makes sure that all persisted and fetched instances in one session are uniquely identifiable and always the same instance.

const session = database.createSession();

for (let i = 0; i < 10_000; i++) {
  session.add(new User('User ' + i));
}

//all items are inserted extremely fast in one
//transaction with a single query
await session.commit();

const users = session.query(User).find();
for (const user of users) {
  if (user.credit < 1000) user.credit += 100;
}

//changes are automatically detected and
//patched to the database
await session.commit();

ActiveRecord

For prototyping purposes Deepkit ORM also supports the ActiveRecord pattern. It allows you to directly work with the entity class, without accessing a Database object.

import { PrimaryKey } from '@deepkit/type';
import { ActiveRecord } from '@deepkit/orm';

class User extends ActiveRecord {
    id: number & PrimaryKey = 0;
    createdAt: Date = new Date;
    //...
}

const user = new User('Marie');
await user.save();

const users = await User.query()
  .filter({logins: ${gt: 10}})
  .find();
for (const user of users) {
  user.credit += 10;
  await user.save();
}

Event system

You can hook into the UoW process or query execution using asynchronous event listeners that are for example able to modify the query itself.

This allows you to write plugins or change the behavior of your entities.

//onFetch is called for find(), findOne(),
// findOneOrUndefined(), etc
database.queryEvents.onFetch.subscribe((event) => {
  if (event.isSchemaOf(User)) {
    //modify the query
    event.query = event.query.addFilter({deleted: false});
  }
});

//onDeletePost is called after
//.deleteOne()/.deleteMany() successfully executed
database.queryEvents.onDeletePost.subscribe((event) => {
  //primaryKeys contains each primary key for
  //all affected records
  for (const id of event.deleteResult.primaryKeys) {
    await event.databaseSession.persist(
      new AuditLog('deleted', event.classSchema.getName())
    );
  }
});

Migrations

Migrations help you migrate schema changes for SQL databases in an easy yet effective way.

The migration file is automatically generated based on the difference between the actual schema of your database and your schema defined in your TypeScript code.

You can modify the generated schema migration as you wish and commit to Git, so your colleagues and deploy procedure can update the database schema correctly.

class User {
  id: number & PrimaryKey & AutoIncrement = 0;
  username: string = '';
}

//version 2 adds new fields
class User {
  id: number & PrimaryKey & AutoIncrement = 0;
  username: string = '';
  lastName: string = '';
  firstName: string = '';
}
//my-app/migration/20200917-1727.ts
import {Migration} from '@deepkit/framework';
export class SchemaMigration implements Migration {
    name = `extended user`;

    adapterName = "sqlite";

    version = 1600362068;

    up() {
        return [
          `ALTER TABLE "user" ADD COLUMN "lastName" TEXT`
          `ALTER TABLE "user" ADD COLUMN "firstName" TEXT`
        ];
    }

    down() {
        return [
          `ALTER TABLE "user" DROP COLUMN "lastName"`
          `ALTER TABLE "user" DROP COLUMN "firstName"`
        ];
    }
}

Query composition

Reuse type-safe query classes and organize database access in the most efficient way.

import { Query } from '@deepkit/orm';

class SoftDeleteQuery<T extends {deletedAt?: Date}>
  extends Query<T> {

  findDeleted(): Promise<T[]> {
    return this.filter({deletedAt: {$ne: undefined}}).find();
  }
}

const deletedUser = await database.query(User)
  .lift(SoftDeleteQuery)
  .findDeleted();
Made in Germany