AI Campaign API is a production-style backend application for creating marketing campaigns and generating AI-powered campaign content.
The application is designed to demonstrate modern Python backend engineering, including REST API design, layered architecture, PostgreSQL persistence, authentication, asynchronous processing, and LLM integration.
The system will eventually support AI-generated:
- Keywords
- Advertising headlines
- Advertising descriptions
- Campaign content variations
Python 3
Primary programming language.
FastAPI
REST API framework used for routing, dependency injection, request handling, validation, and OpenAPI documentation.
Pydantic
Used for request and response validation.
Pydantic Settings
Used for environment-based application configuration.
PostgreSQL
Primary relational database.
SQLAlchemy 2
ORM and database abstraction layer.
Psycopg
PostgreSQL database driver used by SQLAlchemy.
Alembic
Database schema migration management.
Argon2 / pwdlib
Secure password hashing and verification.
JWT Authentication
Will be used for authenticated API access.
Redis
Will act as the message broker/cache for asynchronous jobs.
Celery
Will process long-running AI generation tasks outside the HTTP request lifecycle.
An LLM provider will be integrated through a dedicated AI service layer.
The LLM integration will be responsible for generating campaign keywords, headlines, descriptions, and other marketing content.
Pytest
Automated unit and integration testing.
HTTPX
Testing FastAPI HTTP endpoints.
Docker / Docker Compose
Containerized local development and deployment.
Client
│
│ HTTP / REST
▼
┌─────────────────┐
│ FastAPI │
│ Routes │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Service Layer │
│ Business Logic │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Repository │
│ Layer │
└────────┬────────┘
│
▼
┌─────────────────┐
│ SQLAlchemy │
└────────┬────────┘
│
Psycopg
│
▼
┌─────────────────┐
│ PostgreSQL │
└─────────────────┘
The application follows a layered architecture:
API → Service → Repository → ORM → Database
Each layer has a separate responsibility.
ai-campaign-api/
│
├── app/
│ │
│ ├── api/
│ │ └── users.py
│ │
│ ├── core/
│ │ ├── config.py
│ │ └── security.py
│ │
│ ├── database/
│ │ ├── base.py
│ │ ├── dependencies.py
│ │ └── session.py
│ │
│ ├── models/
│ │ └── user.py
│ │
│ ├── repositories/
│ │ └── user_repository.py
│ │
│ ├── schemas/
│ │ └── user.py
│ │
│ ├── services/
│ │ └── user_service.py
│ │
│ └── main.py
│
├── migrations/
│ └── versions/
│
├── tests/
│
├── .env
├── .env.example
├── .gitignore
├── alembic.ini
├── requirements.txt
└── README.md
As the project grows, additional modules will be introduced for campaigns, authentication, AI generation and background workers.
Location:
app/api/
Responsibilities:
- Receive HTTP requests
- Validate request data
- Inject dependencies
- Call the appropriate service
- Convert application errors into HTTP responses
- Return response schemas
Example:
POST /users
The API layer should not directly execute SQL queries or contain complex business logic.
Location:
app/services/
Responsibilities:
- Business rules
- Application workflows
- Validation beyond request structure
- Coordinating repositories
- Security operations
- Coordinating external services
Example:
When creating a user:
Check whether email exists
↓
Hash password
↓
Create user
The service layer decides what should happen.
Location:
app/repositories/
Responsibilities:
- Database queries
- Creating database records
- Updating records
- Deleting records
- Retrieving records
Example:
UserRepository.get_by_email()
UserRepository.create()
UserRepository.get_by_id()
The repository layer decides how data is stored and retrieved.
Location:
app/models/
Contains SQLAlchemy database models.
Example:
User
Campaign
Generation
These models represent database tables and relationships.
Location:
app/schemas/
Contains Pydantic request and response models.
For example:
UserCreate
UserUpdate
UserResponse
This provides a separation between:
API representation
↓
Pydantic Schema
Database representation
↓
SQLAlchemy Model
Database models should not be exposed directly as unrestricted API responses.
The initial database contains:
users
The project will later introduce:
users
│
│ 1
│
└────────── *
campaigns
│
│ 1
│
└────────── *
generations
Conceptually:
User
│
├── Campaign
│ │
│ ├── Generation
│ ├── Generation
│ └── Generation
│
└── Campaign
│
└── Generation
A user can therefore have multiple campaigns, and each campaign can have multiple AI generation versions.
Alembic manages database schema changes.
The workflow is:
Modify SQLAlchemy model
↓
Generate migration
↓
Review migration
↓
Apply migration
↓
PostgreSQL updated
Example:
alembic revision --autogenerate -m "create users table"
alembic upgrade headDatabase tables should not be manually created for application schema changes.
Current registration architecture:
Client
│
│ POST /users
▼
FastAPI User Route
│
▼
UserService
│
├──── Check email
│
▼
UserRepository
│
▼
PostgreSQL
If the email does not exist:
UserService
│
▼
Hash password
│
▼
Argon2 Hash
│
▼
UserRepository.create()
│
▼
SQLAlchemy
│
▼
PostgreSQL
The raw password is never stored.
Only:
password_hash
is persisted.
FastAPI dependency injection manages database sessions.
HTTP Request
│
▼
get_db()
│
▼
Create SQLAlchemy Session
│
▼
Route
│
▼
Repository
│
▼
Database operation
│
▼
Request finishes
│
▼
Session closed
This prevents routes from manually managing database connection lifecycles.
JWT authentication will be introduced next.
Expected flow:
POST /auth/login
│
▼
Find User
│
▼
Verify Password
│
▼
Create JWT
│
▼
Return Access Token
Protected endpoints will then use:
Authorization:
Bearer <JWT>
The API will decode the token and determine the authenticated user.
After authentication, users will be able to create campaigns.
Example:
POST /campaigns
{
"name": "GreenRide Campaign",
"product": "Electric bicycle subscription",
"target_audience": "Professionals in Paris",
"objective": "Google Ads"
}
Flow:
Authenticated User
│
▼
Campaign API
│
▼
CampaignService
│
▼
CampaignRepository
│
▼
PostgreSQL
A campaign belongs to the authenticated user and can contain multiple generation records.
Authenticated users can manage their own products through /products. Product
records include a required name and optional description, website, and category.
A user can only list, retrieve, update, or delete products they own.
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/products |
Create a product |
GET |
/products |
List the authenticated user's products |
GET |
/products/{product_id} |
Retrieve an owned product |
PUT |
/products/{product_id} |
Replace an owned product's details |
DELETE |
/products/{product_id} |
Delete an owned product |
Requests use a Bearer access token. The products table is created by the
Alembic migration a8c2d91f4b63_create_products_table.py.