Small backend demo project: a FastAPI JSON API for one-time encrypted secrets. The service lets a client create a secret with a passphrase, receive a secret_key, and reveal the secret only once.
The project is based on a backend test-task idea and expanded with a cleaner layered architecture, async SQLAlchemy, encrypted storage, passphrase hashing, Docker Compose, and tests.
- Create one-time secrets with
POST /generate. - Reveal secrets once with
POST /secrets/{secret_key}. - Store secrets encrypted with Fernet.
- Store passphrases as bcrypt hashes, never as plain text.
- Validate request and response bodies with Pydantic schemas.
- Use async SQLAlchemy with PostgreSQL.
- Keep HTTP, service, repository, database, config, and security code separated.
- Run API and PostgreSQL with Docker Compose.
- Include pytest coverage for security helpers, schemas, and API flow.
- Provide an optional static
index.htmlpage for manual API testing.
- Python 3.14
- FastAPI
- Uvicorn
- SQLAlchemy async
- asyncpg
- PostgreSQL
- Pydantic Settings
- bcrypt
- cryptography/Fernet
- pytest
- Docker Compose
app/
main.py
api/
deps.py
routes/
secrets.py
core/
config.py
exceptions.py
security.py
db/
models.py
session.py
repositories/
secrets.py
schemas/
secrets.py
services/
secrets.py
tests/
test_api_secrets.py
test_schemas.py
test_security.py
Dockerfile
docker-compose.yaml
requirements.txt
requirements-dev.txt
index.html
Layer responsibilities:
api/routeshandles HTTP endpoints and HTTP errors.api/deps.pycontains FastAPI dependencies.schemasdefines request and response models.servicescontains business logic.repositoriescontains direct database operations.dbcontains SQLAlchemy models and session setup.corecontains settings, security helpers, and custom exceptions.
POST /generate
Content-Type: application/jsonRequest:
{
"secret": "launch_discount=17",
"passphrase": "qwerty"
}Response:
{
"secret_key": "generated-secret-key"
}POST /secrets/{secret_key}
Content-Type: application/jsonRequest:
{
"passphrase": "qwerty"
}Response:
{
"secret": "launch_discount=17"
}The same secret cannot be revealed twice. A second reveal returns 404.
Wrong passphrase returns 403.
Start the API and PostgreSQL:
docker compose up --buildOpen API docs:
http://127.0.0.1:8000/docs
Stop services:
docker compose downRemove database data too:
docker compose down -vCreate local .env from .env.example.
For local Python running outside Docker, keep:
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:8291/postgresStart PostgreSQL:
docker compose up -d postgresInstall runtime dependencies:
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txtInstall development dependencies:
pip install -r requirements-dev.txtRun API locally:
uvicorn app.main:app --reloadRun tests:
.\.venv\Scripts\python.exe -m pytestThe static index.html file is only a manual testing helper.
Start a local static server:
python -m http.server 63342Open:
http://127.0.0.1:63342/index.html
- Alembic is included as a dependency, but this demo currently uses SQLAlchemy
create_all()on app startup for simplicity. - TTL cleanup is intentionally not implemented because the task does not require secret expiration.
- Docker Compose uses
postgres:5432inside the Docker network andlocalhost:8291for local host access.