Pourquoi vos requêtes SQL brutes sont un problème silencieux

Dans la majorité des projets Node.js et TypeScript, les requêtes SQL finissent par être écrites sous forme de chaînes de caractères brutes. C’est rapide à mettre en place, flexible, et ça fonctionne… jusqu’au jour où un refactoring de base de données casse silencieusement une dizaine de requêtes sans que le compilateur ne bronche.

Le problème des requêtes SQL brutes est bien connu :

  • Aucune vérification de type : un nom de colonne mal orthographié ne sera détecté qu’à l’exécution.
  • Pas d’autocomplétion : votre IDE ne peut pas vous aider si la requête est une simple string.
  • Risques d’injection SQL : même avec des requêtes paramétrées, la tentation du template literal est forte.
  • Maintenance coûteuse : à mesure que le schéma évolue, synchroniser manuellement toutes les requêtes devient un cauchemar.

Selon une étude menée par Snyk en 2023, les injections SQL restent dans le top 5 des vulnérabilités les plus exploitées dans les applications web. Et même sans parler de sécurité, les bugs liés à des requêtes SQL incorrectes représentent une part significative du temps de débogage en backend.

C’est exactement dans ce contexte que Kysely entre en scène.

Qu’est-ce que Kysely exactement ?

Kysely (prononcé « ki-se-li », mot finnois signifiant « requête ») est un query builder SQL pour TypeScript. Son objectif est simple mais ambitieux : vous permettre d’écrire des requêtes SQL en TypeScript avec un typage de bout en bout, vérifié à la compilation.

Concrètement, Kysely :

  • Génère des requêtes SQL à partir d’une API fluide en TypeScript.
  • Vérifie les noms de tables, de colonnes, les types de paramètres et les types de retour au moment de la compilation.
  • Ne produit aucun overhead runtime : il ne fait que construire des chaînes SQL et des paramètres, rien de plus.
  • Reste proche du SQL natif : si vous connaissez SQL, vous connaissez déjà Kysely.

Kysely n’est pas un ORM. Il ne gère pas les relations, ne génère pas de modèles, et ne s’interpose pas entre vous et votre base de données. C’est un outil chirurgical qui fait une chose et la fait remarquablement bien.

Mise en place : de zéro à la première requête type-safe

Installation

L’installation est minimaliste. Selon votre base de données, vous ajoutez Kysely et le driver correspondant :

# Pour PostgreSQL
npm install kysely pg

# Pour MySQL
npm install kysely mysql2

# Pour SQLite
npm install kysely better-sqlite3

Kysely ne nécessite aucune dépendance binaire, contrairement à Prisma qui embarque un engine Rust. Cela signifie des builds plus rapides, des images Docker plus légères et moins de surprises en CI/CD.

Définir le schéma de la base

La force de Kysely réside dans la définition d’une interface TypeScript qui reflète votre schéma de base de données :

import { Generated, Insertable, Selectable, Updateable } from 'kysely'

interface Database {
  users: UsersTable
  posts: PostsTable
}

interface UsersTable {
  id: Generated<number>
  first_name: string
  last_name: string
  email: string
  created_at: Generated<Date>
}

interface PostsTable {
  id: Generated<number>
  title: string
  content: string
  author_id: number
  published: boolean
  created_at: Generated<Date>
}

// Types utilitaires pour les opérations CRUD
type User = Selectable<UsersTable>
type NewUser = Insertable<UsersTable>
type UserUpdate = Updateable<UsersTable>

Cette interface Database est le contrat unique entre votre code et votre base de données. Dès qu’elle est définie, TypeScript sait exactement quelles tables existent, quelles colonnes elles contiennent, et quels sont leurs types.

Initialiser la connexion

import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'

const db = new Kysely<Database>({
  dialect: new PostgresDialect({
    pool: new Pool({
      host: 'localhost',
      database: 'mon_app',
      user: 'postgres',
      password: 'secret',
    }),
  }),
})

À partir de cet instant, chaque requête que vous écrirez via db sera entièrement typée.

Écrire des requêtes type-safe

Voici la magie en action :

// SELECT — autocomplétion sur les colonnes, type de retour inféré
const users = await db
  .selectFrom('users')
  .select(['id', 'first_name', 'email'])
  .where('email', 'like', '%@example.com')
  .orderBy('created_at', 'desc')
  .limit(10)
  .execute()
// Type inféré : { id: number; first_name: string; email: string }[]

// INSERT — TypeScript vérifie que tous les champs requis sont présents
await db
  .insertInto('users')
  .values({
    first_name: 'Marie',
    last_name: 'Dupont',
    email: 'marie@example.com',
  })
  .execute()

// JOIN — les colonnes des deux tables sont disponibles
const postsWithAuthors = await db
  .selectFrom('posts')
  .innerJoin('users', 'users.id', 'posts.author_id')
  .select(['posts.title', 'users.first_name', 'users.email'])
  .where('posts.published', '=', true)
  .execute()

