What it is
Kysely is a type-safe SQL query builder for TypeScript. It is not an ORM — you write SQL structure, and the types follow your database schema exactly.
Declare an interface describing your tables, then build queries. Every clause narrows the result type, so selecting a column you did not join is a compile error.
Installation
npm install kyselyGetting started
The smallest useful thing you can do with it, and what each part means.
interface Database {
books: { id: Generated<number>; title: string; year: number; author_id: number };
authors: { id: Generated<number>; name: string };
}
const db = new Kysely<Database>({ dialect: new PostgresDialect({ pool }) });
const rows = await db
.selectFrom('books')
.innerJoin('authors', 'authors.id', 'books.author_id')
.select(['books.title', 'authors.name as author'])
.where('books.year', '>=', 1990)
.orderBy('books.year', 'desc')
.limit(10)
.execute();
// rows[0].author is string; rows[0].year is a compile error (not selected)const inserted = await db
.insertInto('books')
.values({ title: 'Dune', year: 1965, author_id: 1 })
.returningAll()
.executeTakeFirstOrThrow();
await db
.updateTable('books')
.set({ year: 1966 })
.where('id', '=', inserted.id)
.execute();Advanced usage
Where the library earns its place over a simpler alternative.
await db.transaction().execute(async (trx) => {
await trx.updateTable('stock')
.set((eb) => ({ quantity: eb('quantity', '-', qty) }))
.where('sku', '=', sku)
.execute();
await trx.insertInto('orders').values({ sku, qty }).execute();
});
// Compose filters as reusable functions.
const publishedOnly = (qb: SelectQueryBuilder<Database, 'books', {}>) =>
qb.where('books.published', '=', true);
const results = await publishedOnly(db.selectFrom('books')).selectAll().execute();Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Column does not exist on the result type
- It was not included in select(). Kysely types the result from exactly what you selected.
- Types drift from the real database
- The interface is hand-maintained unless generated. Run kysely-codegen in CI and fail on a diff.
Best practices
- Generate the Database interface from your real schema with kysely-codegen to prevent drift.
- Use executeTakeFirstOrThrow when exactly one row is expected — it removes the undefined from the type.
- Choose Kysely when the team knows SQL and wants no abstraction between intent and query.
- Keep the interface in one file so schema changes surface as compile errors everywhere.
Background
Why it exists, and what it was reacting to.
Kysely exists for developers who want SQL, not an object abstraction, but refuse to give up type safety. Its inference is unusually thorough: joins, aliases and selections all narrow the result type correctly.
