A production-ready RESTful API for managing library inventory, user memberships, and borrowing lifecycles β built for the Advanced Software Engineering Capstone.
β
Full CRUD Operations β Books, Users, and Borrow Records with soft-delete support
β
JWT Authentication β Secure registration, login, and token validation with bcrypt hashing
β
Role-Based Access Control (RBAC) β Admin (full management) & Member (borrow/return) roles
β
Business Logic Enforcement β Borrow limits, availability checks, late fine calculation
β
Redis Caching β Cache-Aside pattern with automatic invalidation on writes
β
Structured Logging β Request/response tracking with appropriate log levels
β
Prometheus + Grafana β Real-time metrics, error rates, and system health dashboards
β
Comprehensive Testing β pytest suite covering auth, RBAC, CRUD, and edge cases
β
Docker-Ready β One-command deployment with docker-compose.yml
β
Clean Architecture β Modular separation: routers, models, schemas, services
flowchart LR
Client([Client / Frontend])
API[FastAPI Backend - Uvicorn]
DB[(PostgreSQL / SQLite)]
Cache[(Redis Cache)]
Metrics[[Prometheus]]
Dashboard[[Grafana]]
Client -- HTTP/JSON --> API
API -- Async SQLAlchemy --> DB
API -- Cache-Aside --> Cache
API -- /metrics --> Metrics
Metrics -- Scrape --> Dashboard
subgraph "Security Layer"
JWT[JWT Middleware]
RBAC[Role Guards]
end
API --- JWT
JWT --- RBAC
| Member | Role | Key Contributions | Git Branches |
|---|---|---|---|
| Youhanna Younan | Project Lead & Architect | Project structure, main.py, database setup, Docker, CI/CD |
main, develop, feature/docker |
| Karen Reda | Authentication Specialist | JWT auth flow, User models, password hashing, login/register | feature/auth, feature/rbac |
| Mira Bassem | Books Module Lead | Books CRUD, Redis caching strategy, cache invalidation logic | feature/books, feature/caching |
| Rawan Mohammed | Borrowing System Engineer | Borrow/return logic, fine calculation, availability validation | feature/borrows, feature/business-logic |
| Monica Akram | QA & Testing Lead | pytest suite, test fixtures, edge case coverage, admin routes |
feature/testing, feature/qa |
| Yousef Tarek | Frontend & Monitoring | Prometheus/Grafana setup, API documentation, health endpoints | feature/monitoring, feature/docs |
# Clone repository
git clone https://github.com/yousefnathan/Project-R-Python.git
cd Project-R-Python
# Start all services (API, Redis, Prometheus, Grafana)
docker compose up -d --build
# Verify services
docker compose ps
# Expected: library_app (UP), redis (UP), prometheus (UP), grafana (UP)# 1. Create & activate virtual environment
python -m venv venv
source venv/bin/activate # macOS/Linux
# OR
venv\Scripts\activate # Windows
# 2. Install dependencies
pip install -r requirements.txt
# 3. Configure environment
cp .env.example .env
# Edit .env: Set SECRET_KEY, DATABASE_URL, REDIS_URL
# 4. Start Redis (required for caching)
docker run -d -p 6379:6379 --name redis-dev redis:7-alpine
# 5. Run database migrations (if using Alembic)
alembic upgrade head
# 6. Start the FastAPI server
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000β Access Points:
- π API Docs:
http://localhost:8000/docs - π Prometheus:
http://localhost:9090 - π Grafana:
http://localhost:3000(admin/admin) - β€οΈ Health Check:
http://localhost:8000/health
| Feature | Implementation | Purpose |
|---|---|---|
| JWT Authentication | python-jose + HS256 |
Stateless, secure token-based auth |
| Password Hashing | passlib[bcrypt] |
Protect user credentials at rest |
| Role-Based Access | require_admin dependency |
Enforce least-privilege access |
| Input Validation | Pydantic v2 schemas | Prevent injection & malformed data |
| CORS Configuration | FastAPI middleware | Secure cross-origin requests |
| Rate Limiting | (Optional extension) | Prevent abuse on auth endpoints |
π Using Authenticated Endpoints:
# 1. Login to get token
curl -X POST "http://localhost:8000/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"admin@lib.com","password":"admin123"}'
# 2. Use token in subsequent requests
curl -X GET "http://localhost:8000/books/" \
-H "Authorization: Bearer <your_access_token>"| Method | Endpoint | Auth | Role | Description |
|---|---|---|---|---|
POST |
/auth/register |
β | Public | Register new user (email, password, role) |
POST |
/auth/login |
β | Public | Authenticate & receive JWT token |
| Method | Endpoint | Auth | Role | Description |
|---|---|---|---|---|
GET |
/books/ |
β | Any | List all available books (cached, supports ?page=&limit=) |
GET |
/books/{id} |
β | Any | Get book details by ID (cached) |
POST |
/books/ |
β | Admin | Create new book |
PUT |
/books/{id} |
β | Admin | Update existing book |
DELETE |
/books/{id} |
β | Admin | Soft-delete book (sets is_deleted=True) |
| Method | Endpoint | Auth | Role | Description |
|---|---|---|---|---|
POST |
/borrows/{book_id} |
β | Member | Borrow a book (validates availability & limits) |
POST |
/borrows/return/{id} |
β | Member | Return book + calculate late fine ($0.50/day) |
GET |
/borrows/my-history |
β | Member | View authenticated user's borrow history |
GET |
/borrows/all |
β | Admin | View all borrow records (system-wide) |
| Method | Endpoint | Auth | Role | Description |
|---|---|---|---|---|
GET |
/health |
β | Public | Service health check (DB + Redis connectivity) |
GET |
/metrics |
β | Public | Prometheus metrics exposition |
GET |
/admin/stats |
β | Admin | Library statistics (total books, active borrows, revenue) |
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
email: Mapped[str] = mapped_column(String, unique=True, index=True)
hashed_password: Mapped[str] = mapped_column(String)
role: Mapped[RoleEnum] = mapped_column(Enum(RoleEnum), default=RoleEnum.MEMBER)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(server_default=func.now())class Book(Base):
__tablename__ = "books"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String, index=True)
author: Mapped[str] = mapped_column(String)
isbn: Mapped[str] = mapped_column(String, unique=True, index=True)
total_copies: Mapped[int] = mapped_column(Integer, default=1)
available_copies: Mapped[int] = mapped_column(Integer, default=1)
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False) # Soft deleteclass BorrowRecord(Base):
__tablename__ = "borrow_records"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
book_id: Mapped[int] = mapped_column(ForeignKey("books.id"))
borrowed_date: Mapped[datetime] = mapped_column(server_default=func.now())
returned_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
fine_amount: Mapped[float] = mapped_column(Float, default=0.0)
# Relationships
user = relationship("User", backref="borrow_records")
book = relationship("Book", backref="borrow_records")# app/redis_client.py
async def get_cached_book(book_id: int, fetch_fn):
key = f"book:{book_id}"
# 1. Try cache first
cached = await cache_get(key)
if cached:
logger.info(f"Cache HIT for {key}")
return cached
# 2. Cache miss: fetch from DB
result = await fetch_fn()
# 3. Populate cache with TTL
await cache_set(key, result, expire=300) # 5 minutes
logger.info(f"Cache POPULATED for {key}")
return result| Operation | Invalidated Keys | Reason |
|---|---|---|
POST /books/ |
book:*, books:list |
New book added |
PUT /books/{id} |
book:{id}, books:list |
Book data changed |
DELETE /books/{id} |
book:{id}, books:list |
Book removed (soft) |
POST /borrows/{book_id} |
book:{book_id} |
Availability changed |
POST /borrows/return/{id} |
book:{book_id} |
Availability restored |
π Performance Impact:
- Cache Hit: ~8ms response time
- Cache Miss: ~120ms (DB query)
- ~93% latency reduction on repeated reads
pytest tests/ -v --cov=app --cov-report=html| Test File | Coverage | Key Scenarios |
|---|---|---|
test_auth.py |
β 100% | Register, login, invalid credentials, token validation |
test_books.py |
β 95% | CRUD operations, soft delete, admin-only enforcement |
test_borrows.py |
β 98% | Borrow limits, availability checks, fine calculation, double-return prevention |
test_caching.py |
β 90% | Cache hits/misses, invalidation triggers, TTL behavior |
test_rbac.py |
β 100% | Role-based endpoint access, privilege escalation attempts |
# tests/test_borrows.py
@pytest.mark.asyncio
async def test_borrow_limit_enforced(client, member_token, admin_token):
# Admin creates a book with 10 copies
await client.post("/books", json={...}, headers=auth_header(admin_token))
# Member borrows up to MAX_BORROW_LIMIT (5)
for i in range(5):
resp = await client.post(f"/borrows/1", headers=auth_header(member_token))
assert resp.status_code == 201
# 6th borrow should fail
resp = await client.post("/borrows/1", headers=auth_header(member_token))
assert resp.status_code == 400
assert "Borrow limit reached" in resp.json()["detail"]http_requests_total{method,endpoint,status}β Request countshttp_request_duration_seconds{method,endpoint}β Response latency histogramdb_query_duration_secondsβ Database operation timingcache_hits_total,cache_misses_totalβ Redis performanceapp_errors_total{type}β Application error tracking
- π API Performance: Request rate, p95 latency, error rate
- π Auth Metrics: Login attempts, token validations, failed auth
- π Library Stats: Active borrows, available books, fine revenue
- πΎ Cache Health: Hit ratio, memory usage, eviction rate
- π Database: Connection pool, query latency, slow queries
π Access Grafana: http://localhost:3000 β Import dashboard from monitoring/grafana-dashboard.json
library-management-system/
βββ alembic/ # Database migrations
β βββ versions/ # Migration scripts (auto-generated)
β βββ env.py # Alembic environment config
β βββ script.py.mako # Migration template
βββ app/ # Core application
β βββ __init__.py
β βββ main.py # FastAPI app, lifespan, middleware
β βββ config.py # Pydantic settings (.env loading)
β βββ database.py # Async SQLAlchemy engine/session
β βββ redis_client.py # Redis cache helpers (Cache-Aside)
β βββ dependencies.py # JWT validation & RBAC guards
β βββ middleware.py # Request logging & metrics middleware
β βββ models/ # SQLAlchemy ORM models
β β βββ __init__.py
β β βββ user.py
β β βββ book.py
β β βββ borrow.py
β βββ schemas/ # Pydantic request/response models
β β βββ __init__.py
β β βββ user.py
β β βββ book.py
β β βββ borrow.py
β βββ routers/ # API endpoint definitions
β β βββ __init__.py
β β βββ auth.py # Register/login endpoints
β β βββ books.py # Book CRUD (admin-protected)
β β βββ borrows.py # Borrow/return logic
β βββ services/ # Business logic layer
β βββ __init__.py
β βββ auth_service.py # Password hashing, JWT creation
β βββ borrow_service.py # Borrow validation, fine calculation
βββ tests/ # Automated test suite
β βββ __init__.py
β βββ conftest.py # Pytest fixtures (DB, client, tokens)
β βββ test_auth.py
β βββ test_books.py
β βββ test_borrows.py
β βββ test_caching.py
βββ monitoring/ # Observability configs
β βββ prometheus.yml # Prometheus scrape config
β βββ grafana-dashboard.json # Pre-built Grafana dashboard
βββ .env.example # Environment variable template
βββ .gitignore # Git ignore rules
βββ alembic.ini # Alembic migration config
βββ docker-compose.yml # Multi-service orchestration
βββ Dockerfile # Container build instructions
βββ pytest.ini # Pytest configuration
βββ requirements.txt # Python dependencies
βββ README.md # This file
main β Production releases (protected)
β
develop β Integration branch (protected)
β
feature/* β Individual work (e.g., feature/auth, feature/caching)
β
hotfix/* β Critical production fixes
# Format: <type>(<scope>): <description>
feat(auth): add JWT token refresh endpoint
fix(borrows): resolve timezone issue in fine calculation
docs(readme): update API endpoint table
test(caching): add cache invalidation unit tests
refactor(models): extract common timestamp mixin- Create feature branch from
develop:git checkout -b feature/your-task - Implement, test locally:
pytest tests/ -v - Commit with conventional message:
git commit -m "feat(scope): description" - Push & open PR to
developwith:- β Description of changes
- β Test coverage report
- β Screenshots (if UI changes)
- Peer review β CI checks pass β Merge via squash
- Release: Merge
developβmainwith version tag
π Academic Note: Each team member's contributions are traceable via distinct feature branches, atomic commits, and PR descriptions. No bulk/last-minute merges.
| Issue | Solution |
|---|---|
Redis Connection Error |
Ensure Redis is running: `docker ps |
Database lock / SQLite error |
Delete library.db and *.db-journal files, then restart server |
JWT validation failed |
Verify SECRET_KEY matches in .env and token generation |
Cache not invalidating |
Check Redis key patterns in redis_client.py; ensure cache_invalidate() is called on writes |
Tests failing on DB setup |
Run pytest tests/ -v --tb=short for detailed traceback; ensure conftest.py fixtures are async-compatible |
Prometheus metrics not showing |
Verify /metrics endpoint returns 200; check prometheus.yml scrape config |
This project is developed for educational purposes as part of the Advanced Software Engineering Capstone program.
πΉ License: MIT License β See LICENSE file for details
πΉ Academic Integrity: This codebase is original work by the listed team members. External libraries are properly attributed via requirements.txt.
πΉ Citation: If referencing this project academically, please cite:
@software{LibraryMS2024,
author = {Younan, Youhanna and Reda, Karen and Bassem, Mira and Mohammed, Rawan and Akram, Monica and Tarek, Yousef},
title = {Library Management System API},
year = {2024},
url = {https://github.com/yousefnathan/Project-R-Python}
}We welcome contributions that align with the project's academic and technical goals:
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'feat(scope): add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request to
develop
β PR Requirements:
- Passes all tests:
pytest tests/ -v - Includes new tests for new functionality
- Follows existing code style (Ruff/Black formatting)
- Updates documentation if API changes