---
title: "FastAPI + MongoDB — Adding Roles with Schema Validator Migration"
aliases: [fastapi-role-enum, mongodb-role-migration, fastapi-lifespan-seed, mongodb-validator-migration]
tags: [fastapi, mongodb, roles, migration, python, backend]
sources:
  - "daily/2026-04-16.md"
created: 2026-04-16
updated: 2026-04-16
---

# FastAPI + MongoDB — Adding Roles with Schema Validator Migration

Adding a new role to a FastAPI + MongoDB application touches three layers: the Python enum, the React frontend routing, and the MongoDB collection schema validator. Missing the MongoDB migration causes the application to reject user creation for the new role at the database level — silently if error handling swallows validation errors.

## Key Points

- **MongoDB schema validators block inserts/updates** with values not in the enum — adding a role to the Python enum alone is insufficient
- **FastAPI lifespan** is the right place for idempotent seed functions (default admin creation) — runs on every startup, safe to re-run
- When a role mirrors another role's permissions, add it alongside the existing role in **all** `require_roles()` calls and all frontend `case` statements
- Use `grep -r "REVIEWER\|reviewer"` to find every location a role is referenced before starting — avoids missed locations
- 21 occurrences was the count for the REVIEWER→add-LINGUIST change — expect 10-25 occurrences in a medium-sized monorepo

## Details

### The Three-Layer Change

**Layer 1 — Python Enum (backend)**
```python
# app/models/user.py
class UserRole(str, Enum):
    ADMIN = "admin"
    REVIEWER = "reviewer"
    LINGUIST = "linguist"  # ← new
```

**Layer 2 — FastAPI route guards**
```python
# In every endpoint that checked for REVIEWER:
@router.get("/qc-review")
async def qc_review(user = Depends(require_roles([UserRole.REVIEWER, UserRole.LINGUIST]))):
    ...
```

Use grep to find all `require_roles` calls:
```bash
grep -rn "require_roles\|UserRole.REVIEWER" app/
```

**Layer 3 — MongoDB migration**
```python
# app/migrations/add_linguist_role.py
from app.db import get_db

async def up():
    db = await get_db()
    await db.command({
        "collMod": "users",
        "validator": {
            "$jsonSchema": {
                "properties": {
                    "role": {
                        "enum": ["admin", "reviewer", "linguist"]  # ← added
                    }
                }
            }
        }
    })
```

Run once after deploy: `python -m app.migrations.migrator up`

### Idempotent Seed in FastAPI Lifespan

Seeding a default admin user in the lifespan handler ensures it exists on every fresh deploy without requiring a manual step:

```python
# app/main.py
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    await seed_default_admin()  # idempotent — no-op if admin exists
    yield

async def seed_default_admin():
    db = await get_db()
    existing = await db.users.find_one({"role": "admin"})
    if not existing:
        await db.users.insert_one({
            "email": "admin@example.com",
            "role": "admin",
            "hashed_password": hash_password("changeme"),
        })
```

Lifespan runs on startup — safe for production because it checks before inserting.

### Frontend Route Mapping (React)

When a role shares all routes with an existing role, add it to every `case` in the router/sidebar:

```jsx
// Sidebar.jsx — route visibility
const showQCRoutes = ['reviewer', 'linguist'].includes(user.role);

// Router switch — add alongside existing case
case 'linguist':   // falls through to reviewer logic
case 'reviewer':
    routes = QC_ROUTES;
    break;
```

A `grep -r "case 'reviewer'" frontend/src/` before starting identifies all locations to change.

## Related Concepts

- [[wiki/tech-patterns/_index]] — FastAPI tech pattern article covers broader FastAPI setup
- [[wiki/concepts/shell-static-deploy-patterns]] — the deploy.sh fix that accompanied this role change
- [[wiki/concepts/monorepo-deploy-script-pitfall]] — the deploy script bug discovered in the same session

## Sources

- [[daily/2026-04-16.md]] — LINGUIST role added to Ford QC web app (FastAPI + React monorepo); 21 REVIEWER occurrences found and updated; MongoDB migration written manually; seed_default_admin added to FastAPI lifespan
