Free Hosting

Using the Shared MySQL Database on the Free Plan

Set up the MySQL database included with the free plan — connection details, pooled connections in Node.js and Python, schema tips and backups.

On this page
  1. What “shared” means
  2. Step 1: Create the database
  3. Step 2: Connect from Node.js
  4. Step 3: Connect from Python
  5. Keep the pool small
  6. Design your tables well
  7. Create tables on startup — carefully
  8. Back it up yourself
  9. Be a good neighbour
  10. Migrating from JSON files
  11. When to move beyond the shared database
  12. Summary

The free plan includes a MySQL database, which is one of the most useful things a small bot can have. It turns JSON files that corrupt on a crash into proper tables with transactions, indexes and concurrent access. This guide shows how to create it, connect from Node.js and Python, and use it well.

What “shared” means

The free database runs on a MySQL server shared with other free users. You get your own database and credentials on that server; other users can’t see your data. What’s shared is the server’s capacity, which is why good habits — small connection pools, indexed queries — matter a little more here than on dedicated plans.

For heavier workloads, paid plans include a database with more headroom and automatic backups, and standalone database plans give MySQL 8 or PostgreSQL 16 dedicated resources.

Step 1: Create the database

In the panel, open your server’s Databases section and create a database. You’ll see:

  • Host and port
  • Database name
  • Username and password

Store these as environment variables on your server — either separately (DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME) or as one URL:

DATABASE_URL=mysql://USER:PASSWORD@HOST:PORT/DATABASE

Never commit them to git. See keeping database credentials secure.

Step 2: Connect from Node.js

Use mysql2 with a small connection pool:

npm install mysql2
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  uri: process.env.DATABASE_URL,
  connectionLimit: 3,        // small pools are plenty for a bot
  waitForConnections: true,
});

async function getBalance(guildId, userId) {
  const [rows] = await pool.execute(
    'SELECT balance FROM balances WHERE guild_id = ? AND user_id = ?',
    [guildId, userId]
  );
  return rows[0]?.balance ?? 0;
}

pool.execute() uses prepared statements — the ? placeholders keep user input out of your SQL, which prevents SQL injection.

Step 3: Connect from Python

For async bots (discord.py, aiogram), use aiomysql:

pip install aiomysql
import os
from urllib.parse import urlparse
import aiomysql

async def create_pool():
    url = urlparse(os.environ["DATABASE_URL"])
    return await aiomysql.create_pool(
        host=url.hostname, port=url.port or 3306,
        user=url.username, password=url.password, db=url.path.lstrip("/"),
        minsize=1, maxsize=3, autocommit=True,
    )

async def get_balance(pool, guild_id: int, user_id: int) -> int:
    async with pool.acquire() as conn, conn.cursor() as cur:
        await cur.execute(
            "SELECT balance FROM balances WHERE guild_id = %s AND user_id = %s",
            (guild_id, user_id),
        )
        row = await cur.fetchone()
        return row[0] if row else 0

Create the pool once at startup (in setup_hook for discord.py, or aiogram’s startup hook) and close it on shutdown with pool.close() followed by await pool.wait_closed().

Keep the pool small

Every open connection uses memory on the database server, and shared servers limit connections per user. A bot rarely needs more than 2–5 connections: each query takes milliseconds, so a few connections serve a lot of traffic. If you see “Too many connections” errors, lower your pool size rather than raising it, and make sure every acquired connection is released. More detail in connection limits and pooling.

Design your tables well

A few rules save a lot of pain later:

  • Store Discord and Telegram IDs as BIGINT. They don’t fit in a regular INT. Telegram chat IDs can be negative, so use signed BIGINT.
  • Add a primary key to every table.
  • Index the columns you filter by. A query like WHERE guild_id = ? AND user_id = ? needs an index on (guild_id, user_id).
  • Use utf8mb4 so emoji and all Unicode characters work.
CREATE TABLE balances (
  guild_id  BIGINT NOT NULL,
  user_id   BIGINT NOT NULL,
  balance   BIGINT NOT NULL DEFAULT 0,
  PRIMARY KEY (guild_id, user_id)
) DEFAULT CHARSET = utf8mb4;

Use atomic updates for anything like balances — UPDATE ... SET balance = balance - ? WHERE ... AND balance >= ? — so double-clicks can’t create money. Designing an economy bot that scales explains why.

Create tables on startup — carefully

For a small bot, running CREATE TABLE IF NOT EXISTS ... at startup is a simple way to set up the schema. When you need to change tables later, use numbered migration files or a migration tool rather than editing by hand, so you always know what state the database is in. See schema migrations for bot developers.

Back it up yourself

The free plan doesn’t include automatic backups, so export your data regularly from your own computer:

mysqldump --single-transaction -h HOST -P PORT -u USER -p DATABASE > bot-backup.sql

Keep a few recent copies. Paid plans back up automatically and let you restore without overwriting the original database.

Be a good neighbour

On a shared server, efficient queries help everyone — including you:

  • Select only the columns you need instead of SELECT *.
  • Add LIMIT to queries that could return many rows, like leaderboards.
  • Cache results that don’t change every second, such as a top-10 leaderboard, for 30–60 seconds.
  • Avoid running a query per member in a loop; fetch what you need in one query.

Migrating from JSON files

If your bot stores data in JSON today, moving to MySQL is a one-off script: read the JSON, insert rows, then switch your code to read from the database. Do it with the bot stopped so nothing changes mid-migration. The result is data that survives crashes and concurrent commands, which JSON files never will.

When to move beyond the shared database

Consider a paid plan or a standalone database when you need automatic backups, more connections, PostgreSQL features, or consistent performance for a busy bot. Upgrading your bot’s plan in place keeps your environment variables, so switching the connection string is often the only change needed.

Summary

The free plan’s shared MySQL database gives your bot a real, private database: create it in the panel, keep the credentials in environment variables, and connect through a small pool — mysql2 in Node.js or aiomysql in Python — with parameterised queries. Store IDs as BIGINT, index what you filter on, use atomic updates, back up with mysqldump, and keep queries efficient. It’s a big step up from JSON files, and it costs nothing.