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

Discord Bot SQLite-integratie met better-sqlite3

Een Discord bot SQLite-opzet is een ruimschoots voldoende databaseoplossing voor kleine en middelgrote bots. Zonder een aparte databaseserver op te zetten houd je al je data in één bestand en bewaar je in seconden zaken als een levelsysteem, een economie, waarschuwingslogboeken of instellingen. In deze gids loop ik stap voor stap door hoe je een lokale database toevoegt aan een Node.js-bot met discord.js en better-sqlite3: tabellen opzetten en snelle, veilige queries schrijven.

Waarom SQLite en waarom better-sqlite3?

SQLite is een serverloze, bestandsgebaseerde relationele database. Voor een bot levert dat verschillende voordelen op:

  • Geen installatie: je hoeft geen aparte dienst zoals MySQL of PostgreSQL te draaien. De database is een enkel .sqlite-bestand.
  • Draagbaarheid: de bot naar een andere VPS verplaatsen is slechts een kwestie van dat bestand kopiëren.
  • Snelheid: data wordt van schijf gelezen binnen hetzelfde proces als je app. Er is geen netwerklatentie.

Aan de Node.js-kant zijn er twee populaire drivers: node:sqlite (de ingebouwde module die met Node 22+ meekomt) en better-sqlite3. Ik kies hier voor better-sqlite3 omdat het een synchrone API heeft. Dat klinkt misschien tegendraads, maar omdat SQLite-aanroepen extreem snel klaar zijn, schrijf je directe, leesbare code zonder te goochelen met async/await-ketens. Het is een volwassen, goed geteste bibliotheek die prepared statements efficiënt afhandelt.

Installatie en projectstructuur

Laten we eerst de afhankelijkheden toevoegen. better-sqlite3 is een native module die bij installatie wordt gecompileerd, dus je hebt buildtools op je machine nodig.

npm install discord.js better-sqlite3

Een eenvoudige, nette structuur kan er zo uitzien:

my-bot/
  index.js        # startpunt van de bot
  db.js           # databaseverbinding en schema
  data/
    bot.sqlite    # SQLite-bestand (wordt automatisch aangemaakt)
  .gitignore

Belangrijk: voeg data/bot.sqlite altijd toe aan je .gitignore. Je database hoort nooit in versiebeheer terecht te komen.

De verbinding openen en het schema aanmaken

De verbinding op één plek openen en exporteren is de schoonste aanpak, zodat elk bestand dezelfde verbinding deelt. In db.js:

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

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

// Aanbevolen instellingen voor prestaties en duurzaamheid
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');

// Maak de tabel aan als die niet bestaat (veilig bij elke start)
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) is bijna altijd de juiste keuze voor een bot, omdat het gelijktijdig lezen en schrijven veel beter afhandelt. Dankzij CREATE TABLE IF NOT EXISTS kun je de tabel veilig klaarzetten telkens als de bot opstart.

Veilig lezen en schrijven met prepared statements

De juiste aanpak in better-sqlite3 is het gebruik van prepared statements. Die zijn sneller en, omdat je waarden als parameters doorgeeft, beschermen ze je tegen SQL-injectie. Stop nooit gebruikersinvoer via string-concatenatie in een query.

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

// Bereid queries een keer voor en hergebruik ze
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);
}

Een paar belangrijke punten hier:

  • .get() geeft één rij terug, .all() geeft alle rijen als array, en .run() wordt gebruikt voor INSERT/UPDATE/DELETE.
  • Je kunt positionele parameters met ? of benoemde parameters met @naam gebruiken.
  • ON CONFLICT ... DO UPDATE (een "upsert") voegt in als het record ontbreekt en werkt het bij als het bestaat. Ideaal voor levelsystemen.

Een berichtgebeurtenis koppelen aan de database

Laten we dit nu koppelen aan een echte gebeurtenis aan de discord.js-kant. Geef wat XP telkens als een gebruiker een bericht stuurt:

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);

  // Eenvoudige level-up-controle
  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(`Gefeliciteerd, level ${user.level + 1}!`);
  }
});

client.login(process.env.DISCORD_TOKEN);

Let op: MessageContent is een privileged intent en je moet het inschakelen via het Discord Developer Portal om toegang te krijgen tot de berichtinhoud.

Bulkbewerkingen en prestaties

Veel schrijfbewerkingen één voor één uitvoeren benadert de schijf te vaak. Ze in een transaction verpakken is veel sneller én geeft je een alles-of-niets-garantie:

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

// Honderden rijen in een enkele schijfbewerking
insertMany(groteDataLijst);

Nog een paar tips: open en sluit de database niet herhaaldelijk, gebruik één verbinding gedurende de hele levensduur van de app; voeg met CREATE INDEX een index toe op kolommen die je vaak opvraagt; en maak regelmatig een back-up van je bot.sqlite-bestand, want al je data zit in dat ene bestand.

Veelgestelde vragen

Is SQLite genoeg voor een grote bot?

Voor bots die vanuit één proces worden benaderd en tot tienduizenden gebruikers bedienen, werkt SQLite prima. Voor enorme projecten met veel gelijktijdige schrijfacties, of waar je de bot horizontaal over meerdere processen of servers moet schalen, is overstappen naar een serverdatabase zoals PostgreSQL logischer.

better-sqlite3 is synchroon; blokkeert het de bot niet?

SQLite-queries duren meestal microseconden, dus in de praktijk blokkeren ze de event loop niet merkbaar. Voer toch zeer grote bulkbewerkingen gegroepeerd uit binnen een transaction en vermijd het één voor één draaien van duizenden queries.

Wat als het databasebestand beschadigd raakt?

De WAL-modus verbetert de duurzaamheid tegen plotselinge afsluitingen, maar regelmatige back-ups blijven essentieel. Kopieer het bestand liever niet terwijl de bot draait; gebruik de back-upmethode die better-sqlite3 biedt, of stop de bot en kopieer het bestand.

Een solide databasebasis nodig voor je bot? Wil je een schaalbare Discord-bot ontwerpen voor een levelsysteem, economie of moderatielogboeken, neem dan contact met me op — laten we samen de juiste oplossing voor jouw project bouwen.

Devamı için