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 voorINSERT/UPDATE/DELETE.- Je kunt positionele parameters met
?of benoemde parameters met@naamgebruiken. 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.