Kysely

A type-safe typescript SQL query builder

README

Stand With Ukraine
Discord Tests


Kysely (pronounce “Key-Seh-Lee”) is a type-safe and autocompletion-friendly typescript SQL query builder. Inspired by
knex. Mainly developed for node.js but also runs on deno
and in the browser.

undefined

Kysely makes sure you only refer to tables and columns that are visible to the part of the query
you're writing. The result type only has the selected columns with correct types and aliases. As an
added bonus you get autocompletion for all that stuff.

As shown in the gif above, through the pure magic of modern typescript, Kysely is even able to parse
the alias given to pet.name and add the pet_name column to the result row type. Kysely is able to infer
column names, aliases and types from selected subqueries, joined subqueries, with statements and pretty
much anything you can think of.

Of course there are cases where things cannot be typed at compile time, and Kysely offers escape
hatches for these situations. See the sql template tag
and the DynamicModule for more info.

All API documentation is written in the typing files and you can simply cmd-click on the module, class
or method you're using to see it. The same documentation is also hosted here.

If you start using Kysely and can't find something you'd want to use, please open an issue or join our

You can find a more thorough introduction here.

Table of contents



Installation


Kysely currently works on PostgreSQL, MySQL and SQLite. You can install it using:

  1. ```
  2. # PostgreSQL
  3. npm install kysely pg

  4. # MySQL
  5. npm install kysely mysql2

  6. # SQLite
  7. npm install kysely better-sqlite3
  8. ```

More dialects will be added soon. Kysely also has a simple interface

3rd party dialects


- D1

Minimal example


All you need to do is define an interface for each table in the database and pass those
interfaces to the Kysely constructor:

  1. ```ts
  2. import { Pool } from 'pg'
  3. import {
  4.   Kysely,
  5.   PostgresDialect,
  6.   Generated,
  7.   ColumnType,
  8.   Selectable,
  9.   Insertable,
  10.   Updateable,
  11. } from 'kysely'

  12. interface PersonTable {
  13.   // Columns that are generated by the database should be marked
  14.   // using the `Generated` type. This way they are automatically
  15.   // made optional in inserts and updates.
  16.   id: Generated<number>

  17.   first_name: string
  18.   gender: 'male' | 'female' | 'other'

  19.   // If the column is nullable in the database, make its type nullable.
  20.   // Don't use optional properties. Optionality is always determined
  21.   // automatically by Kysely.
  22.   last_name: string | null

  23.   // You can specify a different type for each operation (select, insert and
  24.   // update) using the `ColumnType`
  25.   // wrapper. Here we define a column `modified_at` that is selected as
  26.   // a `Date`, can optionally be provided as a `string` in inserts and
  27.   // can never be updated:
  28.   modified_at: ColumnType<Date, string | undefined, never>
  29. }

  30. interface PetTable {
  31.   id: Generated<number>
  32.   name: string
  33.   owner_id: number
  34.   species: 'dog' | 'cat'
  35. }

  36. interface MovieTable {
  37.   id: Generated<string>
  38.   stars: number
  39. }

  40. // Keys of this interface are table names.
  41. interface Database {
  42.   person: PersonTable
  43.   pet: PetTable
  44.   movie: MovieTable
  45. }

  46. // You'd create one of these when you start your app.
  47. const db = new Kysely<Database>({
  48.   // Use MysqlDialect for MySQL and SqliteDialect for SQLite.
  49.   dialect: new PostgresDialect({
  50.     pool: new Pool({
  51.       host: 'localhost',
  52.       database: 'kysely_test'
  53.     })
  54.   })
  55. })

  56. async function demo() {
  57.   const { id } = await db
  58.     .insertInto('person')
  59.     .values({ first_name: 'Jennifer', gender: 'female' })
  60.     .returning('id')
  61.     .executeTakeFirstOrThrow()

  62.   await db
  63.     .insertInto('pet')
  64.     .values({ name: 'Catto', species: 'cat', owner_id: id })
  65.     .execute()

  66.   const person = await db
  67.     .selectFrom('person')
  68.     .innerJoin('pet', 'pet.owner_id', 'person.id')
  69.     .select(['first_name', 'pet.name as pet_name'])
  70.     .where('person.id', '=', id)
  71.     .executeTakeFirst()

  72.   if (person) {
  73.     person.pet_name
  74.   }
  75. }
  76. ```

  1. ```ts
  2. // You can extract the select, insert and update interfaces like this
  3. // if you want (you don't need to):
  4. type Person = Selectable<PersonTable>
  5. type InsertablePerson = Insertable<PersonTable>
  6. type UpdateablePerson = Updateable<PersonTable>
  7. ```

