A backend service that simulates a SaaS subscription system with limits on staff members and bookings, built with FastAPI and SQLite.
# Install dependencies
just install
# Run the server
just run
# Run tests
just testOr with Docker:
just docker-upThe API is available at http://localhost:8000. Interactive docs at http://localhost:8000/docs.
All /accounts endpoints require a JWT Bearer token. Obtain one first:
# Get a token (credentials: user / user)
curl -X POST http://localhost:8000/token \
-H "Content-Type: application/json" \
-d '{"username": "user", "password": "user"}'
# Use the token in subsequent requests
curl -X POST http://localhost:8000/accounts \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "My Salon"}'| Method | Path | Description |
|---|---|---|
| POST | /token |
Get JWT access token |
| POST | /accounts |
Create an account |
| POST | /accounts/{id}/subscribe |
Assign a subscription |
| POST | /accounts/{id}/staff |
Add a staff member |
| POST | /accounts/{id}/bookings |
Create a booking |
| GET | /health |
Health check |
| Feature | Basic | Pro |
|---|---|---|
| Max staff members | 2 | 5 |
| Max bookings per day | 5 | 20 |
app/
core/ # Shared infrastructure
auth.py # JWT authentication
database.py # SQLAlchemy engine & session
exceptions.py # Domain exceptions
plans.py # Plan type enum & limits config
features/ # Feature-based modules
accounts/
models.py # SQLAlchemy model
schemas.py # Pydantic request/response DTOs
interfaces.py # ABC interfaces (IAccountRepository, IAccountService)
repository.py # Database access layer
service.py # Business logic
controller.py # HTTP concern translation
router.py # FastAPI route definitions & DI wiring
staff/ # Same layered structure
bookings/ # Same layered structure
auth/ # Token endpoint (controller + router)
tests/
features/
accounts/
test_account_service.py # Unit tests with mocked repository
staff/
test_staff_service.py
bookings/
test_booking_service.py
- Feature-based directory structure: Each domain concept (accounts, staff, bookings) is a self-contained module with its own model, schemas, interfaces, repository, service, controller, and router. This keeps related code colocated and makes it easy to navigate.
- Layered architecture: Each feature follows the pattern: Router (route definitions) -> Controller (HTTP concern translation, exception-to-status mapping) -> Service (business logic) -> Repository (database access). Each layer has a single responsibility.
- Interface segregation: Services and repositories implement abstract base classes (
IAccountService,IStaffRepository, etc.). This enables dependency injection and makes unit testing straightforward — services are tested with mocked repositories, no database needed. - Domain exceptions: Business rule violations are expressed as domain exceptions (
StaffLimitExceededError,BookingLimitExceededError) incore/exceptions.py. Controllers translate these to appropriate HTTP responses, keeping services framework-agnostic. - Plan limits as data: Subscription plan limits are defined in a
PLAN_LIMITSdictionary incore/plans.py, making it straightforward to add new plans or adjust limits without touching business logic. - FastAPI dependency injection: Each router uses
Depends()to wire the full dependency chain (db session -> repository -> service -> controller) per request.
- Staff members and bookings are append-only (no delete/update endpoints needed).
- The daily booking limit is per-account, not per-staff-member.
- Changing an account's plan does not retroactively remove excess staff or bookings.
- A single shared user authenticates all API requests (no per-account auth).
- Staff and booking creation require an active subscription plan (returns 403 otherwise).
- Database: Replace SQLite with PostgreSQL for concurrent access and better performance.
- User management: Implement proper user registration, password hashing (bcrypt/argon2), and per-account authorization.
- DI container: Replace manual wiring in routers with a proper DI container (e.g.,
dependency-injector) for cleaner composition roots. - Rate limiting: Add request rate limiting to prevent abuse.
- Pagination: Add list endpoints with cursor-based pagination for staff and bookings.
- Plan upgrades/downgrades: Handle edge cases when switching plans (e.g., account has 5 staff on Pro and downgrades to Basic).
- Soft deletes: Add soft-delete support for staff and bookings.
- Migrations: Use Alembic for database schema migrations instead of
create_all. - Observability: Add structured logging, request tracing, and metrics.
- Environment config: Move secrets (JWT key) to environment variables with proper secret management.