Si vous faites une faute de frappe sur un nom de colonne, si vous essayez de comparer une string avec un number, ou si vous oubliez un champ obligatoire lors d’un INSERT, TypeScript vous arrête immédiatement avec un message d’erreur clair, avant même que votre code ne s’exécute.

Kysely face à la concurrence : comparaison détaillée

Pour comprendre la place de Kysely dans l’écosystème, comparons-le aux alternatives les plus populaires :

CritèreSQL brut (pg/mysql2)Knex.jsPrismaDrizzle ORMKysely
Type-safety complète❌❌✅ (partiel)✅✅
Autocomplétion colonnes❌❌✅✅✅
Proximité avec SQL✅✅✅✅✅❌✅✅✅✅✅
Overhead runtimeAucunFaibleÉlevéFaibleAucun
Dépendances binairesNonNonOui (Rust engine)NonNon
Taille du bundle~0 KB~80 KB~8 MB+~50 KB~30 KB
Migrations intégréesNonOuiOuiOuiOui (basique)
Courbe d’apprentissageFaibleFaibleMoyenneMoyenneFaible
Support des sous-requêtes complexes✅✅❌✅✅

Kysely vs Prisma

Prisma est sans doute l’ORM TypeScript le plus populaire. Mais il impose un langage de requête propriétaire qui s’éloigne du SQL. Dès que vous avez besoin d’une sous-requête, d’un CTE (Common Table Expression), ou d’une fonction d’agrégation complexe, vous devez recourir à $queryRaw, ce qui annule l’avantage du typage.

Kysely, en restant collé au SQL, permet d’exprimer 100% des requêtes SQL sans jamais perdre le type-safety. Les CTE, les UNION, les sous-requêtes imbriquées, les fonctions fenêtres… tout est typé.

Kysely vs Knex.js

Knex.js a longtemps été le query builder de référence dans l’écosystème Node.js. Mais il a été conçu à l’ère du JavaScript et n’offre aucun typage significatif. Les types de retour sont toujours any, ce qui revient à coder les yeux fermés. Kysely peut être vu comme le successeur spirituel de Knex, pensé nativement pour TypeScript.

Kysely vs Drizzle ORM

Drizzle est le concurrent le plus direct de Kysely. Les deux offrent un typage complet et une proximité avec SQL. La différence principale : Drizzle se positionne aussi comme un ORM léger avec un système de relations, tandis que Kysely reste un pur query builder. Le choix dépend de vos besoins — si vous voulez un outil minimaliste qui fait une seule chose parfaitement, Kysely est le choix naturel.

Fonctionnalités avancées qui font la différence

Transactions typées

await db.transaction().execute(async (trx) => {
  const user = await trx
    .insertInto('users')
    .values({ first_name: 'Jean', last_name: 'Martin', email: 'jean@test.com' })
    .returningAll()
    .executeTakeFirstOrThrow()

  await trx
    .insertInto('posts')
    .values({
      title: 'Mon premier article',
      content: 'Contenu de l\'article...',
      author_id: user.id,
      published: false,
    })
    .execute()
})

L’objet trx expose exactement la même API que db, avec le même typage. Impossible de mélanger une transaction avec la connexion principale par erreur.

Requêtes dynamiques et conditionnelles

Un besoin récurrent en développement web : construire des requêtes dont les clauses WHERE dépendent des paramètres reçus. Kysely gère cela élégamment avec $if :

function findUsers(criteria: {
  name?: string
  email?: string
  onlyRecent?: boolean
}) {
  let query = db.selectFrom('users').selectAll()

  if (criteria.name) {
    query = query.where('first_name', 'like', `%${criteria.name}%`)
  }

  if (criteria.email) {
    query = query.where('email', '=', criteria.email)
  }

  if (criteria.onlyRecent) {
    query = query.where('created_at', '>', new Date(Date.now() - 86400000 * 30))
  }

  return query.execute()
}

Chaque branche conditionnelle conserve le typage complet. Le type de retour final reste correct quelle que soit la combinaison de filtres appliquée.

Génération automatique des types depuis la base existante

Pour les projets existants, Kysely propose kysely-codegen, un outil qui introspecte votre base de données et génère automatiquement l’interface Database :

npx kysely-codegen --dialect postgres --url postgres://user:pass@localhost/mydb

En quelques secondes, vous obtenez un fichier TypeScript parfaitement synchronisé avec votre schéma réel. Chez Lueur Externe, nous utilisons cette approche pour intégrer Kysely dans des projets existants sans réécrire le schéma manuellement, ce qui représente un gain de temps considérable sur les projets de migration.

Plugins et extensibilité

Kysely dispose d’un système de plugins qui permet d’ajouter des comportements transversaux :

  • CamelCasePlugin : transforme automatiquement les noms de colonnes snake_case en camelCase dans les résultats.
  • DeduplicateJoinsPlugin : élimine les jointures redondantes dans les requêtes construites dynamiquement.
  • ParseJSONResultsPlugin : parse automatiquement les colonnes JSON/JSONB.