Generating types


If you want to generate the table types automatically from the database schema please

Query examples


Select queries


You can find examples of select queries in the documentation of the
among other places.

Stream select query results


Currently only supported by postgres and mysql dialects.

  1. ```ts
  2. import { Pool } from 'pg'
  3. // or `import * as Cursor from 'pg-cursor'` depending on your tsconfig
  4. import Cursor from 'pg-cursor'
  5. import {
  6.     Kysely,
  7.     PostgresDialect,
  8. } from 'kysely'

  9. const db = new Kysely<Database>({
  10.     // PostgresDialect requires the Cursor dependency
  11.     dialect: new PostgresDialect({
  12.         pool: new Pool({
  13.             host: 'localhost',
  14.             database: 'kysely_test'
  15.         }),
  16.         cursor: Cursor
  17.     }),

  18.     // MysqlDialect doesn't require any special configuration
  19. })

  20. async function demo() {
  21.     for await (const male of db.selectFrom("person")
  22.         .selectAll()
  23.         .where("person.gender", "=", "male")
  24.         .stream()) {
  25.         console.log(`Hello mr. ${male.first_name}!`)

  26.         if (male.first_name === "John") {
  27.           // After this line the db connection is released and no more
  28.           // rows are streamed from the database to the client
  29.           break;
  30.         }
  31.     }
  32. }
  33. ```

Update queries


See the set method and the
documentation.

Insert queries


See the values method and the
documentation.

Delete queries


documentation.

Recipes


The recipes folder contains a bunch of small tutorials
or "recipes" for common use cases.


Migrations


Migration files should look like this:

  1. ```ts
  2. import { Kysely } from 'kysely'

  3. export async function up(db: Kysely<any>): Promise<void> {
  4.   // Migration code
  5. }

  6. export async function down(db: Kysely<any>): Promise<void> {
  7.   // Migration code
  8. }
  9. ```

The up function is called when you update your database schema to the next version and down
when you go back to previous version. The only argument for the functions is an instance of
`Kysely`. It's important to use `Kysely` and not `Kysely`.

Migrations should never depend on the current code of your app because they need to work even when the app
changes. Migrations need to be "frozen in time".

The migrations can use the Kysely.schema
module to modify the schema. Migrations can also run normal queries to modify data.

Execution order of the migrations is the alpabetical order of their names. An excellent way to name your
migrations is to prefix them with an ISO 8601 date string. A date prefix works well in large teams
where multiple team members may add migrations at the same time in parallel commits without knowing
about the other migrations.

You don't need to store your migrations as separate files if you don't want to. You can easily
implement your own MigrationProvider
and give it to the Migrator class
when you instantiate one.

