<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg" -->

---
title: A deep dive into Kysely | daily.dev
description: Kysely is a type-safe SQL query builder for TypeScript that compiles chained method calls into parameterized SQL without hiding the query behind a model layer....
canonical: https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: A deep dive into Kysely | daily.dev
og:description: Kysely is a type-safe SQL query builder for TypeScript that compiles chained method calls into parameterized SQL without hiding the query behind a model layer....
og:url: https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg
og:image: https://api.daily.dev/og/posts/oZzxDyihg.png
og:image:alt: A deep dive into Kysely
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# A deep dive into Kysely

**[Flavio Copes](https://daily.dev/sources/flaviocopes)** · 21 min read · 0 upvotes · 0 comments

## Summary

Kysely is a type-safe SQL query builder for TypeScript that compiles chained method calls into parameterized SQL without hiding the query behind a model layer. This deep dive covers setup with PostgreSQL, defining database interfaces using Generated, ColumnType, Selectable, Insertable, and Updateable helpers, building select/insert/update/delete queries, joins, optional filters via immutable builders, transactions, subqueries, CTEs, raw SQL escape hatches, migrations, logging, and testing strategies. Real-world patterns from a Hono marketplace API and an Astro password-reset flow illustrate practical usage. The guide also covers common mistakes, tradeoffs versus full ORMs like Drizzle, and a recommended layered architecture separating routes, validators, services, repositories, and migrations.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://flaviocopes.com/kysely>

## Questions this post answers

### How do I mark a database column that has different types for select, insert, and update in Kysely?

Use the ColumnType generic with three type arguments: the type returned on select, the type accepted on insert, and the type allowed on update. For example, ColumnType<Date, string | undefined, never> means selecting returns a Date, inserting accepts a string or omits the value, and updating the column is disallowed entirely.

_Developers modeling database schemas in TypeScript can find patterns like this through daily.dev._

### Why should generated database columns like serial IDs be wrapped in Generated<> in a Kysely table interface?

Wrapping a column like id in Generated<number> tells Kysely the value is present when selecting a row but optional when inserting one, since the database creates it automatically. Without Generated, an insert would incorrectly require the caller to supply an id value, which the database is meant to generate itself.

_Teams choosing between query builders and ORMs can weigh tradeoffs like this via daily.dev._

### Why does Kysely require TypeScript strict mode to be enabled?

Kysely relies on TypeScript's type system to infer query results, catch mistyped table or column names, and enforce required insert fields, and this level of structural checking depends on strict mode being turned on in tsconfig.json. Without it, many of the compile-time guarantees Kysely provides would not be enforced.

_Developers setting up a new TypeScript data layer can track setup requirements like this on daily.dev._

## Similar posts on daily.dev

- [Kysely: Type-Safe SQL Without ORM Overhead](https://daily.dev/posts/kysely-type-safe-sql-without-orm-overhead-jbjyzrfqy) · Atomic Spin · 103 upvotes · 5 comments
- [Transactions and RLS in Supabase Edge Functions](https://daily.dev/posts/transactions-and-rls-in-supabase-edge-functions-jzhrc5gpe) · marmelab · 1 upvotes · 0 comments
- [Drizzle ORM: an introduction](https://daily.dev/posts/drizzle-orm-an-introduction-ddzeiduom) · Flavio Copes · 7 upvotes · 2 comments
- [Introducing YeSQL: Practical PostgreSQL, One Concept at a Time](https://daily.dev/posts/introducing-yesql-practical-postgresql-one-concept-at-a-time-0hjavfxxa) · Planet PostgreSQL · 0 upvotes · 0 comments
- [How We Built Tinybird's TypeScript SDK for ClickHouse](https://daily.dev/posts/how-we-built-tinybird-s-typescript-sdk-for-clickhouse-xqc3ufsiz) · Tinybird · 1 upvotes · 0 comments

---

Tags: [#typescript](https://daily.dev/tags/typescript), [#sql](https://daily.dev/tags/sql), [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"A deep dive into Kysely","url":"https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg"},"datePublished":"2026-08-11T06:11:38.724Z","dateModified":"2026-09-14T07:18:06.843Z","description":"Kysely is a type-safe SQL query builder for TypeScript that compiles chained method calls into parameterized SQL without hiding the query behind a model layer....","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/a58d83f02c75c29509d399ceac6051c7?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/a58d83f02c75c29509d399ceac6051c7?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Flavio Copes","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Flavio Copes","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/1f2dbd275797411bbd37bfdaaf0951a9","url":"https://daily.dev/sources/flaviocopes"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"typescript,sql,postgresql","timeRequired":"PT21M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Flavio Copes","item":"https://daily.dev/sources/flaviocopes"},{"@type":"ListItem","position":3,"name":"A deep dive into Kysely"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/a-deep-dive-into-kysely-ozzxdyihg#faq","mainEntity":[{"@type":"Question","name":"How do I mark a database column that has different types for select, insert, and update in Kysely?","acceptedAnswer":{"@type":"Answer","text":"Use the ColumnType generic with three type arguments: the type returned on select, the type accepted on insert, and the type allowed on update. For example, ColumnType<Date, string | undefined, never> means selecting returns a Date, inserting accepts a string or omits the value, and updating the column is disallowed entirely. Developers modeling database schemas in TypeScript can find patterns like this through daily.dev."}},{"@type":"Question","name":"Why should generated database columns like serial IDs be wrapped in Generated<> in a Kysely table interface?","acceptedAnswer":{"@type":"Answer","text":"Wrapping a column like id in Generated<number> tells Kysely the value is present when selecting a row but optional when inserting one, since the database creates it automatically. Without Generated, an insert would incorrectly require the caller to supply an id value, which the database is meant to generate itself. Teams choosing between query builders and ORMs can weigh tradeoffs like this via daily.dev."}},{"@type":"Question","name":"Why does Kysely require TypeScript strict mode to be enabled?","acceptedAnswer":{"@type":"Answer","text":"Kysely relies on TypeScript's type system to infer query results, catch mistyped table or column names, and enforce required insert fields, and this level of structural checking depends on strict mode being turned on in tsconfig.json. Without it, many of the compile-time guarantees Kysely provides would not be enforced. Developers setting up a new TypeScript data layer can track setup requirements like this on daily.dev."}}]}
```

