A Web Application built with FastAPI.
In this application, registered users will be able to create, update, and delete events.
This project is based on the book "Building Python Web APIs with FastAPI".
Chapters 5, 6, 7, 8, and 9 from the book have been implemented, which cover the development of a Planner Application.
The book covers essential topics for building web APIs with FastAPI, including:
- FastAPI Basics: Setting up FastAPI applications, routing, and request handling
- Pydantic Models: Data validation and serialization using Pydantic schemas
- Database Integration: Connecting to MongoDB and performing CRUD operations
- Authentication & Authorization: Implementing JWT-based authentication, password hashing, and route protection
- Testing: Writing comprehensive tests with pytest and pytest-asyncio
- Dockerization: Containerizing applications with Docker and Docker Compose
- API Documentation: Automatic OpenAPI documentation generation with Swagger and ReDoc
Before setting up and running this project, ensure the following tools and dependencies are installed on your system.
The instructions below are based on Ubuntu/Debian-based distributions.
Once all prerequisites are installed, you can proceed to the next section.
Git is required to clone the repository.
# Install
$ sudo apt install git
# Verify installation
$ git --version# Verify installation
$ python3 --version# Install
$ sudo apt install python3.12-venv# Create virtual environment
$ python3 -m venv .venv
# Activate virtual environment
source .venv/bin/activate
# Install/upgrade pip
(venv) $ python -m ensurepip --upgrade
# Verify pip
(venv) $ python -m pip --version
(venv) $ python -m pipDocker is required to run the application using containers.
# Update system packages
$ sudo apt update
$ sudo apt upgrade -y
# Install required dependencies
$ sudo apt install -y \
ca-certificates \
curl \
gnupg \
lsb-release
# Add Docker’s official GPG key
$ sudo mkdir -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# Add Docker repository
$ echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine
$ sudo apt update
$ sudo apt install -y docker-ce docker-ce-cli containerd.io
#Verify installation
$ docker --version
$ docker run hello-worldCurl is useful for testing API endpoints from the terminal.
# Install
$ sudo apt update
$ sudo apt install curl
# Verify
$ curl --versionCreate the virtual environment:
# Create virtual environment
$ python3 -m venv .venvVerify Docker is installed and running (required for MongoDB):
# Check Docker version
$ docker --version
# Check Docker service status
$ sudo systemctl status docker
# Start Docker if needed
$ sudo systemctl enable --now dockerActivate the virtual environment:
# Activate virtual environment in Linux
$ source .venv/bin/activateInstall project dependencies:
# Install dependencies
(venv) $ python3 -m pip install -r requirements.txtCreate MongoDB container:
# Run MongoDB container
$ sudo docker run -d --name mongodb -p 27017:27017 -v mongodb_data:/data/db mongo:7.0Configure environment variables by creating a .env file in the project root:
DATABASE_URL=mongodb://localhost:27017/plannerStart MongoDB container (if not already running):
# Start MongoDB container
$ sudo docker start mongodb
# Check if the container is running
$ sudo docker psStart the API:
# Run API
(venv) $ python main.pyVirtual environment:
# Deactivate virtual environment
(venv) $ deactivate
# Reactivate when needed
$ source .venv/bin/activateMongoDB container management:
# Check container status (including stopped containers)
$ sudo docker ps -a
# Stop MongoDB container
$ sudo docker stop mongodb
# Restart MongoDB container
$ sudo docker restart mongodb
# View MongoDB logs
$ sudo docker logs mongodb
# Remove MongoDB container (if you want to recreate it)
$ sudo docker rm mongodbClean up Docker containers:
# Stop all running containers
$ sudo docker stop $(sudo docker ps -q)
# Remove specific container by name or ID
$ sudo docker rm container_name_or_id
# Remove multiple containers
$ sudo docker rm mongodb flamboyant_edison reverent_payne
# Remove all stopped containers
$ sudo docker container prune
# Remove all containers (stopped and running)
$ sudo docker rm -f $(sudo docker ps -aq)
# Remove all stopped containers, unused networks, and dangling images
$ sudo docker system prune
# Remove everything (containers, images, volumes, networks)
$ sudo docker system prune -a --volumesAccess MongoDB shell (inside Docker):
Open the MongoDB shell inside the container:
$ sudo docker exec -it mongodb mongoshExample commands to view your data:
# Select the database
test> use planner
# List collections
planner> show collections
# List users
planner> db.users.find({})
# Delete collection
planner> db.users.drop()
# Empty without dropping
planner> db.users.deleteMany({})
# Exit the shell
planner> quitThe application includes a Dockerfile and docker-compose.yml for containerized deployment. This approach packages the application with all its dependencies, ensuring consistency across different environments.
Check your Docker version to determine which Docker Compose command to use:
# Check Docker version
$ docker --version
# Check Docker service status
$ sudo systemctl status docker
# Start Docker if needed
$ sudo systemctl enable --now dockerDocker Compose versions:
- Modern Docker versions (20.10+) include Docker Compose V2 as a built-in plugin (use
docker composewith a space) - Older Docker versions require separate installation with
sudo apt install docker-compose(usedocker-composewith a hyphen)
Important: All commands in this README use docker compose (with a space), which is the modern Docker Compose V2 syntax available in Docker 20.10+. If you have an older Docker version, you'll need to install docker-compose separately and replace docker compose with docker-compose (with a hyphen) in all commands below.
Configure environment variables by creating a .env file in the project root:
DATABASE_URL=mongodb://database:27017/plannerBuild and prepare containers:
$ sudo docker compose buildStart all services (API + MongoDB):
$ sudo docker compose up -dDocker compose commands:
# Stop all services
$ sudo docker compose down
# Stop and remove volumes
$ sudo docker compose down -v
# View logs
$ sudo docker compose logs
# View logs for specific service
$ sudo docker compose logs api
# Follow logs in real-time
$ sudo docker compose logs -f
# Follow logs in real-time for specific service
$ sudo docker compose logs api -f
# Restart services
$ sudo docker compose restart
# View running containers
$ sudo docker compose ps
# Rebuild and start services
$ sudo docker compose up -d --buildRun tests with Docker Compose:
# Run all tests
$ sudo docker compose exec api pytest
# Run tests with verbose output
$ sudo docker compose exec api pytest -v
# Run a specific test file
$ sudo docker compose exec api pytest tests/test_events.py
# Run a specific test function
$ sudo docker compose exec api pytest tests/test_events.py::test_create_event
# Run tests with coverage
$ sudo docker compose exec api coverage run -m pytest
# Display coverage report
$ sudo docker compose exec api coverage reportRun linter with Docker Compose:
# Run Ruff linter
$ sudo docker compose exec api ruff check .
# Run Ruff formatter check
$ sudo docker compose exec api ruff format --check .
# Run Ruff formatter (apply formatting)
$ sudo docker compose exec api ruff format .Clean up Docker Compose resources:
# Remove all stopped containers, networks, and images created by docker compose
$ sudo docker compose down --rmi all
# Remove all stopped containers, networks, images, and volumes
$ sudo docker compose down --rmi all -v
# Remove only specific service containers
$ sudo docker compose rm api
# Force remove containers without confirmation
$ sudo docker compose rm -fFastAPI automatically generates interactive API documentation using Swagger and ReDoc.
Access the interactive API documentation where you can test endpoints directly in your browser:
http://localhost:8080/docs
Access an alternative documentation interface with a clean, three-panel design:
http://localhost:8080/redoc
| Method | Endpoint | Description |
|---|---|---|
| GET | /event/ |
List all events |
| GET | /event/{event_id} |
Retrieve a specific event by ID |
| POST | /event/new |
Create a new event |
| PUT | /event/edit/{event_id} |
Update a specific event by ID |
| DELETE | /event/{event_id} |
Delete a specific event by ID |
| DELETE | /event/ |
Delete all events |
| POST | /user/signup |
Register a new user |
| POST | /user/signin |
Sign in a user |
Request:
curl -X 'POST' \
'http://0.0.0.0:8080/user/signup' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"email": "[email protected]",
"password": "exemplary",
"events": []
}'Response:
{
"message": "User created successfully"
}Request:
curl -X 'POST' \
'http://0.0.0.0:8080/user/signin' \
-H 'accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=&username=Example%40example.com&password=exemplary&scope=&client_id=&client_secret='Response:
{
"access_token": "<JWT_TOKEN>",
"token_type": "Bearer"
}Request:
curl -X 'GET' \
'http://0.0.0.0:8080/event/' \
-H 'accept: application/json'Response:
[]Request:
curl -X 'GET' \
'http://0.0.0.0:8080/event/698a1381a18d400a2e8d5f2e' \
-H 'accept: application/json'Response:
{
"id": "698a1381a18d400a2e8d5f2e",
"title": "FastAPI Book Launch CLI",
"image": "https://linktomyimage.com/image.png",
"description": "We will be discussing the contents of the FastAPI book in this event. Ensure to come with your own copy to win gifts!",
"tags": [
"python",
"fastapi",
"book",
"launch"
],
"location": "Google Meet"
}Request:
curl -X 'POST' \
'http://0.0.0.0:8080/event/new' \
-H 'accept: application/json' \
-H 'Authorization: Bearer <JWT_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"title": "FastAPI Book Launch CLI",
"image": "https://linktomyimage.com/image.png",
"description": "We will be discussing the contents of the FastAPI book in this event. Ensure to come with your own copy to win gifts!",
"tags": [
"python",
"fastapi",
"book",
"launch"
],
"location": "Google Meet"
}'Response:
{
"message": "Event created successfully",
"id": "698b48d7b891862af3a15ac6"
}Request:
curl -X 'DELETE' \
'http://0.0.0.0:8080/event/698b48d7b891862af3a15ac6' \
-H 'accept: application/json' \
-H 'Authorization: Bearer <JWT_TOKEN>'Response:
{
"message": "Event deleted successfully"
}Request:
curl -X 'DELETE' \
'http://0.0.0.0:8080/event/' \
-H 'accept: application/json'Response:
{
"message": "All events deleted successfully"
}Request:
curl -X 'PUT' \
'http://0.0.0.0:8080/event/edit/698b48d7b891862af3a15ac6' \
-H 'accept: application/json' \
-H 'Authorization: Bearer <JWT_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"title": "Event update"
}'Response:
{
"id": "698b48d7b891862af3a15ac6",
"title": "Event update"
}This project uses pytest and pytest-asyncio for testing. All tests are asynchronous and interact with a test database that is cleaned after each test execution.
- pytest: A powerful testing framework that simplifies test writing with fixtures and decorators
- pytest-asyncio: An extension that enables pytest to run async test functions and fixtures
- httpx: Asynchronous HTTP client used to make requests to the FastAPI application during tests
This decorator marks a test function as asynchronous. It allows pytest to run the async function in the event loop.
A fixture is a reusable function that sets up test data or resources before a test runs. It can also clean up after the test completes.
Fixtures have different scopes that determine how long they live:
- scope="function" (default): Creates a new fixture instance for each test function. Use this when you need fresh data for each test. Database cleanup happens after each test.
- scope="module": Creates one fixture instance for all tests in a module. Useful for expensive setups that multiple tests can share (like access tokens).
- scope="session": Creates one fixture instance for the entire test session. Use this for resources needed across all tests, like the event loop.
Tests are configured in pytest.ini:
[pytest]
asyncio_mode = autoThis tells pytest-asyncio to automatically detect and run async fixtures and tests.
If you are using Docker Compose, you can run the tests inside the api container:
# Run all tests
$ sudo docker compose exec api pytest
# Run tests with verbose output
$ sudo docker compose exec api pytest -v
# Run a specific test file
$ sudo docker compose exec api pytest tests/test_events.py
# Run a specific test function
$ sudo docker compose exec api pytest tests/test_events.py::test_create_eventThis project uses Ruff as its linter and code formatter. Ruff is an extremely fast Python linter and formatter written in Rust, designed as a drop-in replacement for tools like Flake8, isort, and Black. It is included in the project dependencies.
You can run the Ruff linter and formatter inside the api container:
# Run Ruff linter
$ sudo docker compose exec api ruff check .
# Run Ruff formatter check
$ sudo docker compose exec api ruff format --check .
# Run Ruff formatter (apply formatting)
$ sudo docker compose exec api ruff format .Use coverage.py to measure code coverage and identify untested parts of your codebase.
# Run tests with coverage
$ sudo docker compose exec api coverage run -m pytest
# Display coverage report in terminal
$ sudo docker compose exec api coverage report
# Generate HTML coverage report
$ sudo docker compose exec api coverage html
# Clear coverage data
$ sudo docker compose exec api coverage eraseThe HTML report provides detailed line-by-line coverage information for each file.
.
├── main.py # Application entry point
├── requirements.txt # Python dependencies
├── pytest.ini # Pytest configuration
├── Dockerfile # Docker image configuration
├── docker-compose.yml # Multi-container Docker configuration
├── README.md # Project documentation
├── auth/ # Authentication module
│ ├── __init__.py
│ ├── authenticate.py # Authentication dependency for route protection
│ ├── hash_password.py # Password hashing and verification functions
│ └── jwt_handler.py # JWT encoding and decoding functions
├── database/ # Database configuration and connections
│ ├── __init__.py
│ └── connection.py # MongoDB connection setup
├── models/ # Data models (Pydantic schemas)
│ ├── __init__.py
│ ├── events.py # Event model definitions
│ └── users.py # User model definitions
├── routes/ # API route handlers
│ ├── __init__.py
│ ├── events.py # Event CRUD endpoints
│ └── users.py # User authentication endpoints (signup/signin)
└── tests/ # Test suite
├── __init__.py
├── conftest.py # Pytest fixtures and configuration
├── test_events.py # Event endpoint tests
└── test_users.py # User endpoint tests