The frontend is React with Mantine and the backend is FastAPI.
- Docker Engine / Docker Desktop
- mise to manage Node, uv, and yarn
Install tools and set up pre-commit hooks:
mise install- Install frontend dependencies:
cd frontend && yarn install- Run code generation (gRPC + Orval):
make- Start backend + infrastructure:
docker compose up --build-
Start the frontend (see Frontend).
-
Open http://localhost:3000.
make generates both gRPC stubs (backend) and Orval API clients (frontend). Run it from the project root whenever .proto files or the backend API change.
make # generate everything
make clean # clean generated artifactsDocker Compose starts the backend, PostgreSQL, MinIO (S3), Keycloak, and the Notifications API.
docker compose up --buildThe backend reloads automatically on file changes. For changes to dependencies or generated code, restart the container.
Services:
| Service | URL |
|---|---|
| Backend API | http://localhost:8000 |
| API Docs (OpenAPI) | http://localhost:8000/docs |
| MinIO (S3) | http://localhost:9000 |
| Keycloak | http://localhost:8181 |
| Notifications API | http://localhost:6781 |
| PostgreSQL | localhost:5432 |
You can run the frontend locally or in Docker. Both support hot reload.
Locally:
cd frontend && yarn devIn Docker:
docker compose --profile frontend up --buildMigrations run automatically when the backend container starts. To create a new migration after model changes:
docker compose exec backend alembic revision --autogenerate -m "description"To apply or revert manually:
docker compose exec backend alembic upgrade head
docker compose exec backend alembic downgrade -1docker compose exec backend sh -lc "cd /app && /opt/venv/bin/python scripts/seed_test_data.py"Creates/updates test users (unconfirmed, staff, admin, confirmed company), companies, and one KP event with confirmed bookings, nametags, and a simple nametag background. The script is idempotent.
The backend follows a layered pattern: routes -> services -> repositories.
- Routes (
app/routes/) handle HTTP concerns only. - Services (
app/services/) contain business logic. Authorization is enforced via decorators (@require_staff,@require_admin, etc.) inapp/core/decorators.py. - Repositories (
app/repositories/) handle all database access, one repository per domain.
Periodic tasks are registered with the Scheduler in app/core/scheduler.py. To add a task, define an async function and register it in app/main.py:
scheduler.add(my_task, interval=3600) # interval in secondsBoth run automatically as pre-commit hooks via mise install. To run manually:
ruff format backend/ # format
ruff check backend/ # lint
pre-commit run --all-files # run all hooks- Ensure FastAPI routes are properly typed with return types and request body schemas
- Use meaningful
operation_ids -- Orval uses these to generate client function names - Example:
@router.post("/login", operation_id="loginUser") async def login(request: LoginRequest) -> TokenResponse: ...
- Use i18next for all UI text -- never hardcode strings
- Save translations in
frontend/public/locales/[language].json - Use translation keys:
t("key.path")instead of plain text