Contexto
A pergunta que separa Drizzle de Kysely é anterior a qualquer benchmark: você quer um ORM ou só um jeito tipado de escrever SQL? Drizzle é um ORM leve. Você declara o schema em TypeScript (pgTable, mysqlTable etc.), e a partir dele ganha inferência de tipos, um query builder com relations e o drizzle-kit para gerar e aplicar migrações. Kysely não é um ORM: é um query builder tipado que espelha SQL quase 1:1. Não há schema declarado como fonte da verdade dentro da biblioteca; você fornece uma interface Database com os tipos das tabelas (à mão ou gerada por kysely-codegen a partir do banco) e escreve selectFrom(...).select(...).where(...) como escreveria o próprio SQL.
Essa diferença define quem é dono do schema. Em Drizzle, o TypeScript é a fonte da verdade: o drizzle-kit faz o diff do schema e emite o SQL das migrações. Em Kysely, o banco é a fonte da verdade: o kysely-codegen introspecta as tabelas e gera os tipos, e as migrações você escreve à mão com um migrator mínimo que a própria lib oferece, sem um gerador que faça diff de schema. Os dois são TypeScript puro, sem binário nativo, então ambos rodam bem em edge e serverless (é aí que a comparação com um ORM de engine em Rust deixa de valer). O que sobra de diferença tangível é o custo do pacote (cerca de 8,4 kB gzip no Drizzle contra 38,7 kB no Kysely) e o alcance: Drizzle puxa mais downloads (11,4M/sem contra 8,8M/sem) e carrega drizzle-kit e Drizzle Studio no mesmo guarda-chuva, enquanto Kysely se mantém estritamente focado em construir queries.
Quando escolher Drizzle
Drizzle faz sentido quando você quer as garantias de um ORM sem abrir mão de queries que leem como SQL: schema declarado em um só lugar, tipos inferidos dele, migrações versionadas pelo drizzle-kit e helpers de relations quando o modelo tem joins recorrentes.
import { pgTable, serial, text, integer } from "drizzle-orm/pg-core";
import { eq } from "drizzle-orm";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
teamId: integer("team_id").notNull(),
});
const rows = await db.select().from(users).where(eq(users.teamId, 1));
O users acima é ao mesmo tempo o schema e a origem dos tipos: o db.select() já sabe que id é number e name é string, sem passo de codegen manual. Quando o schema muda, o drizzle-kit gera a migração correspondente, então o histórico de mudanças vive junto do código. Some a isso a API de relations para joins recorrentes, o Drizzle Studio para inspecionar dados e um runtime que é só TypeScript (sem binário), e você tem uma camada de dados completa que ainda assim roda em edge sem cold start de engine nativa. O custo é aceitar uma camada de ORM: convenções próprias de definição de schema e um query builder que, apesar de SQL-like, ainda é uma abstração acima do SQL literal.
Quando escolher Kysely
Kysely faz sentido quando você não quer um ORM, quer SQL. Ele dá tipagem total sobre queries que você mesmo escreve, na forma mais próxima possível do SQL, sem esconder joins, subqueries ou funções de agregação atrás de uma API de objeto.
import { Kysely } from "kysely";
interface Database {
users: { id: number; name: string; team_id: number };
}
const db = new Kysely<Database>({ dialect });
const rows = await db
.selectFrom("users")
.select(["id", "name"])
.where("team_id", "=", 1)
.execute();
A interface Database é o contrato: você a escreve à mão ou deixa o kysely-codegen gerá-la a partir do banco existente. A partir dela, cada método espelha uma cláusula de SQL, e a inferência acompanha: o select(["id", "name"]) acima estreita o tipo de rows para exatamente essas duas colunas, sem any e sem trazer campos que você não pediu. Não há relations mágicas nem engine intermediária; o que você escreve é o que roda no banco. A contrapartida é que Kysely não faz o trabalho de um ORM: as migrações você escreve à mão com o migrator mínimo da lib (não há diff de schema como no drizzle-kit), manter a interface Database em sincronia com o banco é responsabilidade sua (ou do codegen no seu pipeline), e o pacote é maior, cerca de 38,7 kB gzip contra 8,4 kB do Drizzle.
Veredito
O eixo é simples: Drizzle é um ORM, Kysely não é. Se você quer uma camada de dados que seja dona do schema, gere migrações a partir dele (drizzle-kit) e ofereça relations, Drizzle entrega tudo isso num runtime que continua sendo só TypeScript, o que aqui pesa a favor dele, inclusive no tamanho menor (8,4 kB contra 38,7 kB). Se você quer escrever SQL com tipagem total e nada mais, sem uma abstração de ORM entre o seu código e o banco, Kysely é a ferramenta mais honesta: cada query é SQL que você reconhece, com a rede de segurança dos tipos.
Como os dois são TypeScript puro, o desempate de runtime que costuma decidir um ORM contra outro não aparece aqui: a pergunta volta a ser quanta abstração sobre o SQL você aceita e quem é dono do schema. Um time que já tem um banco e pensa em SQL tende a se dar bem com Kysely mais kysely-codegen, deixando o banco como fonte da verdade. Um time começando do zero, que quer schema, migrações e relations saindo de um lugar só, encontra em Drizzle uma camada mais completa. Trocar um pelo outro num projeto que já funciona raramente compensa: a escolha é de estilo de acesso a dados, não de correção, e reescrever queries que já passam nos testes só para mudar de sintaxe é esforço sem retorno claro.