aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Bots Discord

Discord Bot SQLite avec better-sqlite3 : le guide

Une configuration Discord bot SQLite est une solution de base de données largement suffisante pour les bots de petite et moyenne taille. Sans monter un serveur de base de données dédié, vous gardez toutes vos données dans un seul fichier et vous persistez en quelques secondes des éléments comme un système de niveaux, une économie, des journaux d'avertissements ou des réglages. Dans ce guide, je détaille pas à pas comment ajouter une base locale à un bot Node.js utilisant discord.js et better-sqlite3 : créer les tables et écrire des requêtes rapides et sûres.

Pourquoi SQLite et pourquoi better-sqlite3 ?

SQLite est une base de données relationnelle sans serveur, basée sur un fichier. Pour un bot, cela apporte plusieurs avantages :

  • Aucune installation : pas besoin de faire tourner un service séparé comme MySQL ou PostgreSQL. La base est un simple fichier .sqlite.
  • Portabilité : déplacer le bot vers un autre VPS revient à copier ce fichier.
  • Rapidité : les données sont lues sur le disque dans le même processus que votre application. Aucune latence réseau.

Côté Node.js, il existe deux pilotes populaires : node:sqlite (le module intégré livré avec Node 22+) et better-sqlite3. Je privilégie ici better-sqlite3 car il possède une API synchrone. Cela peut sembler à contre-courant, mais comme les appels SQLite se terminent extrêmement vite, vous écrivez un code direct et lisible sans jongler avec des chaînes async/await. C'est une bibliothèque mature, bien testée, qui gère efficacement les requêtes préparées.

Installation et structure du projet

Ajoutons d'abord les dépendances. better-sqlite3 est un module natif qui se compile à l'installation ; vous aurez donc besoin d'outils de compilation sur votre machine.

npm install discord.js better-sqlite3

Une structure simple et propre pourrait ressembler à ceci :

my-bot/
  index.js        # point d'entree du bot
  db.js           # connexion et schema de la base
  data/
    bot.sqlite    # fichier SQLite (cree automatiquement)
  .gitignore

Important : ajoutez toujours data/bot.sqlite à votre .gitignore. Votre base de données ne doit jamais finir dans le contrôle de version.

Ouvrir la connexion et créer le schéma

Ouvrir la connexion à un seul endroit et l'exporter est l'approche la plus propre, ainsi tous les fichiers partagent la même connexion. Dans db.js :

const Database = require('better-sqlite3');
const path = require('node:path');

const db = new Database(path.join(__dirname, 'data', 'bot.sqlite'));

// Reglages recommandes pour la performance et la durabilite
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');

// Creer la table si elle n'existe pas (sur a chaque demarrage)
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    user_id   TEXT PRIMARY KEY,
    guild_id  TEXT NOT NULL,
    xp        INTEGER NOT NULL DEFAULT 0,
    level     INTEGER NOT NULL DEFAULT 1,
    coins     INTEGER NOT NULL DEFAULT 0
  );
`);

module.exports = db;

journal_mode = WAL (Write-Ahead Logging) est presque toujours le bon choix pour un bot car il gère bien mieux les lectures et écritures concurrentes. Grâce à CREATE TABLE IF NOT EXISTS, vous préparez la table en toute sécurité à chaque démarrage du bot.

Lire et écrire en sécurité avec les requêtes préparées

La bonne approche dans better-sqlite3 est d'utiliser des requêtes préparées. Elles sont plus rapides et, parce que vous passez les valeurs en paramètres, elles vous protègent contre l'injection SQL. N'intégrez jamais une saisie utilisateur dans une requête par concaténation de chaînes.

const db = require('./db');

// Preparez les requetes une fois et reutilisez-les
const getUser = db.prepare(
  'SELECT * FROM users WHERE user_id = ? AND guild_id = ?'
);

const upsertXp = db.prepare(`
  INSERT INTO users (user_id, guild_id, xp)
  VALUES (@userId, @guildId, @amount)
  ON CONFLICT(user_id) DO UPDATE SET xp = xp + @amount
`);

function addXp(userId, guildId, amount) {
  upsertXp.run({ userId, guildId, amount });
  return getUser.get(userId, guildId);
}

Quelques points importants ici :

  • .get() renvoie une seule ligne, .all() renvoie toutes les lignes sous forme de tableau, et .run() sert pour INSERT/UPDATE/DELETE.
  • Vous pouvez utiliser des paramètres positionnels avec ? ou des paramètres nommés avec @nom.
  • ON CONFLICT ... DO UPDATE (un « upsert ») insère si l'enregistrement est absent et met à jour s'il existe. Idéal pour les systèmes de niveaux.

Relier un événement de message à la base

Relions maintenant cela à un véritable événement côté discord.js. Donnons un peu d'XP chaque fois qu'un utilisateur envoie un message :

const { Client, GatewayIntentBits } = require('discord.js');
const { addXp } = require('./xp');

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
});

client.on('messageCreate', (message) => {
  if (message.author.bot || !message.guild) return;

  const user = addXp(message.author.id, message.guild.id, 5);

  // Verification simple de passage de niveau
  const needed = user.level * 100;
  if (user.xp >= needed) {
    db.prepare('UPDATE users SET level = level + 1 WHERE user_id = ?')
      .run(message.author.id);
    message.reply(`Felicitations, niveau ${user.level + 1} !`);
  }
});

client.login(process.env.DISCORD_TOKEN);

Remarque : MessageContent est une intention privilégiée et vous devez l'activer depuis le Discord Developer Portal pour accéder au contenu des messages.

Opérations groupées et performance

Effectuer de nombreuses écritures une par une sollicite le disque trop souvent. Les envelopper dans une transaction est à la fois bien plus rapide et offre une garantie tout ou rien :

const insertMany = db.transaction((rows) => {
  for (const row of rows) upsertXp.run(row);
});

// Des centaines de lignes en une seule operation disque
insertMany(grandeListeDeDonnees);

Quelques conseils supplémentaires : n'ouvrez et ne fermez pas la base à répétition, utilisez une seule connexion pendant toute la durée de vie de l'application ; ajoutez un index avec CREATE INDEX sur les colonnes souvent interrogées ; et sauvegardez régulièrement votre fichier bot.sqlite, car toutes vos données vivent dans ce seul fichier.

Questions fréquentes

SQLite suffit-il pour un gros bot ?

Pour des bots accédés depuis un seul processus et servant jusqu'à des dizaines de milliers d'utilisateurs, SQLite fonctionne sans souci. Pour des projets massifs avec de fortes écritures concurrentes, ou si vous devez répartir le bot horizontalement sur plusieurs processus ou serveurs, passer à une base serveur comme PostgreSQL est plus pertinent.

better-sqlite3 est synchrone ; ne va-t-il pas bloquer le bot ?

Les requêtes SQLite prennent généralement des microsecondes, donc en pratique elles ne bloquent pas la boucle d'événements de façon perceptible. Néanmoins, regroupez les très grosses opérations en lot dans une transaction et évitez d'exécuter des milliers de requêtes une par une.

Que se passe-t-il si le fichier de base est corrompu ?

Le mode WAL améliore la durabilité face aux arrêts soudains, mais des sauvegardes régulières restent indispensables. Plutôt que de copier le fichier pendant que le bot tourne, préférez la méthode de sauvegarde offerte par better-sqlite3, ou arrêtez le bot puis copiez le fichier.

Besoin d'une base de données solide pour votre bot ? Si vous souhaitez concevoir un bot Discord évolutif pour un système de niveaux, une économie ou des journaux de modération, contactez-moi — construisons ensemble la solution adaptée à votre projet.

Devamı için