| applyTo | backend/**/*, Taskfile.yml, compose.yaml, frontend/schema/schema.graphql |
|---|
- Keep all backend logic within
backend/. Do not import fromfrontend/. - Cross-repo changes are allowed only when required by the task.
- Contract sync changes include GraphQL schema/codegen outputs (e.g.,
task backend:schema:export/task frontend:codegen). .env.example/ README updates when backend env vars or workflows change.
- Framework: Litestar (
litestar[standard]). Preferasync defhandlers for IO. - ORM: SQLAlchemy via Advanced Alchemy (async, Postgres via
asyncpg). - GraphQL: Strawberry +
strawberry.litestarintegration. - Python:
>=3.14(modern typing; keep type hints accurate forty). - Settings/validation:
pydantic-settings(Pydantic v2). - Package manager:
uv(uv.lockexists). Do not add packages without explicit permission.
- Follow existing Litestar conventions and project structure.
- Avoid drive-by refactors; change only what the task requires.
- Keep Strawberry types/queries/mutations/inputs in their module files under
backend/src/backend/apps/**/graphql/. - Keep resolvers/handlers small and readable. Move multi-step logic into a local
services.pyand call it from resolvers/handlers.
- Do not manually change DB schema/state outside the migration workflow.
- Any SQLAlchemy model/table change requires a migration and a note on how to apply it.
- Prefer Taskfile workflows. If a required workflow is missing (e.g., “create a migration”), add a Task target rather than documenting multi-step manual commands as the official process.
- If a migration is executed (or the task explicitly asks you to execute it),
validate reversibility by running
task backend:migrations:migrateandtask backend:migrations:revert, then re-apply migrations so the local DB returns to latest state unless instructed otherwise.
- If GraphQL behavior/schema changes, call it out and run the repo’s codegen
workflow (
task frontend:codegen) to keep the frontend in sync.
- REST endpoints live in
backend/src/backend/apps/**/controllers.pyand should use LitestarControllerclasses. - Prefer
msgspec.Structfor request/response bodies (Pydantic only for settings). - Keep handlers thin; move non-trivial logic into
services.py. - Pagination (lists):
- Prefer cursor/keyset pagination over offsets.
- Use Litestar’s
AbstractAsyncCursorPaginatorand return{ items: [...], page: { next_cursor, limit, has_next } }. - If a filter/sort changes, clients must reset pagination (drop
cursor). Reusing a cursor with different filters should return 400.
| Layer | Pattern | Example |
|---|---|---|
| Model (ORM) | {Entity}Model |
UserModel |
| Service | {Entity}Service |
UserService |
| GraphQL Type | {Entity}Type |
UserType |
| GraphQL Input | {Entity}Input |
UserInput |
| GraphQL Query | {Entity}Query |
UserQuery |
| GraphQL Mutation | {Entity}Mutation |
UserMutation |
| Admin View | {Entity}AdminView |
UserAdminView |
| Table name | lowercase singular | "user" |
backend/src/backend/
├── apps/{app_name}/
│ ├── __init__.py
│ ├── models.py # SQLAlchemy models
│ ├── services.py # Service + Repository
│ └── graphql/
│ ├── __init__.py # Exports Query/Mutation classes
│ ├── types.py # Output types
│ ├── inputs.py # Input types
│ ├── queries.py # Query resolvers
│ └── mutations.py # Mutation resolvers
└── admin/views/
├── __init__.py # ADMIN_VIEWS registry
└── {entity}.py # Admin view for entity
- Model (
models.py)- Inherit from
IdentityAuditBase. - Use
Mapped[]withmapped_column(). - Set
__tablename__to lowercase singular.
- Inherit from
- Service (
services.py)- Inherit from
SQLAlchemyAsyncRepositoryService[EntityModel]. - Define nested
Repowithmodel_type.
- Inherit from
- GraphQL type (
graphql/types.py)- Use
@strawberry.type+@dataclass. - Implement
from_model()for ORM → GraphQL conversion. - Implement
relay.Nodeand typeidasrelay.NodeID[int].
- Use
- GraphQL input (
graphql/inputs.py)- Use
@strawberry.input+@dataclass. - Include only fields needed for creation/mutation.
- Use
- GraphQL query (
graphql/queries.py)- Prefer
*_by_id(id: strawberry.relay.GlobalID)for details lookup. - Prefer Relay connections for lists when appropriate.
- Prefer
- REST list pagination (when adding REST list endpoints)
- Use cursor pagination with
limit+ optionalcursorand the{ items, page }response envelope.
- Use cursor pagination with
- GraphQL mutation (
graphql/mutations.py)- Use
@strawberry.type+@strawberry.mutation. - Handle
DuplicateKeyErrorandRepositoryError→GraphQLError. - Call
db_session.rollback()on errors; useauto_commit=Truefor single-operation mutations.
- Use
- GraphQL exports (
graphql/__init__.py)- Export Query/Mutation classes.
- Schema registration (
backend/src/backend/schema.py)- Add the new Query/Mutation via inheritance in the root schema.
- Service registration (context): ensure the service is available in GraphQL context via dependency injection/context factory.
- Admin view (
backend/src/backend/admin/views/{entity}.py): inherit fromModelView, set label/name/identity, and exportview. - Admin registration (
backend/src/backend/admin/views/__init__.py): add the new view toADMIN_VIEWS. - Migration: generate and apply a migration using Taskfile targets (review the generated file). If migrations are run, verify downgrade works by reverting the latest migration and migrating again.
Before declaring a backend task complete:
- Run
task backend:format,task backend:lint,task backend:typecheck,task backend:test. - If GraphQL was touched: run
task frontend:codegenand ensurefrontend/schema/schema.graphqlupdates accordingly.