Imaginez un bac de recyclage sur lequel vous avez clairement apposé une étiquette indiquant "Bouteilles en plastique uniquement", mais lorsque quelqu’un y jette un morceau de papier, il l’accepte silencieusement sans un seul mot de protestation ?
C’est l’expérience effrayante que vivent les développeurs lorsqu’ils découvrent le système de types de SQLite pour la première fois.
Si vous êtes habitué au style rigide d’agent des douanes de PostgreSQL (où les types incorrects sont directement rejetés), la désinvolture de SQLite pourrait vous faire douter de votre vie.
Encore plus effrayant, lorsque vous souhaitez modifier la structure d’une table, il vous dira :
"La table ne peut pas être modifiée directement. Veuillez construire une nouvelle maison, y déménager les meubles, puis faire sauter l’ancienne maison."
Sous le capot, SQLite ne possède que 5 classes de stockage
Peu importe les noms de types élégants que vous déclarez dans CREATE TABLE (VARCHAR(255), BIGINT, DECIMAL), SQLite sous le capot ne reconnaît que ces 5 classes de stockage :
| Classe de stockage | Description |
|---|---|
| NULL | Valeur nulle |
| INTEGER | Entier (occupe automatiquement 1 à 8 octets selon l’importance de la valeur) |
| REAL | Nombre à virgule flottante (fixé à 8 octets) |
| TEXT | Chaîne de caractères (encodage UTF-8 par défaut) |
| BLOB | Grand objet binaire (stocké exactement comme saisi) |
Les types que vous définissez sur les colonnes sont simplement des "suggestions" pour SQLite, pas des "règles obligatoires".
C’est ce qu’on appelle la "Type Affinity" (Affinité de types).
SQLitetentera de convertir vos données vers le type suggéré, mais s’il ne le peut pas, il insérera simplement les données d’origine sans générer d’erreur.
Vous pouvez déclarer une colonne age INTEGER dans SQLite puis y insérer la chaîne 'toujours dix-huit ans'; il l’acceptera volontiers.
Trois pièges de types les plus faciles à commettre
Piège 1 : Pas de Boolean nativo
SQLite n’a pas de type booléen. True et False ne peuvent être représentés que par les entiers 1 et 0.
Lorsque vous récupérez des données de SQLite à l’aide de Node.js, vous obtiendrez le nombre 1 ou 0, pas true ou false.
Si vous effectuez directement une vérification telle que if (user.is_admin === true), elle ne sera jamais vraie.
Piège 2 : Pas de type Date/Time
SQLite n’a pas de type date/heure. Vous ne pouvez stocker le temps que sous forme de :
| Méthode de stockage | Exemple | Avantages & Inconvénients |
|---|---|---|
| TEXT (chaîne ISO-8601) | '2026-05-19T18:00:00Z' |
Le plus recommandé, grande lisibilité, conversion parfaite lors du passage à PostgreSQL à l’avenir |
| INTEGER (Unix Timestamp) | 1747656000 |
Faible encombrement, mais illisible pour l’homme |
Ne stockez jamais de dates dans des formats personnalisés arbitraires comme 19/05/2026 18:00, sinon la future migration de données sera une catastrophe.
Piège 3 : Insérer une chaîne dans une colonne Integer ne générera pas d’erreur
Dans PostgreSQL, insérer une chaîne dans une colonne INTEGER génère une erreur immédiatement. Mais SQLite essaiera seulement de la convertir silencieusement, et en cas d’échec, l’acceptera telle quelle.
Cela signifie que des données sales peuvent s’infiltrer silencieusement dans votre base de données jusqu’à ce qu’un jour votre programme plante en raison de la réception d’un type inattendu, ce n’est qu’alors que vous découvrirez le problème.
Programmation défensive : Traiter une base de données désinvolte avec une attitude stricte
Face à la désinvolture de SQLite, vous devez construire des mécanismes de défense stricts dans le développement avec Node.js :
| Niveau de défense | Outil | Rôle |
|---|---|---|
| Garde au compile-time | TypeScript | Capturer les types incorrects à l’étape d’écriture du code |
| Validation d’entrée d’API | Zod | Valider les données entrantes strictement (s’assurer que age est toujours un nombre) |
| Conversion implicite de types | Prisma / Drizzle ORM | Gérer automatiquement les différences de types entre SQLite et PostgreSQL |
Déplacer le "videur de validation de données" de la couche base de données vers la couche application
Node.jsest une stratégie clé pour tirer parti de la rapidité de développement de SQLite tout en garantissant l’évolutivité future.
Lors de l’utilisation d’un ORM, tant que vous déclarez type: 'boolean' dans votre code, l’ORM le convertit automatiquement en 1/0 lors de la sauvegarde dans SQLite, et le reconvertit en true/false lors de la lecture, masquant parfaitement les différences de types sous-jacentes.
ALTER TABLE est à moitié opérationnel : Ce qui peut et ne peut pas être modifié
Le support de SQLite pour la modification des structures de tables est très limité :
| Opération | Pris en charge |
|---|---|
Ajouter une colonne (ADD COLUMN) |
Oui |
Renommer une colonne (RENAME COLUMN) |
Oui |
Supprimer une colonne (DROP COLUMN) |
Oui (dans les versions plus récentes) |
Renommer une table (RENAME TO) |
Oui |
| Modifier le type d’une colonne | Non |
Ajouter/supprimer les contraintes UNIQUE, NOT NULL |
Non |
| Modifier la clé primaire | Non |
| Modifier la clé étrangère | Non |
Une fois que vous devez effectuer l’une des modifications "non prises en charge",
SQLiteexige que vous exécutiez la stratégie de "recréer et déplacer".
Les quatre étapes pour recriar et déplacer : La façon de mettre à niveau les tables de SQLite
Comme cela ne peut pas être modifié directement, la pratique standard recommandée par les documents officiels est de construire une nouvelle maison, d’y déménager les meubles, de faire sauter l’ancienne maison et d’apposer la nouvelle plaque nominative :
| Étape | Action | Description |
|---|---|---|
| 1 | Créer une nouvelle table | CREATE TABLE users_new (...) en utilisant la structure correcte |
| 2 | Copier les données | INSERT INTO users_new SELECT ... FROM users |
| 3 | Supprimer l’ancienne table | DROP TABLE users |
| 4 | Renommer la table | ALTER TABLE users_new RENAME TO users |
Ces quatre étapes doivent être exécutées d’une seule traite; toute coupure de courant ou crash d’application à mi-chemin entraînera la perte de données.
Garantir que les mises à niveau ne perdent pas de données : Deux lignes de défense de sécurité
Ligne de défense 1 : Défense physique, copier le fichier directement
SQLite est essentiellement un simple fichier. Avant d’exécuter des modifications de schéma, copiez simplement le fichier .db en tant que sauvegarde.
const fs = require('fs');
fs.copyFileSync('my_project.db', 'my_project_backup.db');
Si les choses tournent mal, le remplacement du fichier restaure tout en un clin d’œil. C’est un avantage que d’autres grandes bases de données ne peuvent pas offrir.
Ligne de défense 2: Enveloppe de Transaction, la machine à remonter le temps de la base de données
Enveloppez toutes les étapes de migration dans une seule Transaction; si une étape échoue, tout le processus sera automatiquement annulé (Rollback) comme si rien ne s’était passé.
const Database = require('better-sqlite3');
const db = new Database('my_project.db');
const migrateData = db.transaction(() => {
db.prepare(`
CREATE TABLE users_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL DEFAULT 18
)
`).run();
db.prepare(`
INSERT INTO users_new (id, name, age)
SELECT id, name, COALESCE(age, 18) FROM users
`).run();
db.prepare('DROP TABLE users').run();
db.prepare('ALTER TABLE users_new RENAME TO users').run();
});
try {
migrateData();
console.log('Table mise à niveau avec succès');
} catch (error) {
console.error('La mise à niveau a échoué, données restaurées en toute sécurité :', error.message);
}
"Fichier de sauvegarde + Liaison par Transaction" constitue l’airbag de la migration de votre base de données.
Contrôlez la désinvolture de SQLite avec une attitude stricte
Domptez la désinvolture de
SQLiteavec une architecture stricte de couche d’application pour profiter de sa vitesse de développement ultrarapide tout en évitant les dettes techniques futures.
Le système de types de SQLite est très désinvolte, et ALTER TABLE présente de nombreuses limitations.
Cependant, tant que vous effectuez la vérification des types TypeScript + validation Zod + l’abstraction ORM du côté de Node.js, combinées à la stratégie de sécurité de sauvegarde physique + Transaction, vous pouvez profiter en toute sécurité de l’efficacité de développement offerte par SQLite tout en ouvrant la voie à une migration aisée vers PostgreSQL à l’avenir.