RESTful API built with FastAPI for book management, backed by AWS DynamoDB and deployable via Serverless Framework on AWS Lambda.
- Python 3.14 with FastAPI + Pydantic 2
- AWS DynamoDB (local via Docker or cloud)
- AWS Lambda + API Gateway via Serverless Framework
- uv — package manager
- ruff — linter & formatter
- pytest + pytest-cov — testing with ≥90% coverage gate
- Python 3.14
- uv
- Docker (for DynamoDB Local)
- Serverless Framework v4 (
npm i -g serverless) - AWS CLI configured (for deployment)
make setup # Installs Python deps (via uv) and pre-commit hooks# Start DynamoDB Local container (port 18749)
make db
# Create .env pointing to the local instance
echo "DYNAMODB_ENDPOINT=http://localhost:18749" > .env
# Create the books table in DynamoDB Local
make db-initDynamoDB Local runs in-memory — data is wiped on container restart. No real AWS credentials are required; the init script supplies dummy values automatically.
Requires the AWS CLI configured with valid credentials (aws configure).
# .env — do NOT set DYNAMODB_ENDPOINT (app uses the real AWS endpoint)
echo "AWS_REGION=ap-southeast-1" > .env
# Create the books table in AWS
make db-initmake dev # http://localhost:9876| Variable | Default | Description |
|---|---|---|
DYNAMODB_ENDPOINT |
(unset — uses AWS) | Set to http://localhost:18749 for DynamoDB Local |
ENVIRONMENT |
dev |
Runtime environment — auto-prefixes table names ({env}-books) |
AWS_REGION |
ap-southeast-1 |
AWS region for DynamoDB |
# Run all tests (unit + integration)
make test
# Run unit tests only
make test-unit
# Run integration tests only
make test-integration
# Run tests with 90% coverage gate
make coverageCurrent coverage: 100% (55 tests)
All /api/books endpoints require authentication via Bearer token.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/auth/login |
No | Login, returns access token |
POST |
/api/books |
Yes | Create a book |
GET |
/api/books |
Yes | List books (cursor-paginated) |
GET |
/api/books/{id} |
Yes | Get a book by ID |
DELETE |
/api/books/{id} |
Yes | Delete a book |
# Login
curl -s -X POST http://localhost:9876/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin123"}'
# Create book (use token from login response)
curl -X POST http://localhost:9876/api/books \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"id": "/books/id1",
"author": "/authors/id1",
"name": "Fancy Tech",
"note": "Awesome book for beginners in Fancy.",
"serial": "C040102"
}'
# → 201 Created
# Get book
curl http://localhost:9876/api/books/id1 \
-H "Authorization: Bearer <token>"
# → 200 OK with book JSONDefault credentials: admin / admin123
| Status | When |
|---|---|
400 Bad Request |
Missing required fields in the payload |
401 Unauthorized |
Missing or invalid Bearer token |
404 Not Found |
Book ID does not exist |
500 Internal Server Error |
Unexpected server-side error |
Open http://localhost:9876 in a browser for a single-page client that supports login/logout, listing books, viewing details, creating, and deleting books.
├── src/
│ ├── main.py # FastAPI app + Lambda handler (mangum)
│ ├── config.py # Pydantic settings
│ ├── core/
│ │ ├── exceptions.py # Custom exception classes
│ │ └── aws.py # DynamoDB resource factory
│ ├── auth/
│ │ └── token_store.py # Token-based authentication
│ ├── schemas/
│ │ ├── auth.py # Auth-related schemas
│ │ └── books.py # Book-related schemas
│ ├── middleware/
│ │ ├── rate_limit.py # Rate limit middleware
│ │ └── rate_limiter.py # RateLimiter implementation
│ ├── routes/
│ │ ├── auth.py # Login endpoint
│ │ └── books.py # Book CRUD endpoints
│ └── services/
│ └── book_service.py # DynamoDB operations
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── test_auth.py
│ ├── test_config.py
│ ├── test_exceptions.py
│ ├── test_main.py
│ ├── test_models.py
│ ├── test_routes/ # Route-level tests
│ ├── test_services/ # Service-level tests
│ └── integration/ # Integration tests
├── static/
│ └── index.html # Web UI client
├── serverless.yml # AWS Lambda deployment config
├── pyproject.toml # Project metadata + dependencies
├── Makefile # Developer task runner
└── docker-compose.yml # DynamoDB Local
make deploy # Deploy to AWS (dev stage)
make deploy-prod # Deploy to AWS (production)
make remove # Remove deployed AWS resources