Skip to main content

Backend

Stack

TechnologyPurpose
Python 3.12Runtime
FastAPIWeb framework
SQLModelORM (built on SQLAlchemy + Pydantic)
PydanticData validation and settings
PyJWTJWT creation and validation
requests-oauthlibOAuth1 client for SSO
psycopgPostgreSQL driver
uvicornASGI server

Entry point

apps/api/main.py

Creates the FastAPI app, registers CORS middleware, and includes all routers:

app = FastAPI(title="DETI Maker Lab API", version="1.0")

app.include_router(auth_router, tags=["auth"])
app.include_router(equipment_router)
app.include_router(requisitions_router)
app.include_router(projects_router)
app.include_router(users_router)

CORS origins are configured from settings.FRONTEND_URL — no hardcoded domains.


Configuration

apps/api/core/config.py

Settings are loaded from environment variables using Pydantic's BaseSettings. All required variables are defined in .env.example.

Key settings:

SettingDescription
DATABASE_URLPostgreSQL connection URL
SNIPEIT_BASE_URLInternal Docker URL for Snipe-IT API
SNIPEIT_API_TOKENSnipe-IT personal access token
SNIPEIT_RESERVED_STATUS_IDNumeric ID of the "Reserved" status label in Snipe-IT
SNIPEIT_PUBLIC_URLBrowser-facing Snipe-IT URL
FRONTEND_URLBrowser-facing frontend URL
SSO_CALLBACK_URLOAuth1 callback URL registered with university SSO
DML_AUTH_KEYUniversity OAuth1 client key
DML_AUTH_SECRETUniversity OAuth1 client secret
JWT_SECRET_KEYSecret for JWT signing
JWT_ALGORITHMAlways HS256
JWT_EXPIRE_MINUTESToken lifetime (default: 60)
LAB_TECHNICIANSComma-separated list of technician emails

Database access

apps/api/db/database.py

Uses SQLModel's create_engine with the DATABASE_URL. Sessions are provided via FastAPI dependency injection:

def get_session() -> Generator[Session, None, None]:
with Session(engine) as session:
yield session

Models

apps/api/db/models.py

SQLModel classes map to PostgreSQL tables. See Data Model for the full schema. Key models:

  • User
  • Project
  • ProjectMember
  • EquipmentModel
  • Equipment
  • EquipmentRequest
  • EquipmentRequestItems (table exists in schema but SQLModel class is defined in migration context)
  • EquipmentUsage
  • StatusHistory
  • Notification

Auth module

apps/api/auth/

FilePurpose
router.pySSO login, callback, verify, logout endpoints
service.pyOAuth1 flow, JWT creation, user creation, Snipe-IT user provisioning
dependencies.pyget_current_user dependency for protected routes
schemas.pyPydantic schemas for auth responses

The get_current_user dependency reads the JWT from either the Authorization: Bearer header or the token cookie and returns the authenticated User model.


Services

apps/api/services/

FileResponsibility
project_service.pyProject CRUD, member management, project status transitions
requisition_service.pyRequisition lifecycle: create, approve, reject, sync with Snipe-IT
inventory_service.pyCatalog sync from Snipe-IT
notification_service.pyCreate and deliver in-app notifications
users_service.pyUser update logic
constants.pyShared constants (status values, etc.)

Snipe-IT client

apps/api/services/snipeit/

The Snipe-IT client is a wrapper around the Snipe-IT REST API using requests. It handles:

  • Asset listing, detail, and status updates.
  • Model listing.
  • Activity log polling.
  • User creation and lookup.

All calls use the SNIPEIT_BASE_URL and SNIPEIT_API_TOKEN from settings. The client is injected into services that require Snipe-IT access.


Routers

apps/api/routers/

FileRouter prefixDescription
equipment.py/api/equipmentCatalog, sync, asset detail
projects.py/api/projectsProject CRUD, members, requisitions
requisitions.py/api/requisitionsApproval, rejection, Snipe-IT sync
users.py/api/usersUser management
schemas.pyShared Pydantic request/response models

Running the backend

# Development (via Turborepo)
pnpm --filter api dev

# Direct (from apps/api with venv active)
uvicorn main:app --reload --host 0.0.0.0 --port 8000

The pnpm --filter api dev command uses venv/bin/uvicorn directly. You must create the virtual environment first.


Adding a new endpoint

  1. Define the Pydantic schema in routers/schemas.py.
  2. Add the route handler in the appropriate router file.
  3. Implement business logic in the corresponding services/ file.
  4. Update the data model in db/models.py if new tables or fields are needed.
  5. Update infra/db/init/schema.sql with any DDL changes.
  6. Document the endpoint in API Reference.