Learning objectives
After reading this article you will be able to:
- Know the criteria that decide the right database for a Python project
- Compare SQLite, PostgreSQL, MySQL, MongoDB and Redis with current libraries
- Pick a database per scenario: Django, FastAPI, bot, script or cache
Python works well with almost any database. There are mature drivers for PostgreSQL, MySQL, MongoDB and Redis, and SQLite already ships in the standard library. That is good news, but it turns the choice into a long list of options that look equivalent.
In practice, the decision comes down to a few questions: what shape your data has, which framework you use, how many processes write at the same time, and whether your code is synchronous or asynchronous. Below you will find the criteria, each option with its current library, and a recommendation per scenario.
Which criteria matter when choosing?#
- Shape of the data. Users, orders and payments linked to each other call for tables, foreign keys and
JOIN, which means a relational database. Documents whose fields vary a lot from record to record fit a document database better. The difference between the two models is covered in SQL vs NoSQL. - Framework. Django has its own ORM and officially supports five relational databases. FastAPI and Flask impose nothing, and the most common pick is SQLAlchemy.
- Concurrency. A script or single-process bot is different from an API with several workers writing at the same time.
- Sync or async. If your code uses
asyncio(FastAPI, bots), prefer a driver with an async API so you do not block the event loop. - Operations. A SQLite file needs no server. The others need a host, credentials, connections and backups.
When is SQLite enough?#
The sqlite3 module is part of Python's standard library. SQLite stores everything in one file and runs inside your process, with no separate server. It is ideal for scripts, command-line tools, prototypes, tests and small single-process apps.
import sqlite3
from contextlib import closing
with closing(sqlite3.connect("app.db")) as con:
con.execute("PRAGMA journal_mode=WAL")
con.execute(
"CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL)"
)
with con: # commit at the end of the block, rollback on error
con.execute("INSERT INTO notes (text) VALUES (?)", ("hello",))
for row in con.execute("SELECT id, text FROM notes"):
print(row)A detail that trips up many people: using the connection in a with block commits or rolls back, but does not close the connection. That is why the example uses contextlib.closing, as the documentation itself suggests.
The limit is on writes. The SQLite documentation explains that it allows one writer at a time per database file and is not a good fit when many machines access the same database over a network. If the file lives in a folder your deploy replaces, the data goes with it.
Why is PostgreSQL the default choice?#
PostgreSQL is a client-server relational database built for many simultaneous connections, with transactions, a wide range of indexes, JSONB and extensions. For almost any Python project that will grow, it is the choice with the fewest regrets. Learn more in what PostgreSQL is.
The most common libraries:
- psycopg 3 (the
psycopgpackage): the current driver, with sync and async APIs. Django's documentation recommends it and warns that psycopg2 support is likely to be deprecated in the future. - asyncpg: an async-only driver, common in FastAPI projects.
- SQLAlchemy 2: an ORM and SQL toolkit widely used outside Django. It works with PostgreSQL, MySQL and MariaDB, SQLite, Oracle and SQL Server, and on PostgreSQL it accepts psycopg, psycopg2, asyncpg and pg8000.
With SQLAlchemy, the same code runs on SQLite on your machine and on PostgreSQL in production. Only the URL changes: without DATABASE_URL, the example uses sqlite:///app.db; in production, set something like postgresql+psycopg://user:password@host/dbname.
import os
from sqlalchemy import String, create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(80))
engine = create_engine(os.environ.get("DATABASE_URL", "sqlite:///app.db"))
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(User(name="Ana"))
session.commit()
for user in session.scalars(select(User).where(User.name == "Ana")):
print(user.id, user.name)Create the engine once, when the process starts. It keeps a connection pool; creating one per request is the recipe for the too many connections error. For production, install the driver with pip install "psycopg[binary]" sqlalchemy. Differences between SQLite and PostgreSQL still show up in types and functions, so test on the same database you will use in production before you ship.
What about MySQL or MariaDB?#
MySQL and MariaDB are relational databases as capable as PostgreSQL for most web applications. Django supports both and recommends the mysqlclient driver. Other options are PyMySQL, written in pure Python, and Oracle's MySQL Connector/Python. In SQLAlchemy, the URL becomes mysql+mysqldb:// or mysql+pymysql://.
Pick MySQL when your team already knows it, when you will integrate with a system that already runs it, or when it is what your host offers. The detailed comparison is in PostgreSQL vs MySQL.
When does MongoDB make sense in Python?#
MongoDB stores documents that look like Python dictionaries, with no fixed schema. It fits catalogs with varying attributes, event logs and profiles that change with each app version. See what MongoDB is.
The official driver is PyMongo. Watch out for old tutorials: MongoDB deprecated Motor on May 14, 2025 in favor of PyMongo's own async API (AsyncMongoClient). Motor received only bug fixes until May 14, 2026 and, after that, only critical fixes until its end of support.
import os
from pymongo import MongoClient
client = MongoClient(os.environ["MONGODB_URI"])
profiles = client["app"]["profiles"]
profiles.update_one(
{"_id": "user-42"},
{"$inc": {"xp": 10}, "$setOnInsert": {"plan": "basic"}},
upsert=True,
)
print(profiles.find_one({"_id": "user-42"}))In Django, MongoDB has no official support. There is the third-party django-mongodb-backend, and Django's documentation warns that the features third-party backends support vary considerably. If the project is Django, stay relational.
Can Redis be the main database?#
Redis keeps data in memory and answers very fast. In Python, the redis package includes both a sync and an async client (redis.asyncio). It is the right tool for caching, sessions, rate limiting, task queues (Celery and RQ can use Redis as a broker) and counters.
As a main database it needs care: writing to disk is configurable, and the data has to fit in RAM. In most projects Redis sits next to PostgreSQL or MongoDB, not in their place. The difference is explained in Redis as a cache or as a database.
Which database should you pick for each scenario?#
| Scenario | Recommendation | Library |
|---|---|---|
| Script, CLI, prototype, tests | SQLite | sqlite3 (standard library) |
| Django project | PostgreSQL | Django ORM + psycopg 3 |
| API with FastAPI or Flask | PostgreSQL | SQLAlchemy 2 + psycopg 3 or asyncpg |
| Legacy system or team already on MySQL | MySQL or MariaDB | mysqlclient or PyMySQL |
| Documents with varying shapes | MongoDB | PyMongo (sync or AsyncMongoClient) |
| Cache, sessions, queues | Redis, next to the main database | redis (redis.asyncio for async) |
| Small Discord or Telegram bot | SQLite, then PostgreSQL | sqlite3, then psycopg 3 |
When in doubt, start with PostgreSQL and SQLAlchemy (or the Django ORM). You get a database that can grow and a layer that hides most differences between engines. For bots, the detailed analysis is in which database to use for a Discord bot.
Which mistakes should you avoid?#
- Building SQL with f-strings. Always use parameters (
?in sqlite3,%sin psycopg, or the ORM). An f-string with user input opens the door to SQL injection. - Opening a connection per request. Create the engine or pool once and reuse it.
- A sync driver inside async code. One blocking query stalls the whole FastAPI or bot event loop.
- Credentials in code. Read the connection URL from an environment variable, as in the examples.
- No copy of the data. Schedule dumps (
pg_dump,mongodump) and test a restore before you need one.
To ship the application together with the database, see how to host FastAPI, Flask or Django.
How to do this on Square Cloud#
On Square Cloud you can host MongoDB, PostgreSQL, MySQL and Redis starting on the Standard plan (R$44.99 per month); Hobby does not include databases. You create the database in the dashboard (name, memory and engine), and it is provisioned in seconds in the same datacenter as your applications, with an average latency of about 1 ms. PostgreSQL is native PostgreSQL, without extensions, and Redis has persistence.
Almost nothing changes on the Python side: put the full connection URL the dashboard hands you in an environment variable on your application (DATABASE_URL, MONGODB_URI) and read it with os.environ, as in the examples above. The change applies on the next restart, with no rebuild. TLS is mandatory: download the certificate in the dashboard and point the driver at the CA; on PostgreSQL and MongoDB the client also presents the .pem certificate. On Redis, use rediss://. Because the database accepts outside connections, you can run the same code on your machine with the same certificate.
You take manual snapshots whenever you want, and every plan that includes databases also has an automatic daily snapshot, kept for 7 days. A snapshot can be restored into the same database, another one of the same engine or a new database, or downloaded as a .zip. See the engines and plans on the database hosting page.
Database hosting
MongoDB, PostgreSQL, MySQL and Redis next to your app
FAQs
What is the best database for Django?
PostgreSQL. Django officially supports PostgreSQL, MariaDB, MySQL, Oracle and SQLite, and many of the framework's extras, such as array fields and full-text search, exist only for PostgreSQL. To get started, the project's default SQLite works.
Does Python need anything installed to use SQLite?
Usually not. The sqlite3 module is part of Python's standard library, and SQLite runs inside your process with no separate server. Some distributions build Python without it, so check that the import works.
Should I still use Motor for MongoDB with asyncio?
Not in a new project. MongoDB deprecated Motor in May 2025 in favor of PyMongo's own async API, which provides the AsyncMongoClient class. Motor stopped getting regular bug fixes in May 2026.
psycopg2 or psycopg 3?
psycopg 3 (the psycopg package) for new projects. It supports async, and Django's documentation recommends it, warning that psycopg2 support is likely to be deprecated in the future.
More in Databases
See all 24 articles- How much does database hosting cost?Cloud database hosting costs from zero to hundreds of dollars a month. See what sets the price: instance, disk, traffic, snapshots, and how to estimate yours.
- How to host a database for your appTo host a database, pick an engine, create the instance, store the connection string in an environment variable and test the connection. Step-by-step guide.
- What is Redis used for?Redis is an in-memory data store that keeps key-value pairs and rich data structures. Learn how it works, what it is used for and when not to use it.
- How to back up your databaseBack up MySQL, PostgreSQL and MongoDB with mysqldump, pg_dump and mongodump, then restore the dump. See the commands, how to verify and common mistakes.