Docs
Skip to content

MySQL

Django_

Use Django's ORM with an Appwrite native MySQL database. Configure the DATABASES setting, run migrations against the direct MySQL port, and choose the right pooler mode for Django connections.

4 min read

Raw

A native MySQL database is a standard MySQL engine, so Django's ORM works against it with no Appwrite-specific configuration. Point the DATABASES setting at the credentials from the Connections page, then use migrations, models, and the rest of Django as you would against any MySQL server.

Install a driver

Django talks to MySQL through a DB API driver. Django's current recommended driver is mysqlclient:

Bash
pip install mysqlclient

mysqlclient builds against MySQL client libraries. If installation fails, install the MySQL development headers and pkg-config for your operating system, then run the same command again.

Django's MySQL backend uses the ENGINE value django.db.backends.mysql.

Configure DATABASES

In settings.py, point the default connection at your native MySQL database. Appwrite Cloud requires TLS on the public hostname, and the certificate is signed by a public certificate authority. Read every value from the environment so credentials stay out of source control:

Python
import os
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ["DB_HOST"],
"PORT": os.environ["DB_PORT"],
"OPTIONS": {
"charset": "utf8mb4",
"ssl_mode": os.environ.get("DB_SSL_MODE", "REQUIRED"),
},
}
}

The MySQL backend passes OPTIONS to mysqlclient. charset keeps client encoding aligned with Django's UTF-8 expectations, and ssl_mode controls TLS for the MySQL connection. Use REQUIRED for encrypted Cloud connections. For full certificate and hostname verification, set DB_SSL_MODE=VERIFY_IDENTITY. See Network security for TLS and IP allowlist guidance.

Populate the environment with values from mysql.get() in the API credentials flow:

.env
DB_NAME=<database>
DB_USER=admin
DB_PASSWORD=<password>
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=3306
DB_SSL_MODE=REQUIRED

Port 3306 is the direct MySQL port. Keep migrations on this port, and see pooling below for when to add the pooler.

Run migrations

Generate migrations from your models, then apply them against the direct MySQL port:

Bash
python manage.py makemigrations
python manage.py migrate

migrate needs a session connection with DDL privileges. The primary admin user owns the database and can run schema changes. Always run migrate on the direct MySQL port, not through the transaction-mode pooler.

Define a model

Models map to tables in your native MySQL database. Define one in an app's models.py:

Python
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "django_articles"
ordering = ["-created_at"]

Run makemigrations and migrate again to create the table, then query it through the ORM:

Python
from blog.models import Article
Article.objects.create(title="Hello", body="First post")
recent = Article.objects.order_by("-created_at")[:10]

Persistent connections and pooling

By default Django opens a new connection per request (CONN_MAX_AGE = 0). On a long-running WSGI server (Gunicorn, uWSGI), you can reuse connections by raising it. Each worker thread keeps its own connection, so the database must allow at least as many connections as you run worker threads:

Python
DATABASES["default"]["CONN_MAX_AGE"] = 60
DATABASES["default"]["CONN_HEALTH_CHECKS"] = True

CONN_HEALTH_CHECKS revalidates a reused connection once per request, reducing errors after an engine restart. Do not enable persistent connections under the development server, because it spawns a thread per request and gains nothing. Keep persistent connections disabled under ASGI.

If you front the database with the transaction-mode connection pooler, point HOST and PORT at the MySQL pooler on port 6033 and keep CONN_MAX_AGE = 0:

.env
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=6033

Transaction mode does not preserve session state between statements. Use the direct port or session mode for migrations, temporary tables, user variables, named locks, and other session-scoped behavior. See the pooler page for the trade-offs.

Use a branch for previews and CI

Branches are instant, isolated copies of a database with their own hostname and connection details. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

  1. Create a branch from the API and read its connection details.
  2. Export them as the DB_* environment variables your settings read.
  3. Run python manage.py migrate and your test suite against the branch.
  4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, its schema and data match the source database at branch time, so migrations run against realistic data without touching production.

Was this page helpful?

Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.