Database Integration & Analytics
Learn how to use SQLite database with your bot to store and retrieve data. Track user activity, save preferences, and build simple leaderboards.
Learning Objectives
By the end of this module, you will be able to:
Database Setup
Set up a SQLite database to store bot data persistently using Bun's built-in database support.
Initialize SQLite Database
Bun includes SQLite built-in. Create a database file and define a simple table to track user activity.
import { Database } from 'bun:sqlite'
// Create or open database
const db = new Database('bot.db')
// Create a simple user stats table
db.run(`
CREATE TABLE IF NOT EXISTS user_stats (
user_id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
message_count INTEGER DEFAULT 0,
reaction_count INTEGER DEFAULT 0,
last_active INTEGER DEFAULT 0
)
`)
console.log('✅ Database initialized!')What This Does
- • Creates a
bot.dbfile in your project - • Defines a
user_statstable with basic columns - • Uses
IF NOT EXISTSto avoid errors on restart - • Stores user ID, space ID, and activity counts
Tracking Activity
Track user messages and reactions by updating the database whenever users interact.
Track Messages
Update the database every time a user sends a message.
bot.onMessage(async (handler, event) => {
const { userId, spaceId } = event
// Skip bot messages
if (userId === bot.botId) return
// Update or insert user stats
db.run(`
INSERT INTO user_stats (user_id, space_id, message_count, last_active)
VALUES (?, ?, 1, ?)
ON CONFLICT(user_id) DO UPDATE SET
message_count = message_count + 1,
last_active = ?
`, [userId, spaceId, Date.now(), Date.now()])
console.log(`📊 Updated stats for ${userId}`)
})Track Reactions
Similarly, track when users add reactions.
bot.onReaction(async (handler, event) => {
const { userId, spaceId } = event
// Skip bot reactions
if (userId === bot.botId) return
// Update reaction count
db.run(`
INSERT INTO user_stats (user_id, space_id, reaction_count, last_active)
VALUES (?, ?, 1, ?)
ON CONFLICT(user_id) DO UPDATE SET
reaction_count = reaction_count + 1,
last_active = ?
`, [userId, spaceId, Date.now(), Date.now()])
})Key Concepts
- •
INSERT ... ON CONFLICTupdates existing records or creates new ones - • Always skip bot's own messages to avoid tracking bot activity
- • Use
Date.now()to track when users were last active - • Database operations are fast and don't slow down message handling
Leaderboard System
Query the database to show top users by message count.
Leaderboard Command
Create a /leaderboard command that shows the most active users.
bot.onSlashCommand('leaderboard', async (handler, { channelId, spaceId }) => {
try {
// Query top 10 users by message count
const topUsers = db.query(`
SELECT user_id, message_count, reaction_count
FROM user_stats
WHERE space_id = ?
ORDER BY message_count DESC
LIMIT 10
`).all(spaceId)
if (topUsers.length === 0) {
await handler.sendMessage(channelId, '📊 No activity data yet!')
return
}
// Build leaderboard message
let leaderboard = '🏆 **Top Contributors**\n\n'
topUsers.forEach((user, index) => {
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `${index + 1}.`
leaderboard += `${medal} <@${user.user_id}>\n`
leaderboard += ` 💬 ${user.message_count} messages | ❤️ ${user.reaction_count} reactions\n\n`
})
await handler.sendMessage(channelId, leaderboard)
} catch (error) {
console.error('Leaderboard error:', error)
await handler.sendMessage(channelId, '❌ Error fetching leaderboard')
}
})SQL Query
- •
ORDER BY message_count DESCsorts highest first - •
LIMIT 10gets only top 10 - •
WHERE space_id = ?filters by space
Formatting
- • Use medals (🥇🥈🥉) for top 3
- • Mention users with
<@userId> - • Show both messages and reactions
Database for Production
Prepare your database code to work in production environments like Render.
Use Environment Variables
Make your database path configurable so it works both locally and in production.
import { Database } from 'bun:sqlite'
// Use environment variable for database path
// Locally: uses './bot.db'
// On Render: uses '/data/bot.db' (persistent disk)
const DB_PATH = process.env.DATABASE_PATH || './bot.db'
// Create or open database
const db = new Database(DB_PATH)
// Create tables
db.run(`
CREATE TABLE IF NOT EXISTS user_stats (
user_id TEXT PRIMARY KEY,
space_id TEXT NOT NULL,
message_count INTEGER DEFAULT 0,
reaction_count INTEGER DEFAULT 0,
last_active INTEGER DEFAULT 0
)
`)
console.log(`✅ Database initialized at: ${DB_PATH}`)How This Works
- • Local Development: No env var set → uses
./bot.db - • Production (Render): Set
DATABASE_PATH=/data/bot.db - • Persistent Storage: Render Disks keep data between deployments
Render Database Configuration
When deploying to Render, you'll need to configure a persistent disk for your database.
Render Disk Setup
- • Add a persistent disk in Render
- • Mount path:
/data - • Size: 1 GB (free tier)
- • Set env:
DATABASE_PATH=/data/bot.db
Why This Matters
- • Data persists across restarts
- • Leaderboards don't reset
- • User stats are saved
- • Production-ready setup
Next Steps
You'll learn the complete Render deployment process in Module 6: Deployment & Production, including setting up the persistent disk, environment variables, and automatic deployments.
Module 5 Complete!
Great work! You've learned how to use SQLite with your bot to store data, track user activity, and build leaderboards. Your bot can now remember information and show useful statistics!