PostgreSQL migration example


  1. ```ts
  2. import { Kysely, sql } from 'kysely'

  3. export async function up(db: Kysely<any>): Promise<void> {
  4.   await db.schema
  5.     .createTable('person')
  6.     .addColumn('id', 'serial', (col) => col.primaryKey())
  7.     .addColumn('first_name', 'varchar', (col) => col.notNull())
  8.     .addColumn('last_name', 'varchar')
  9.     .addColumn('gender', 'varchar(50)', (col) => col.notNull())
  10.     .addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql`now()`).notNull())
  11.     .execute()

  12.   await db.schema
  13.     .createTable('pet')
  14.     .addColumn('id', 'serial', (col) => col.primaryKey())
  15.     .addColumn('name', 'varchar', (col) => col.notNull().unique())
  16.     .addColumn('owner_id', 'integer', (col) =>
  17.       col.references('person.id').onDelete('cascade').notNull()
  18.     )
  19.     .addColumn('species', 'varchar', (col) => col.notNull())
  20.     .execute()

  21.   await db.schema
  22.     .createIndex('pet_owner_id_index')
  23.     .on('pet')
  24.     .column('owner_id')
  25.     .execute()
  26. }

  27. export async function down(db: Kysely<any>): Promise<void> {
  28.   await db.schema.dropTable('pet').execute()
  29.   await db.schema.dropTable('person').execute()
  30. }
  31. ```

MySQL migration example


  1. ```ts
  2. import { Kysely } from 'kysely'

  3. export async function up(db: Kysely<any>): Promise<void> {
  4.   await db.schema
  5.     .createTable('person')
  6.     .addColumn('id', 'integer', (col) => col.autoIncrement().primaryKey())
  7.     .addColumn('first_name', 'varchar(255)', (col) => col.notNull())
  8.     .addColumn('last_name', 'varchar(255)')
  9.     .addColumn('gender', 'varchar(50)', (col) => col.notNull())
  10.     .execute()

  11.   await db.schema
  12.     .createTable('pet')
  13.     .addColumn('id', 'integer', (col) => col.autoIncrement().primaryKey())
  14.     .addColumn('name', 'varchar(255)', (col) => col.notNull().unique())
  15.     .addColumn('owner_id', 'integer', (col) => col.notNull())
  16.     .addColumn('species', 'varchar(255)', (col) => col.notNull())
  17.     .addForeignKeyConstraint(
  18.       'pet_owner_id_fk', ['owner_id'], 'person', ['id'],
  19.       (cb) => cb.onDelete('cascade')
  20.     )
  21.     .execute()

  22.   await db.schema
  23.     .createIndex('pet_owner_id_index')
  24.     .on('pet')
  25.     .column('owner_id')
  26.     .execute()
  27. }

  28. export async function down(db: Kysely<any>): Promise<void> {
  29.   await db.schema.dropTable('pet').execute()
  30.   await db.schema.dropTable('person').execute()
  31. }
  32. ```

You can then use

  1. ```ts
  2. const migrator = new Migrator(migratorConfig);
  3. await migrator.migrateToLatest(pathToMigrationsFolder)
  4. ```

to run all migrations that have not yet been run. See the
class's documentation for more info.

Kysely doesn't have a CLI for running migrations and probably never will. This is because Kysely's
migrations are also written in typescript. To run the migrations, you need to first build the
typescript code into javascript. A CLI would cause confusion over which migrations are being
run, the typescript ones or the javascript ones. If we added support for both, the CLI would
need to depend on a typescript compiler, which most production environments don't (and shouldn't)
have. You will probably want to add a simple migration script to your projects like this:

  1. ```ts
  2. import * as path from 'path'
  3. import { Pool } from 'pg'
  4. import { promises as fs } from 'fs'
  5. import {
  6.   Kysely,
  7.   Migrator,
  8.   PostgresDialect,
  9.   FileMigrationProvider
  10. } from 'kysely'

  11. async function migrateToLatest() {
  12.   const db = new Kysely<Database>({
  13.     dialect: new PostgresDialect({
  14.       pool: new Pool({
  15.         host: 'localhost',
  16.         database: 'kysely_test',
  17.       })
  18.     }),
  19.   })

  20.   const migrator = new Migrator({
  21.     db,
  22.     provider: new FileMigrationProvider({
  23.       fs,
  24.       path,
  25.       migrationFolder: 'some/path/to/migrations',
  26.     })
  27.   })

  28.   const { error, results } = await migrator.migrateToLatest()

  29.   results?.forEach((it) => {
  30.     if (it.status === 'Success') {
  31.       console.log(`migration "${it.migrationName}" was executed successfully`)
  32.     } else if (it.status === 'Error') {
  33.       console.error(`failed to execute migration "${it.migrationName}"`)
  34.     }
  35.   })

  36.   if (error) {
  37.     console.error('failed to migrate')
  38.     console.error(error)
  39.     process.exit(1)
  40.   }

  41.   await db.destroy()
  42. }

  43. migrateToLatest()
  44. ```

