A modern, scalable backend system for a bookstore application built with FastAPI, designed to support a Flutter frontend.
This project started as a simple CRUD API and evolved into a production-grade backend system implementing authentication, background processing, email verification, role-based authorization, and more.
Pagely is a backend service that powers a bookstore platform. It provides:
- User authentication & authorization
- Book management
- Review system
- Email verification
- Background job processing
- Token-based security with revocation
- Clean architecture with service layer separation
This project follows key backend engineering principles:
-
Separation of Concerns
- Routers → HTTP layer
- Services → Business logic
- Models → Database layer
- Dependencies → Wiring & injection
-
Async-first design
-
Stateless APIs with JWT
-
Background processing for heavy tasks
-
Scalable modular structure
| Layer | Technology |
|---|---|
| Framework | FastAPI |
| ORM | SQLModel |
| Database | PostgreSQL (via SQLAlchemy async) |
| Migrations | Alembic |
| Authentication | JWT |
| Password Hashing | Passlib (Argon2) |
| Cache / Broker | Redis |
| Background Jobs | Celery |
| Email Service | FastAPI-Mail |
| Frontend (external) | Flutter |
src/
├── auth/ # Authentication domain
├── books/ # Books domain
├── reviews/ # Reviews domain
├── db/ # Database & Redis config
├── middleware/ # Middleware logic
├── errors/ # Custom exception system
├── mail.py # Email configuration
├── celery.py # Background worker setup
├── config.py # Environment settings
- JWT-based authentication
- Access & Refresh tokens
- Token revocation via Redis
- Email verification
- Role-based authorization
-
User signs in
-
Server returns:
- Access token (short-lived)
- Refresh token (long-lived)
-
Access token is used for API requests
-
Refresh token is used to generate new access tokens
Each token contains:
sub→ user IDjti→ unique token IDiat→ issued atexp→ expirationtype→ access / refresh
Logout doesn't just delete tokens client-side.
Instead:
-
Token
jtiis stored in Redis blacklist -
Every request checks:
if jti in Redis → reject request
- User signs up
- A URL-safe token is generated
- Email sent with verification link
- User clicks link → account verified
Uses:
itsdangerous.URLSafeTimedSerializer
Why?
- Safer for URLs than JWT
- Built-in expiration support
http://DOMAIN/api/v1/auth/verify/<token>
Sending emails during requests is slow and blocking.
Solution:
FastAPI → sends task → Redis → Celery worker executes
- API triggers:
send_mail.delay(data)- Task sent to Redis
- Worker picks it up
- Email is sent asynchronously
Celery is synchronous, but email sending is async.
Solution used:
asgiref.sync.async_to_sync(async_func)- Prevents blocking requests
- Enables horizontal scaling
- Decouples heavy operations
FastAPI's Depends() is used to:
- Inject services
- Inject database sessions
- Inject authenticated users
async def endpoint(service: BookService = Depends(get_book_service)):- Loose coupling
- Testability
- Clean architecture
- Authentication info
- Role system
- Email verification
- Created by a user
- Contains metadata
- Many-to-many (User ↔ Book)
- Composite primary key
- User → Books (1:N)
- User ↔ Book (M:N via Review)
- Book → Reviews (1:N)
- Composite primary key in reviews
- Relationship linking via
link_model - Lazy loading with
selectinload
alembic revision --autogenerate -m "message"
alembic upgrade head- Schema evolution
- Table creation
- Indexes
- Constraints
Logs:
- Request URL
- Method
- Execution time
Adds header:
X-Process-Time
Allows cross-origin requests
Protects against host header attacks
Centralized error handling using custom exceptions.
class AppError(Exception):- InvalidTokenError
- UnauthorizedError
- ServerError
- Consistent API responses
- Cleaner code (no scattered HTTPException)
- Better debugging
Supports:
- Admin-only routes
- Multi-role access
RequireRoles(["ADMIN"])Fine-grained access control without cluttering endpoints.
Uses:
- FastAPI-Mail
- HTML templates
- Config-driven SMTP
- Styled HTML emails
- Configurable SMTP
- Background sending via Celery
Managed via pydantic-settings
- Database
- JWT settings
- Redis config
- Mail credentials
- Celery config
Instead of:
Router → DB directly
We use:
Router → Service → DB
- Token blacklist
- Celery broker
- Fast access
- Better concurrency
- Non-blocking I/O
- Scalability
pip install -r requirements.txtCreate .env based on .env.example
alembic upgrade headuvicorn src:app --reloadredis-servercelery -A src.celery.celery_app worker --loglevel=info- Retry logic for failed tasks
- Email queue monitoring
- Rate limiting
- Caching layer for books
- Search functionality
- Notification system
This project demonstrates:
- How to build a production-ready FastAPI backend
- How to design for scalability early
- How to separate concerns across layers
- How to handle async + background jobs correctly
Feel free to fork, explore, or suggest improvements.
https://github.com/EslamKamel89/pagely
This is not just a CRUD API.
It’s a backend system designed with real-world constraints in mind: performance, scalability, maintainability, and clean architecture.