An Entity Framework-inspired ORM for TypeScript.
Model ordinary classes, write typed queries, track changes, and evolve schemas across SQLite, Postgres, and MySQL.
Install · Quick start · Docs · Model · Packages · Migrations · Learn · Alpha
Important
EntityKit is prerelease software; APIs may change before 1.0. This branch is
the coordinated 0.1.0-alpha.2 family. The previous alpha.1 release lacks
both @entitykit/nestjs and the source-backed DbContext constructor shown
below; if @alpha still resolves there, select a shared source explicitly
with options.useDataSource(source) in configure().
With a configured context, application code looks like this:
const user = db.users.create({
id: "usr_1",
email: "ada@example.com",
name: "Ada",
});
await db.saveChanges();
const users = await db.users
.where(user => user.email.endsWith("@example.com"))
.orderBy(user => user.name)
.toArray();The quick start below includes the complete model and context setup.
Install the provider-neutral runtime, one database provider, and the CLI:
npm install @entitykit/core@alpha @entitykit/sqlite@alpha
npm install -D @entitykit/cli@alpha| Database | Provider | Driver |
|---|---|---|
| SQLite | @entitykit/sqlite |
Built into Node |
| Postgres | @entitykit/postgres |
pg |
| MySQL | @entitykit/mysql |
mysql2 |
# Postgres
npm install @entitykit/core@alpha @entitykit/postgres@alpha pg
# MySQL
npm install @entitykit/core@alpha @entitykit/mysql@alpha mysql2EntityKit requires Node 22.13 or newer. Importing @entitykit/core does not
load a provider or database driver.
Define an ordinary class and map it in a DbContext:
import {
DbContext,
type ModelBuilder,
} from "@entitykit/core";
import { createSqliteDataSource } from "@entitykit/sqlite";
type NewUser = { id: string; email: string; name: string };
class User {
id: string;
email: string;
name: string;
constructor(input: NewUser) {
this.id = input.id;
this.email = input.email;
this.name = input.name;
}
}
class AppDbContext extends DbContext {
readonly users = this.set(User);
protected override model(model: ModelBuilder): void {
model.entity(User, entity => {
entity.toTable("users");
entity.hasKey(user => user.id);
entity.property(user => user.id).hasColumnType("text").isRequired();
entity.property(user => user.email).hasColumnType("text").isRequired();
entity.property(user => user.name).hasColumnType("text").isRequired();
entity.materialize(values => {
const { id, email, name } = values;
if (typeof id !== "string" ||
typeof email !== "string" ||
typeof name !== "string") {
throw new Error("Cannot materialize User: required fields are missing or invalid.");
}
return new User({ id, email, name });
});
entity.hasIndex(user => user.email).isUnique();
});
}
}
const dataSource = createSqliteDataSource("./app.db");Create a local schema, write a row, query it, and save a tracked change.
users.create() constructs and tracks the entity; it executes no SQL.
saveChanges() persists the pending work:
try {
await using db = dataSource.createContext(AppDbContext);
await db.database.ensureCreated();
const user = db.users.create({
id: "usr_1",
email: "ada@example.com",
name: "Ada",
});
await db.saveChanges();
const loaded = await db.users
.where(candidate => candidate.email.eq("ada@example.com"))
.single();
loaded.name = "Ada Lovelace";
await db.saveChanges();
} finally {
await dataSource.dispose();
}The lifecycle is deliberate: create one provider data source for the
application, create a fresh context for every request, job, or other unit of
work, dispose that context, then dispose the data source during application
shutdown. DbContext accepts the source in its optional constructor, so a
source-backed context needs no provider-specific configure() method.
ensureCreated() is convenient for a one-time prototype or disposable-database
bootstrap; it is not a deployment or schema-evolution primitive. Once a schema
must evolve without losing data, use source-controlled migrations.
|
Domain model Plain TypeScript classes, explicit fluent mapping, value converters, complex properties, keys, indexes, and relationships. |
Typed queries Filters, ordering, paging, projections, joins, aggregates, relationship predicates, streaming, and parameterized SQL. |
|
Unit of work Identity-map materialization, snapshot change tracking, transactional saveChanges(), savepoints, and optimistic concurrency.
|
Explicit relationships Split-query includes, filtered collections, explicit loading, and opt-in awaitable lazy loading—never hidden property-access I/O. |
|
Schema workflow Model snapshots, generated migrations, dry runs, SQL scripts, guarded destructive changes, and database-first model generation. |
Application controls Tenant scopes, soft deletes, audit fields, outbox rows, cancellation, diagnostics, retries, and provider-neutral test doubles. |
EntityKit does not require decorators, generated clients, function-source parsing, or hidden lazy loading. Query selectors build typed expression trees; values stay separate from generated SQL.
| Package | Role |
|---|---|
@entitykit/core |
Contexts, mapping, queries, tracking, errors, and configuration |
@entitykit/sqlite |
SQLite provider using Node's built-in node:sqlite |
@entitykit/postgres |
Postgres provider using pg |
@entitykit/mysql |
MySQL provider using mysql2 |
@entitykit/cli |
Migrations, database inspection, and scaffolding |
@entitykit/testing |
Provider-neutral recording test doubles |
@entitykit/nestjs |
Native-ESM NestJS 12 lifecycle integration; begins in alpha.2 |
Core also exposes focused /migrations, /tooling, and /adapter entry
points. /experimental contains unstable compiler and builder internals.
The CLI creates configuration and context files, compares the current model with its checked-in snapshot, and produces reviewable TypeScript migrations:
npx entitykit init
# add entities and mappings to src/db/app-db-context.ts
npx entitykit migration add InitialCreate
npx entitykit db migrate --dry-run
npx entitykit db migrateRename hints preserve data when a model name changes. Destructive forward
operations are reported and require explicit --allow-data-loss approval.
Generated migrations are source code: review them before applying them.
The migration guide covers renames, rollback planning, deployment scripts, database-first projects, and provider-specific DDL rules.
- Documentation map — the shortest path for newcomers, framework users, contributors, and release maintainers.
- Usage guide — build a real context, query, save, load relationships, run transactions, and manage migrations.
- API reference — packages, entry points, public operations, errors, and extension surfaces.
- Framework guide — NestJS 12 and the Node-runtime Next.js 16 integration pattern.
- Next.js + Postgres demo — a production-shaped
App Router example pinned to the exact
alpha.2workspace family. - Compatibility — Node support, provider parity, and current alpha boundaries.
- Architecture — package ownership, provider seams, and the query/save pipelines.
EntityKit is deliberately honest about its current boundary. The
0.1.0-alpha.1 release was the original six-package family; the coordinated
0.1.0-alpha.2 source adds the NestJS package and the refined data-source
lifecycle API.
- Every package in a coordinated release moves on one exact prerelease version.
- The public API may change before 1.0;
/experimentalhas no compatibility promise during alpha. - Provider-neutral behavior is shared, but database DDL, isolation, locking, collation, and schema-introspection details still differ.
- Migration generation is conservative. Renames and destructive changes need explicit review.
- SQLite table rebuilds and MySQL's implicitly committed DDL require extra care.
The complete, provider-by-provider contract lives in Compatibility.
npm ci
npm run verifyRead Contributing before changing package boundaries or a public API. Report suspected vulnerabilities through the private process in Security. Maintainers should follow the exact release runbook for the published alpha family.
MIT. See LICENSE.