Vous pouvez également créer vos propres plugins pour logger les requêtes, mesurer les performances ou appliquer des politiques de soft-delete.

Cas d’usage concrets : quand adopter Kysely ?

Kysely s’impose comme le choix idéal dans plusieurs scénarios :

  • API REST ou GraphQL en TypeScript où la fiabilité des requêtes est critique.
  • Projets avec un schéma complexe impliquant de nombreuses jointures, CTE ou sous-requêtes.
  • Applications serverless (AWS Lambda, Vercel Edge Functions) où la taille du bundle et le cold start comptent. Avec ses ~30 KB sans dépendances binaires, Kysely démarre en un éclair comparé aux ~8 MB de Prisma.
  • Migration progressive depuis du SQL brut : vous pouvez introduire Kysely table par table sans refactoring massif.
  • Projets multi-dialecte : le même code Kysely peut cibler PostgreSQL en production et SQLite en test.

En tant qu’agence experte en développement web depuis 2003, Lueur Externe accompagne ses clients dans le choix et l’intégration de ces outils modernes. Sur des projets e-commerce Prestashop ou des applications métier sur-mesure hébergées sur AWS, le choix du bon outil d’accès aux données a un impact direct sur la maintenabilité et les performances à long terme.

Les limites à connaître

Par souci d’honnêteté, il est important de mentionner les points où Kysely peut ne pas être le meilleur choix :

  • Pas de gestion des relations : si vous avez besoin de charger automatiquement des entités liées (eager loading), un ORM comme Prisma ou TypeORM sera plus adapté.
  • Migrations basiques : le système de migrations intégré fonctionne, mais il est moins riche que celui de Prisma ou Knex. Sur des projets complexes, vous pouvez le coupler avec un outil dédié comme dbmate ou flyway.
  • Écosystème plus jeune : même si la communauté grandit rapidement (plus de 11 000 étoiles sur GitHub fin 2024), l’écosystème de plugins et d’intégrations est moins vaste que celui de Prisma.
  • Courbe d’apprentissage TypeScript : les messages d’erreur de TypeScript pour les types complexes de Kysely peuvent être intimidants pour les développeurs moins expérimentés avec les types avancés.

Bonnes pratiques pour intégrer Kysely dans votre projet

Voici les recommandations que nous appliquons chez Lueur Externe lors de l’intégration de Kysely dans un projet client :

  1. Utilisez kysely-codegen en CI : régénérez les types à chaque migration pour garantir la synchronisation schéma/code.
  2. Créez un module repository : encapsulez vos requêtes Kysely dans des fonctions dédiées plutôt que de les disperser dans les contrôleurs.
  3. Activez le plugin CamelCase : si votre base utilise snake_case mais votre code TypeScript préfère camelCase.
  4. Loggez les requêtes en développement : utilisez un plugin de logging pour voir le SQL généré et optimiser vos requêtes.
  5. Typez vos fonctions de repository : utilisez les types utilitaires Selectable, Insertable et Updateable exportés par Kysely pour typer les entrées et sorties de vos fonctions.

Performances : des chiffres qui parlent

Puisque Kysely ne fait que construire des chaînes SQL sans couche d’abstraction intermédiaire, ses performances sont quasiment identiques à celles du driver natif :

  • Temps de construction d’une requête simple : ~0.01 ms (négligeable par rapport au temps réseau)
  • Cold start en serverless : ~50 ms pour Kysely vs ~500-800 ms pour Prisma (selon les benchmarks de la communauté)
  • Empreinte mémoire : minimale, aucun cache d’entités, aucun tracking de changements

Pour des applications e-commerce ou des API à forte charge, cette légèreté fait une vraie différence sur les coûts d’infrastructure, notamment en environnement AWS Lambda — un domaine que nous maîtrisons parfaitement chez Lueur Externe en tant que partenaire certifié AWS Solutions Architect.

Conclusion : faut-il passer à Kysely ?

Si vous développez en TypeScript et que vous êtes frustré par les limitations des requêtes SQL brutes, par le manque de typage de Knex, ou par la lourdeur de Prisma sur des requêtes complexes, Kysely mérite sérieusement votre attention.

Son approche est limpide : rester aussi proche que possible du SQL tout en vous offrant la puissance du système de types de TypeScript. Pas de magie, pas d’abstraction opaque, pas de dépendances lourdes. Juste du SQL type-safe, vérifié à la compilation.

C’est un outil qui s’inscrit dans une tendance de fond : des bibliothèques plus légères, plus transparentes, qui font confiance au développeur plutôt que de l’enfermer dans un framework.

Vous envisagez de moderniser votre stack backend, de migrer vers TypeScript ou d’optimiser vos requêtes SQL ? L’équipe de Lueur Externe vous accompagne dans le choix des bons outils et leur intégration, avec plus de 20 ans d’expertise en développement web. Contactez-nous pour en discuter.