The moment your bot needs to remember something between restarts — a running score, a warning count, per-server settings — it needs somewhere to put it. Plain variables reset every time the server stops. This guide covers three real options, from a text file to a managed database, and when each one is the right call. The code examples are for the Node.js application; the trade-offs apply to any language.
| At a glance | |
|---|---|
| You need | a working bot (see the discord.js or discord.py guide) |
| Time | about twenty minutes |
Option 1: a JSON file
The simplest thing that works. Read a .json file on start, keep the data in memory, write it back when it changes:
const fs = require('fs');
let data = {};
try {
data = JSON.parse(fs.readFileSync('data.json', 'utf8'));
} catch {
data = {}; // first run, no file yet
}
function save() {
fs.writeFileSync('data.json', JSON.stringify(data, null, 2));
}
This is genuinely fine for a handful of values that rarely change — a config, a couple of counters, a prototype. But it stops being fine as you grow. There's no protection against two events writing at once, and if the process is killed mid-write (an out-of-memory kill, a timer expiry) you can end up with a truncated, unreadable file. Treat JSON as a starting point, not a foundation.
Option 2: SQLite, on the server
When you outgrow a JSON blob but don't want a separate service, SQLite is the sweet spot: a real database that lives in a single file next to your bot, with proper queries and safe concurrent writes. On the Node.js application the better-sqlite3 package installs cleanly — it ships prebuilt binaries, so there's no compiler to worry about. Add it from the Packages page, then:
const Database = require('better-sqlite3');
const db = new Database('bot.db');
// Create the table once; the IF NOT EXISTS makes this safe every start.
db.exec(`
CREATE TABLE IF NOT EXISTS points (
user_id TEXT PRIMARY KEY,
score INTEGER NOT NULL DEFAULT 0
)
`);
// Prepared statements: reusable, fast, and safe against injection.
const addPoint = db.prepare(
`INSERT INTO points (user_id, score) VALUES (?, 1)
ON CONFLICT(user_id) DO UPDATE SET score = score + 1`
);
const getScore = db.prepare('SELECT score FROM points WHERE user_id = ?');
// ...inside a command handler:
addPoint.run(interaction.user.id);
const row = getScore.get(interaction.user.id);
await interaction.reply(`You have ${row.score} points.`);
💡 Tip: Always pass user input as
?parameters like this, never by building SQL strings — that's what keeps a mischievous username from becoming a database command.
⚠️ Heads up: The
bot.dbfile lives in/home/container, on the server itself. It survives restarts, but a reinstall wipes it — and switching your server to a different application (or deploying a template that switches it) is a reinstall.
For a single bot that stays on one runtime, SQLite is an excellent default. If your data must outlive that, use option 3.
Option 3: a managed database
For data that has to survive anything — reinstalls, application switches, or being shared between two servers — use a managed MySQL database from the server's Databases page. It runs on a shared database host that Falix keeps online for you; nothing to start or babysit, and it outlives your server's own lifecycle. Create one following Add a database, and the page shows a connection string like:
mysql://user:pass@host:port/dbname
Put that string in your .env (call it DATABASE_URL) so it stays out of your code and out of Git, add the mysql2 package from the Packages page, and connect:
const mysql = require('mysql2/promise');
const pool = mysql.createPool(process.env.DATABASE_URL);
await pool.execute(
`CREATE TABLE IF NOT EXISTS points (
user_id VARCHAR(32) PRIMARY KEY,
score INT NOT NULL DEFAULT 0
)`
);
// ...inside a command handler:
await pool.execute(
`INSERT INTO points (user_id, score) VALUES (?, 1)
ON DUPLICATE KEY UPDATE score = score + 1`,
[interaction.user.id]
);
const [rows] = await pool.execute(
'SELECT score FROM points WHERE user_id = ?',
[interaction.user.id]
);
await interaction.reply(`You have ${rows[0].score} points.`);
Same ?-parameter discipline as SQLite. For the full connection walkthrough (and PostgreSQL/MongoDB, which the Databases page also offers), see Connect your app to a database.
Which one?
| JSON | SQLite | Managed DB | |
|---|---|---|---|
| Best for | a few values, one writer, a prototype | a single bot on one runtime | data shared or that must outlive reinstalls |
| Queries | none | real SQL | real SQL |
| Extra service | none | none | managed host |
| Survives a reinstall | no | no | yes |
JSON is easiest to start and first to break under load; SQLite is the default for a single bot, just remember its file is local; a managed database is the pick when the data must outlive reinstalls, or you switch runtimes, or several servers share it.
Keep it safe
- Keep data files out of Git.
.env,bot.db,data.json— none of them belong in a repository. The starter.gitignorealready excludes.env; add your data files too. See Keep secrets out of Git. - Back up what matters.
bot.dbanddata.jsonsit in/home/containerlike any other server file, so a server backup captures them — take one from the Backups page before risky changes, and restore it if something goes wrong. See Backups. A managed database already lives independently of your server, so it isn't part of these file backups.