Kysely

Getting Started

Introduction

Kysely is a type-safe and autocompletion-friendly TypeScript SQL query builder.

Prerequisites

Before getting started with Kysely, make sure you have:

  1. TypeScript version 4.6 or later installed

  2. TypeScript's strict mode enabled in your tsconfig.json

Installation

npm install kysely

Types

To enable Kysely's type-safety and autocompletion features, you need to define your database structure using TypeScript interfaces. The main Database interface maps table names to their corresponding schema interfaces.

Here's how to define your first database interface:

src/types.ts
import {
  ColumnType,
  Generated,
  Insertable,
  JSONColumnType,
  Selectable,
  Updateable,
} from 'kysely'
 
export interface Database {
  person: PersonTable
  pet: PetTable
}
 
// This interface describes the `person` table to Kysely. Table
// interfaces should only be used in the `Database` type above
// and never as a result type of a query!. See the `Person`,
// `NewPerson` and `PersonUpdate` types below.
export interface PersonTable {
  // Columns that are generated by the database should be marked
  // using the `Generated` type. This way they are automatically
  // made optional in inserts and updates.
  id: Generated<number>
 
  first_name: string
  gender: 'man' | 'woman' | 'other'
 
  // If the column is nullable in the database, make its type nullable.
  // Don't use optional properties. Optionality is always determined
  // automatically by Kysely.
  last_name: string | null
 
  // You can specify a different type for each operation (select, insert and
  // update) using the `ColumnType<SelectType, InsertType, UpdateType>`
  // wrapper. Here we define a column `created_at` that is selected as
  // a `Date`, can optionally be provided as a `string` in inserts and
  // can never be updated:
  created_at: ColumnType<Date, string | undefined, never>
 
  // You can specify JSON columns using the `JSONColumnType` wrapper.
  // It is a shorthand for `ColumnType<T, string, string>`, where T
  // is the type of the JSON object/array retrieved from the database,
  // and the insert and update types are always `string` since you're
  // always stringifying insert/update values.
  metadata: JSONColumnType<{
    login_at: string
    ip: string | null
    agent: string | null
    plan: 'free' | 'premium'
  }>
}
 
// You should not use the table schema interfaces directly. Instead, you should
// use the `Selectable`, `Insertable` and `Updateable` wrappers. These wrappers
// make sure that the correct types are used in each operation.
//
// Most of the time you should trust the type inference and not use explicit
// types at all. These types can be useful when typing function arguments.
export type Person = Selectable<PersonTable>
export type NewPerson = Insertable<PersonTable>
export type PersonUpdate = Updateable<PersonTable>
 
export interface PetTable {
  id: Generated<number>
  name: string
  owner_id: number
  species: 'dog' | 'cat'
}
 
export type Pet = Selectable<PetTable>
export type NewPet = Insertable<PetTable>
export type PetUpdate = Updateable<PetTable>

Codegen

For production applications, it's highly recommended to automatically generate your Database interface by introspecting your database schema or Prisma schema. This ensures your TypeScript types stay in sync with your actual database structure.

You can learn more about the available code generation options in the Generate Types guide.

Dialects

Kysely needs a Dialect to work with your database. A Dialect tells Kysely:

  1. How to compile SQL queries for your specific database
  2. How to connect and communicate with your database

Kysely comes with built-in support for 4 popular databases:

  • PostgreSQL
  • MySQL
  • Microsoft SQL Server (MSSQL)
  • SQLite

The community has also created dialects for other databases. Check out Dialects to learn more.

Dialect Driver Installation

Kysely's built-in PostgreSQL dialect uses the "pg" driver library under the hood. Please refer to its official documentation for configuration options.

npm install pg

Let's create a Kysely instance using the built-in PostgresDialect dialect:

src/database.ts
import { Database } from './types.ts' // this is the Database interface we defined earlier
import { Pool } from 'pg'
import { Kysely, PostgresDialect } from 'kysely'
 
const dialect = new PostgresDialect({
  pool: new Pool({
    database: 'test',
    host: 'localhost',
    user: 'admin',
    port: 5434,
    max: 10,
  }),
})
 
// Database interface is passed to Kysely's constructor, and from now on, Kysely
// knows your database structure.
// Dialect is passed to Kysely's constructor, and from now on, Kysely knows how
// to communicate with your database.
export const db = new Kysely<Database>({
  dialect,
})

Building Your First Query Repository

Now that we have our database connection set up, let's explore how to build type-safe queries by implementing a repository pattern for managing person records. This example will demonstrate common CRUD operations using Kysely's expressive API:

src/queries.ts
import { db } from './database'
import { PersonUpdate, Person, NewPerson } from './types'
 
export async function findPersonById(id: number) {
  return await db
    .selectFrom('person')
    .where('id', '=', id)
    .selectAll()
    .executeTakeFirst()
}
 
export async function findPeople(criteria: Partial<Person>) {
  let query = db.selectFrom('person')
 
  if (criteria.id) {
    query = query.where('id', '=', criteria.id) // Kysely is immutable, you must re-assign!
  }
 
  if (criteria.first_name) {
    query = query.where('first_name', '=', criteria.first_name)
  }
 
  if (criteria.last_name !== undefined) {
    query = query.where(
      'last_name',
      criteria.last_name === null ? 'is' : '=',
      criteria.last_name,
    )
  }
 
  if (criteria.gender) {
    query = query.where('gender', '=', criteria.gender)
  }
 
  if (criteria.created_at) {
    query = query.where('created_at', '=', criteria.created_at)
  }
 
  return await query.selectAll().execute()
}
 
export async function updatePerson(id: number, updateWith: PersonUpdate) {
  await db.updateTable('person').set(updateWith).where('id', '=', id).execute()
}
 
export async function createPerson(person: NewPerson) {
  return await db
    .insertInto('person')
    .values(person)
    .returningAll()
    .executeTakeFirstOrThrow()
}
 
export async function deletePerson(id: number) {
  return await db
    .deleteFrom('person')
    .where('id', '=', id)
    .returningAll()
    .executeTakeFirst()
}

On this page