Django and PostgreSQL backend for a curriculum knowledge platform that models concepts, relationships and learning paths.
curricula.live is being developed around a simple question:
What must a learner understand, and what should come before it?
The platform represents curriculum content as a graph of concepts and typed relationships. This repository is the Django backend API, with environment-based configuration, PostgreSQL connectivity, administration, graph read endpoints, health checks and isolated tests.
- Django 5.2 on Python 3.12.
- PostgreSQL connection through provider-neutral
DATABASE_URL. - Compatibility settings for PostgreSQL poolers.
- Django admin and built-in authentication foundation.
- JSON discovery endpoint at
/. - JSON health endpoint at
/health/. - Versioned read endpoints for concepts, relations, relation types, neighbourhoods and prerequisite traversal.
- Explicit cross-origin access for configured curricula.live frontend origins; no wildcard CORS.
- Environment configuration through
django-environ. - Dependency and virtual-environment management through
uv. pytestandpytest-djangotest infrastructure.- In-memory SQLite test database isolated from development and deployed PostgreSQL data.
- WSGI and ASGI entry points for deployment flexibility.
- Vercel-compatible production settings with exact preview-host handling.
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
API discovery metadata |
GET |
/health/ |
Service readiness and smoke-check response |
| varies | /admin/ |
Django administrative interface |
GET |
/v1/concepts/ |
List concepts |
GET |
/v1/concepts/<slug>/ |
Read a concept |
GET |
/v1/concepts/<slug>/neighborhood/ |
Read incoming/outgoing graph neighbourhood |
GET |
/v1/concepts/<slug>/prerequisites/ |
Bounded prerequisite traversal |
GET |
/v1/relations/ |
List/filter relations |
GET |
/v1/relations/<uuid>/ |
Read a relation |
GET |
/v1/relation-types/ |
List relation types |
Example discovery response:
{
"service": "curricula.live API",
"latest_version": "v1",
"versions": {
"v1": "/v1/"
}
}Example health response:
{
"status": "ok",
"service": "curricula.live api"
}The dedicated api.curricula.live hostname makes a second /api/ namespace redundant. Stable public consumers select an explicit major version such as /v1/; bare paths such as /concepts/ are not floating aliases. A future /v2/ should be introduced only for genuinely breaking contract changes.
flowchart LR
Web[curricula.live web client]
Admin[Django admin user]
Monitor[Health monitor]
Django[Django 5.2 API]
Core[core application]
DB[(Managed PostgreSQL)]
Tests[pytest-django]
SQLite[(In-memory SQLite)]
Web --> Django
Admin --> Django
Monitor -->|GET /health/| Django
Django --> Core
Django --> DB
Tests --> Core
Tests --> SQLite
flowchart TD
Env[.env or deployment environment]
Settings[config/settings.py]
Secret[DJANGO_SECRET_KEY]
Debug[DJANGO_DEBUG]
Hosts[DJANGO_ALLOWED_HOSTS]
CORS[DJANGO_CORS_ALLOWED_ORIGINS]
URL[DATABASE_URL]
Psycopg[psycopg 3]
Postgres[(PostgreSQL)]
Env --> Settings
Settings --> Secret
Settings --> Debug
Settings --> Hosts
Settings --> CORS
Settings --> URL
URL --> Psycopg
Psycopg --> Postgres
api/
├── config/
│ ├── settings.py # Runtime and production configuration
│ ├── test_settings.py # Isolated test environment
│ ├── urls.py # Root URL routing
│ ├── asgi.py # ASGI application entry point
│ └── wsgi.py # WSGI application entry point
├── core/
│ ├── admin.py # Domain admin configuration
│ ├── middleware.py # Narrow CORS policy
│ ├── models.py # Unmanaged curriculum read models
│ ├── urls.py # Versioned graph routes
│ ├── views.py # Discovery, health and graph read endpoints
│ └── migrations/
├── tests/ # API, graph, admin and deployment-facing tests
├── docs/
│ ├── architecture.svg
│ └── deployment.md # Vercel production runbook
├── .env.example # Local/environment configuration template
├── .python-version # Python runtime selection
├── manage.py # Django management command entry point
├── pyproject.toml # Project and dependency declaration
├── pytest.ini # pytest-django configuration
├── uv.lock # Reproducible dependency lockfile
└── README.md
- Python 3.12 or newer within the supported project range.
uvfor dependency and environment management.- PostgreSQL for normal development and deployment.
- A PostgreSQL connection string exposed as
DATABASE_URL.
git clone https://github.com/curricula-live/api.git
cd apiLinux and macOS:
curl -LsSf https://astral.sh/uv/install.sh | shWindows PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"Restart the terminal if uv is not immediately available on PATH.
uv sync --devThis creates or updates the local virtual environment from pyproject.toml and uv.lock.
The main runtime dependencies are:
- Django;
django-environ;- psycopg 3 with its binary distribution.
The development dependency group adds:
- pytest;
- pytest-django.
Linux, macOS or Git Bash:
cp .env.example .envWindows Command Prompt:
copy .env.example .envGenerate a Django secret:
uv run python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"Update .env:
APP_ENV=development
DJANGO_SECRET_KEY=replace-with-generated-secret
DJANGO_DEBUG=true
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver
DJANGO_CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?sslmode=requireAny PostgreSQL provider can be used. Never commit .env.
uv run python manage.py checkuv run python manage.py migrateThe curriculum graph tables are currently read through unmanaged models. Django migrations still manage Django's built-in authentication, administration, session and content-type tables.
uv run python manage.py createsuperuseruv run python manage.py runserverOpen:
- API discovery:
http://127.0.0.1:8000/ - Health endpoint:
http://127.0.0.1:8000/health/ - Concepts:
http://127.0.0.1:8000/v1/concepts/ - Admin interface:
http://127.0.0.1:8000/admin/
curl http://127.0.0.1:8000/
curl http://127.0.0.1:8000/health/Expected health response:
{"status":"ok","service":"curricula.live api"}uv run pytestThe suite covers discovery/routing, health, domain read models, concepts, relations, relation types, neighbourhoods, prerequisite traversal, admin configuration and CORS behavior.
pytest.ini loads config.test_settings. That module overrides:
DATABASE_URL=sqlite://:memory:
before importing normal settings. Consequently:
- tests cannot accidentally modify a developer or deployed PostgreSQL database;
- the suite does not require network access;
- test state is temporary and discarded after the process exits;
- health and application tests remain fast.
Database-specific behaviour still requires separate PostgreSQL integration tests.
The application requires DATABASE_URL:
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DATABASEFor hosted PostgreSQL requiring TLS:
DATABASE_URL=postgresql://USER:PASSWORD@HOST:5432/DATABASE?sslmode=requireWhen the configured engine is PostgreSQL, settings apply:
CONN_MAX_AGE = 0;- disabled server-side cursors;
prepare_threshold = None.
These choices avoid connection-state problems with poolers and serverless instances. They trade some persistent-connection optimisation for predictable provider compatibility.
The API must not import provider-specific database SDKs. Moving from one managed PostgreSQL provider to another should primarily be a DATABASE_URL and compatibility-validation change.
| Variable | Required | Example | Description |
|---|---|---|---|
DJANGO_SECRET_KEY |
yes | generated random value | Cryptographic signing secret |
DJANGO_DEBUG |
no | false |
Enables Django debug mode; defaults to false |
DJANGO_ALLOWED_HOSTS |
no | api.curricula.live |
Comma-separated accepted hostnames |
DJANGO_CORS_ALLOWED_ORIGINS |
no | https://curricula.live |
Browser origins allowed to call the read API |
DJANGO_CSRF_TRUSTED_ORIGINS |
no | https://api.curricula.live |
Trusted origins for Django CSRF checks |
DATABASE_URL |
yes | PostgreSQL URL | Database connection |
APP_ENV |
no | production |
Enables production-safe defaults when set to production |
DJANGO_SECURE_SSL_REDIRECT |
no | true |
Override HTTPS redirect behavior |
DJANGO_SESSION_COOKIE_SECURE |
no | true |
Override secure session-cookie behavior |
DJANGO_CSRF_COOKIE_SECURE |
no | true |
Override secure CSRF-cookie behavior |
DJANGO_SECURE_HSTS_SECONDS |
no | 31536000 |
Override HSTS lifetime |
DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS |
no | false |
Opt into HSTS for subdomains |
DJANGO_SECURE_HSTS_PRELOAD |
no | false |
Opt into HSTS preload signaling |
When Vercel supplies VERCEL_URL or VERCEL_PROJECT_PRODUCTION_URL, their exact hostnames are accepted automatically for preview/production deployments. The app does not allow all *.vercel.app hosts.
| Task | Command |
|---|---|
| Install/synchronise dependencies | uv sync --dev |
| Run Django checks | uv run python manage.py check |
| Run deployment checks | uv run python manage.py check --deploy |
| Create migrations | uv run python manage.py makemigrations |
| Apply migrations | uv run python manage.py migrate |
| Create administrator | uv run python manage.py createsuperuser |
| Start local server | uv run python manage.py runserver |
| Run tests | uv run pytest |
| Open Django shell | uv run python manage.py shell |
This backend is intentionally being built in small, reviewable increments:
- Establish a stable Django and PostgreSQL foundation.
- Add one domain concept at a time.
- Keep environment configuration explicit.
- Add tests with every behavioural change.
- Preserve a clear boundary between canonical data, API behaviour and presentation.
- Avoid hiding infrastructure decisions behind unexplained abstractions.
A future contributor should be able to understand why a dependency or setting exists without reconstructing the project’s history from pull requests.
The wider curricula.live system is expected to grow around entities such as:
- Concept — a unit of knowledge or skill;
- Relation type — the meaning of a connection, such as prerequisite or part-of;
- Relation — a directed, typed connection between concepts;
- Curriculum — an organised educational framework or programme;
- Curriculum placement — where a concept appears within a curriculum;
- Resource — a lesson, explanation, example, exercise or assessment;
- Learning path — an ordered or graph-derived route through concepts;
- Evidence / provenance — where curriculum claims and mappings originated.
The current production target is Vercel at api.curricula.live, with the frontend deployed independently and PostgreSQL reached through DATABASE_URL.
Vercel's Django integration detects manage.py, WSGI and Django staticfiles, so this repository intentionally does not carry a legacy Python builder or catch-all routing configuration.
Production requires at minimum:
APP_ENV=production
DJANGO_SECRET_KEY=<generated secret>
DJANGO_DEBUG=false
DJANGO_ALLOWED_HOSTS=api.curricula.live
DATABASE_URL=<managed PostgreSQL URL>
DJANGO_CORS_ALLOWED_ORIGINS=https://curricula.live,https://www.curricula.live
DJANGO_CSRF_TRUSTED_ORIGINS=https://api.curricula.live,https://curricula.live,https://www.curricula.liveProduction migrations are not run automatically on preview deployments. They are a controlled release step so previews cannot mutate a shared production database.
See docs/deployment.md for the Vercel project setup, public routing contract, custom-domain/DNS procedure, migration flow, static/admin verification, security settings and migration-away strategy.
CI also runs:
uv run python manage.py check --deployagainst a production-shaped configuration.
- PostgreSQL-specific behaviour is not yet covered by a dedicated integration-test environment.
- API authentication and write authorization have not yet been introduced.
- There is no generated OpenAPI schema yet.
- Production deployment still requires account-level Vercel environment variables and custom-domain configuration outside the repository.
Keep each contribution focused on one logical change. A typical workflow:
git checkout dev
git pull
git checkout -b feat/descriptive-change-name
uv sync --dev
uv run pytestBefore opening a pull request:
uv run python manage.py check
uv run pytestDocument new environment variables, migrations, dependencies and operational assumptions in the same pull request that introduces them.
The curricula-live organisation separates application concerns into focused repositories. This API works alongside repositories for the web client, canonical data and organisation-level documentation.
No explicit licence is currently included. Unless a licence is added, the repository remains under the copyright holder’s default rights.