An advanced Flask REST API boilerplate with JWT authentication, PostgreSQL, Redis, background jobs, Mailgun integration and tests.
- Features
- Requirements
- Installation
- Database Schema
- Endpoints
- Validation and Errors
- Background Jobs
- Testing
- JWT authentication (access + refresh tokens)
- Token revocation stored in Redis
- PostgreSQL database with SQLAlchemy and Flask-Migrate
- Background job queue using RQ and Redis
- Email sending via Mailgun API
- API documentation with Swagger UI (via Flask-Smorest) available at
/swagger-ui - Database migrations with Flask-Migrate / Alembic
- Environment variable support via
.env/.flaskenv - Docker and docker-compose setup
- Unit and integration tests with pytest
- Python 3.13
- Flask
- Flask-Smorest
- SQLAlchemy
- Flask-SQLAlchemy
- Flask-Migrate
- Flask-JWT-Extended
- Passlib
- python-dotenv
- Redis
- requests
- rq
- psycopg2
- Docker & Docker Compose
See requirements.txt and requirements-dev.txt.
- Clone repository
git clone https://github.com/mateusz-przybyla/FlaskAPI-Advanced.git
cd FlaskAPI-Advanced- Create virtual environment (Windows Powershell)
py -3 -m venv .venv
.\.venv\Scripts\Activate.ps1- Install dependencies
pip install -r requirements-dev.txt- Copy and configure environment variables
copy .env.example .env (Windows Powershell)
# then edit .env and set your values, e.g.:
# --- Flask / JWT ---
JWT_SECRET_KEY=your_jwt_secret_key
# --- Database (PostgreSQL) ---
DATABASE_URL=postgresql://postgres:password@host.docker.internal:5432/flask-api
DB_USERNAME=postgres
DB_PASSWORD=password
DB_DATABASE=flask-api
# --- Redis ---
REDIS_URL=redis://host.docker.internal:6379
# --- Mailgun ---
MAILGUN_API_KEY=your-mailgun-api-key
MAILGUN_DOMAIN=your-domain.mailgun.orgNote: If you’re using Docker Desktop (on Windows or macOS), host.docker.internal allows containers to communicate with the host machine.
- Start Redis, Database and Worker via Docker
docker compose up -d db redis rq_worker - Initialize database
flask db upgrade- Run the Flask app
flask runApp will start at: http://localhost:5000
- Copy environment variables file
copy .env.example .env (Windows Powershell)
# then edit .env and set your values, e.g.:
# --- Flask / JWT ---
JWT_SECRET_KEY=your_jwt_secret_key
# --- Database (PostgreSQL) ---
DATABASE_URL=postgresql://postgres:password@db:5432/flask-api
DB_USERNAME=postgres
DB_PASSWORD=password
DB_DATABASE=flask-api
# --- Redis ---
REDIS_URL=redis://redis:6379
# --- Mailgun ---
MAILGUN_API_KEY=your-mailgun-api-key
MAILGUN_DOMAIN=your-domain.mailgun.org- Build image and start container
docker compose up -d --build- Run migrations inside container
docker compose exec web flask db upgrade- Check logs
docker compose logs -f- Stop and remove containers
docker compose down-
POST
/register
Register a new user.
Request:{ "username": "mateusz", "email": "user@example.com", "password": "secret123" }
Response:{ "message": "User created successfully." },201 Created
Errors:409 Conflict→ email already exists500 Internal Server Error→ database issue
-
POST
/login
Authenticate user and return tokens.
Request:{ "email": "user@example.com", "password": "secret123" }
Response:{ "access_token": "...", "refresh_token": "..." },200 OK
Errors:401 Unauthorized→ invalid credentials
-
POST
/refresh
Get new access token using refresh token.
Headers:Authorization: Bearer <refresh_token>
Response:{ "access_token": new_token},200 OK
Errors:401 Unauthorized→ expired/invalid/blacklisted refresh token
-
POST
/logout
Revoke current refresh token.
Headers:Authorization: Bearer <refresh_token>
Response:{ "message": "Successfully logged out." },200 OK -
GET
/users/me
Retrieve the profile of the currently authenticated user.
Headers:Authorization: Bearer <access_token>
Response:{ "email": "...", "id": "..." },200 OK
Endpoints for verifying JWT behavior:
- GET
/guest→ open for everyone (no token required) - GET
/protected→ requires valid access token - GET
/fresh-protected→ requires fresh token (i.e. directly from login, not from refresh)
Endpoints for user management:
- GET
/users/<id>
Fetch user by id.
Response:200 OK→ with user data
Errors:404 Not Found→ if user doesnt't exist
- DELETE
/users/<id>
Delete user.
Response:{ "message": "User deleted." },200 OK
Errors:404 Not Found→ if user doesnt't exist500 Internal Server Error→ on database issue
-
Common JWT errors (always return
401 Unauthorized)- Missing token
{ "message": "Request does not contain an access token.", "error": "authorization_required" }- Invalid token
{ "message": "Signature verification failed.", "error": "invalid_token" }- Expired token
{ "message": "The token has expired.", "error": "token_expired" }- Revoked token
{ "message": "The token has been revoked.", "error": "token_revoked" }- Non-fresh token on fresh-only endpoint
{ "message": "The token is not fresh.", "error": "fresh_token_required" } -
Validation errors (
422 Unprocessable Entity)If request body fails schema validation (Marshmallow), errors are returned per field:
{ "email": ["Not a valid email address."], "password": ["Shorter than minimum length 6."] } -
Resource errors
- Duplicate user (
409 Conflict)
{ "message": "A user with that email already exists." }- User not found (
404 Not Found)
{ "message": "User not found." }- Database errors (
500 Internal Server Error)
{ "message": "An error occurred while creating the user." } - Duplicate user (
This template uses a minimal background job setup (no retry logic or advanced error handling) to keep the project simple and easy to extend. The API uses RQ (Redis Queue) to process background tasks - for example: sending emails asynchronously after user registration.
Email-related tasks are enqueued into the emails queue, defined in api/extensions.py.
A dedicated RQ worker listens to this queue and processes incoming jobs.
Email tasks are implemented in api/tasks/email_tasks.py.
- User registers via the
/registerendpoint. - API validates data, saves user in the DB.
- API enqueues email task.
- Job is stored in Redis.
- The running email worker (
workers.email_worker) picks up the job. - Email is rendered with Jinja2 and sent using Mailgun.
Run all tests:
pytest -vRun all tests with coverage:
pytest -v --cov=api tests/Run all tests with coverage via Docker:
docker-compose exec web pytest -v --cov=api tests/Test structure:
tests/unit/→ models, schemas, helperstests/integration/→ auth flow, protected endpoints