The migration methods use a lock on the database level and parallel calls are executed serially.
This means that you can safely call migrateToLatest and other migration methods from multiple
server instances simultaneously and the migrations are guaranteed to only be executed once. The
locks are also automatically released if the migration process crashes or the connection to the
database fails.

Deno


Kysely doesn't include drivers for deno, but you can still use Kysely as a query builder
or implement your own driver:

  1. ```ts
  2. // We use jsdeliver to get Kysely from npm.
  3. import {
  4.   DummyDriver,
  5.   Generated,
  6.   Kysely,
  7.   PostgresAdapter,
  8.   PostgresIntrospector,
  9.   PostgresQueryCompiler,
  10. } from 'https://cdn.jsdelivr.net/npm/kysely/dist/esm/index.js'

  11. interface Person {
  12.   id: Generated<number>
  13.   first_name: string
  14.   last_name: string | null
  15. }

  16. interface Database {
  17.   person: Person
  18. }

  19. const db = new Kysely<Database>({
  20.   dialect: {
  21.     createAdapter() {
  22.       return new PostgresAdapter()
  23.     },
  24.     createDriver() {
  25.       // You need a driver to be able to execute queries. In this example
  26.       // we use the dummy driver that never does anything.
  27.       return new DummyDriver()
  28.     },
  29.     createIntrospector(db: Kysely<unknown>) {
  30.       return new PostgresIntrospector(db)
  31.     },
  32.     createQueryCompiler() {
  33.       return new PostgresQueryCompiler()
  34.     },
  35.   },
  36. })

  37. const query = db.selectFrom('person').select('id')
  38. const sql = query.compile()

  39. console.log(sql.sql)
  40. ```

Browser


Kysely also runs in the browser:

  1. ```ts
  2. import {
  3.   Kysely,
  4.   Generated,
  5.   DummyDriver,
  6.   SqliteAdapter,
  7.   SqliteIntrospector,
  8.   SqliteQueryCompiler,
  9. } from 'kysely'

  10. interface Person {
  11.   id: Generated<number>
  12.   first_name: string
  13.   last_name: string | null
  14. }

  15. interface Database {
  16.   person: Person
  17. }

  18. const db = new Kysely<Database>({
  19.   dialect: {
  20.     createAdapter() {
  21.       return new SqliteAdapter()
  22.     },
  23.     createDriver() {
  24.       return new DummyDriver()
  25.     },
  26.     createIntrospector(db: Kysely<unknown>) {
  27.       return new SqliteIntrospector(db)
  28.     },
  29.     createQueryCompiler() {
  30.       return new SqliteQueryCompiler()
  31.     },
  32.   },
  33. })

  34. window.addEventListener('load', () => {
  35.   const sql = db.selectFrom('person').select('id').compile()

  36.   const result = document.createElement('span')
  37.   result.id = 'result'
  38.   result.innerHTML = sql.sql

  39.   document.body.appendChild(result)
  40. })
  41. ```

Why not just contribute to knex


Kysely is very similar to knex, but it also attempts to fix things that I personally find not-so-good
in knex. Bringing the type system and the changes to knex would mean very significant breaking changes
that aren't possible at this point of the project. Knex was also originally written for javascript and
the typescript typings were added afterwards. That always leads to compromises in the types. Designing
a library for typescript from the ground up produces much better and simpler types.

How to contribute to Kysely