What is kysely?
Kysely is a type-safe SQL query builder for TypeScript. It allows developers to write SQL queries in a type-safe manner, ensuring that the queries are correct at compile time. Kysely supports various SQL databases and provides a fluent API for building complex queries.
What are kysely's main functionalities?
Type-safe Query Building
Kysely allows you to build SQL queries in a type-safe manner. The above code demonstrates a simple SELECT query that retrieves the 'id' and 'first_name' columns from the 'person' table where the 'id' is equal to 1.
const result = await db.selectFrom('person').select(['id', 'first_name']).where('id', '=', 1).execute();
Insert Queries
Kysely supports type-safe insert queries. The above code demonstrates how to insert a new row into the 'person' table with the specified values.
const result = await db.insertInto('person').values({ first_name: 'John', last_name: 'Doe' }).execute();
Update Queries
Kysely allows you to perform type-safe update queries. The above code demonstrates how to update the 'first_name' column of the 'person' table where the 'id' is equal to 1.
const result = await db.updateTable('person').set({ first_name: 'Jane' }).where('id', '=', 1).execute();
Delete Queries
Kysely supports type-safe delete queries. The above code demonstrates how to delete a row from the 'person' table where the 'id' is equal to 1.
const result = await db.deleteFrom('person').where('id', '=', 1).execute();
Joins
Kysely allows you to perform type-safe join operations. The above code demonstrates an inner join between the 'person' and 'address' tables, selecting the 'id' from the 'person' table and the 'city' from the 'address' table.
const result = await db.selectFrom('person').innerJoin('address', 'person.id', 'address.person_id').select(['person.id', 'address.city']).execute();
Other packages similar to kysely
knex
Knex.js is a SQL query builder for Node.js that supports various SQL databases. It provides a fluent API for building queries but lacks the type-safety features of Kysely. Knex is widely used and has a large community.
sequelize
Sequelize is a promise-based Node.js ORM for various SQL databases. It provides a higher-level abstraction over SQL queries and includes features like model definitions, associations, and migrations. Unlike Kysely, Sequelize is more focused on being an ORM rather than just a query builder.
typeorm
TypeORM is an ORM for TypeScript and JavaScript that supports various SQL databases. It provides a high-level API for defining entities, relationships, and performing database operations. TypeORM offers type-safety but is more feature-rich compared to Kysely, as it includes migrations, caching, and more.
A type-safe and autocompletion-friendly typescript SQL query builder for node.js. Heavily inspired by
knex but not a clone.
Kysely's typings only allow you to use tables that are available in the database and refer to
columns of the tables that are joined to the query. The result type only contains the selected
columns with correct types and aliases. This allows tools like vscode autocompletion to make your life
so much easier.
As you can see 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 and types from selected subqueries, joined subqueries, with
statements and pretty much
anything you can think of. Typescript is always there for you to offer completions and tell what kind
of query you can build.
Of course there are cases where things cannot be typed at compile time, and Kysely offers escape
hatches for these situations. With typescript you can always cast something to any
if the types
fail you. with Kysely you can also explicitly tell it to ignore the typings, but the default is always
type-safety! See the DynamicModule
for more info.
Kysely is still young and some useful methods and modules are not yet implemented. If you start using
Kysely, please open an issue as soon as you can't find something you want to use.
Installation
Kysely currently only works on postgres. You can install it using
npm install kysely pg
MySQL and sqlite support will be added soon. Kysely also has a simple interface
for third-party dialects.
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:
import { Kysely } from 'kysely'
interface Person {
id: number
first_name: string
last_name: string
gender: 'male' | 'female' | 'other'
}
interface Pet {
id: number
name: string
owner_id: number
species: 'dog' | 'cat'
}
interface Movie {
id: string
stars: number
}
interface Database {
person: Person
pet: Pet
movie: Movie
}
const db = new Kysely<Database>({
dialect: 'postgres',
host: 'localhost',
database: 'kysely_test',
})
async function demo() {
const person = await db
.selectFrom('person')
.innerJoin('pet', 'pet.owner_id', 'person.id')
.select(['first_name', 'pet.name as pet_name'])
.where('person.id', '=', 1)
.executeTakeFirst()
if (person) {
person.pet_name
}
}
Migrations
Migration files should look like this:
import { Kysely } from 'kysely'
export async function up(db: Kysely<any>): Promise<void> {
}
export async function down(db: Kysely<any>): Promise<void> {
}
The up
function is called when you update your database schema to next version and down
when you go back to previous version. The only argument to the functions is an instance of
Kysely<any>
. It is important to use Kysely<any>
and not Kysely<YourDatabase>
. Migrations
should never depend on the current code because they need to work even if the code changes
completely. 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 the data.
import { Kysely } from 'kysely'
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('person')
.integer('id', (col) => col.increments().primary())
.string('first_name')
.string('last_name')
.string('gender')
.execute()
await db.schema
.createTable('pet')
.integer('id', (col) => col.increments().primary())
.string('name', (col) => col.unique())
.integer('owner_id', (col) =>
col.references('person.id').onDelete('cascade')
)
.string('species')
.execute()
await db.schema
.createIndex('pet_owner_id_index')
.on('pet')
.column('owner_id')
.execute()
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('pet').execute()
await db.schema.dropTable('person').execute()
}
You can then use
await db.migration.migrateToLatest(pathToMigrationsFolder)
to run all migrations that have not yet been run. The migrations are executed in alphabetical
order by their file name.
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. The CLI would cause confusion over which migrations are being
run, the typescript ones or the javascript ones. If we added support for both, it would mean the
CLI would 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:
import path from 'path'
import { db } from './database'
db.migration.migrateToLatest(path.join(__dirname, 'migrations'))
The migration methods use a lock in 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.
NOTE: Only db.migration.migrateToLatest
method is implemented at the moment. There is no way
to run the down migrations, or to go forward to a specific migration. These methods will be
added soon.
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.