flask-smorest-api
Skill1 starsChanged 7 days agoMITView on GitHub
Set up Flask REST API with flask-smorest, OpenAPI docs, blueprint architecture, and dataclass models. Use when creating a new Flask API server, building REST endpoints, or setting up a production API.
- Reads credentials
- Installs packages
In this repository14 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- aegis-nextjs-frontendSkill
- byteforge-loki-loggingSkill
- byteforge-prometheus-metricsSkill
- flask-docker-deploymentSkill
- flask-telegram-botSkill
- gatekeeper-nginx-setupSkill
- mcp-async-upgradeSkill
- mcp-docker-deploymentSkill
- mcp-server-nginx-gatekeeperSkill
- postgres-setupSkill
- python-lib-setupSkill
- python-project-scaffoldSkill
- uv-supply-chain-hardeningSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
26 rules. About 0 are common, 14 are distinctive, and 12 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈14
Distinctive · estimate
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
Use this skill when:
- Distinctive: line 31
IMPORTANT: Before creating files, ask the user these questions:
- Distinctive: line 34
Use this to derive:
- Distinctive: line 52
Create these directories if they don't exist:
- Very specific: line 69
Create {project_name}.py using this template:
- Very specific: line 118
If the project uses the database, validate its settings at the very top of create_app().
- Very specific: line 146
If the user opted in to a Swagger UI docs endpoint, add these three config lines to create_app() immediately after OPENAPI_VERSION, and add …
- Very specific: line 174
If that path 404s, /swagger renders a broken shell.
- Very specific: line 364
CRITICAL: Every handler above starts its except chain with except HTTPException: raise.
- Distinctive: line 378
If the project needs shared services (database, API clients, etc.), create a singleton manager:
- Very specific: line 494
Create or update example.env with required environment variables:
- Very specific: line 540
Create requirements.txt with dependencies (no version pinning):
- Very specific: line 558
Create dev-requirements.txt with test tooling.
- Very specific: line 593
Use pytest.ini, not pyproject.toml: a Flask service usually has no pyproject.toml, and creating one just for pytest config invites …
- Very specific: line 650
Keep the route test above and append this configuration test to tests/test_app.py:
- Distinctive: line 670
Create or update README.md with:
- Distinctive: line 727
If enabled, access Swagger UI at:
- Distinctive: line 755
JSON Responses: - All endpoints return JSON via jsonify()
- Distinctive: line 758
Environment-Based Config: - All secrets via environment variables
- Distinctive: line 767
If database is needed, use postgres-setup skill first:
- Very specific: line 772
Important: When running postgres-setup, also follow its Step 7 ("Create Resilient Database Driver") to scaffold …
- Distinctive: line 782
If publishing as a package, use python-lib-setup skill:
- Distinctive: line 821
If user requests Docker, reference the flask-docker-deployment skill for production-ready containerization with automated versioning and …
- Distinctive: line 827
If you find a bug or stale instruction while using this skill:
- Very specific: line 829
If HiveMake is connected and byteforge-skills-maintainer-agent is available, file a ticket with that agent.
- Distinctive: line 833
If you cannot submit a report, give the human the details and this link.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Flask REST API with flask-smorest Pattern
This skill helps you set up a Flask REST API following a standardized pattern with flask-smorest for OpenAPI documentation, blueprint architecture, and dataclass models for request/response handling.
When to Use This Skill
Use this skill when:
- Starting a new Flask REST API project
- You want automatic OpenAPI/Swagger documentation
- You need a clean, modular blueprint architecture
- You want type-safe data models using dataclasses with to_dict/from_dict patterns
- You're building a production-ready API server
What This Skill Creates
- Main application file - Flask app initialization with flask-smorest
- Blueprint structure - Modular endpoint organization
- Data models - Dataclasses with to_dict/from_dict methods and validation
- Singleton manager pattern - Centralized service/database initialization
- CORS support - Cross-origin request handling
- Requirements files - Runtime dependencies (
requirements.txt) and dev/test tools (dev-requirements.txt) pytest.ini+tests/- pytest scoped totests/with the project root importable, plus a DB-free smoke test
Step 1: Gather Project Information
IMPORTANT: Before creating files, ask the user these questions:
- "What is your project name?" (e.g., "myapp")
- Use this to derive:
- Main module:
{project_name}.py(e.g.,myapp.py) - Port number (suggest based on project, default: 5000)
- Main module:
- Use this to derive:
- "What features/endpoints do you need?" (e.g., "users", "tokens", "orders")
- Each feature will become a blueprint
- "Do you need database integration?" (yes/no)
- If yes, reference the
postgres-setupskill for the database layer. Use its Step 7 ("Create Resilient Database Driver") to scaffoldsrc/{project_name}/database.py— the singleton manager below importsDatabasefrom that exact path. The resilient pattern (pre-ping + retry + mid-flight death detection) is what keeps the Flask process from wedging when Postgres restarts; the naïveThreadedConnectionPoolpattern returns 500s on every request until the process restarts.
- If yes, reference the
- "What port should the server run on?" (default: 5000)
- "Do you want a Swagger UI docs endpoint at
/swagger?" (yes/no — default no)- Default is no. Most internal services (tailnet-only, backend-only, no need for a browsable doc surface) should keep it off — an exposed Swagger UI is attack surface they never asked for, and
flask-smorest'sApi()/Blueprint/abortmachinery all still work without anyOPENAPI_URL_PREFIX/OPENAPI_SWAGGER_UI_*config (as long asAPI_TITLE/API_VERSION/OPENAPI_VERSIONare set, which they are in the base template). - Say yes only if the service genuinely wants a browsable UI (public API, dev-portal, etc.). If yes, you'll be responsible for vendoring
swagger-ui-distassets under/static/swagger-ui/(or an internal mirror) — this skill no longer defaults to a third-party CDN (previous versions pointedOPENAPI_SWAGGER_UI_URLatcdn.jsdelivr.net, which is a supply-chain surface and doesn't work in air-gapped/tailnet-only environments).
- Default is no. Most internal services (tailnet-only, backend-only, no need for a browsable doc surface) should keep it off — an exposed Swagger UI is attack surface they never asked for, and
Step 2: Create Directory Structure
Create these directories if they don't exist:
{project_root}/
├── blueprints/ # Blueprint modules (one per feature)
│ ├── __init__.py
│ └── {feature}.py
├── models/ # Dataclass models with to_dict/from_dict
│ ├── __init__.py
│ └── {feature}.py
├── tests/ # pytest tests (see Step 9b)
│ └── test_app.py
├── pytest.ini # pytest config (see Step 9b)
└── {project_name}.py # Main application fileStep 3: Create Main Application File
Create {project_name}.py using this template:
import os
import logging
from flask import Flask
from flask_cors import CORS
from flask_smorest import Api
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_app():
app = Flask(__name__)
# These three are ALWAYS required by flask-smorest's `Api()`, even
# when no Swagger UI is served — `Api()` refuses to initialize without
# them. They govern the OpenAPI spec object flask-smorest builds in
# memory (used for validation and, if enabled, doc-surface serving).
app.config['API_TITLE'] = '{Project Name} API'
app.config['API_VERSION'] = 'v1'
app.config['OPENAPI_VERSION'] = '3.0.2'
CORS(app)
api = Api(app)
from blueprints.{feature} import blp as {feature}_blp
api.register_blueprint({feature}_blp)
logger.info("Flask app initialized")
return app
if __name__ == '__main__':
port = int(os.environ.get('PORT', {port_number}))
app = create_app()
app.run(host='0.0.0.0', port=port)CRITICAL: Replace:
{Project Name}→ Human-readable project name (e.g., "My App"){project_name}→ Snake case project name (e.g., "myapp"){port_number}→ Actual port number (e.g., 5151){feature}→ Feature name from user's response
Fail fast on missing database config (only if Step 1 Q3 = yes)
If the project uses the database, validate its settings at the very top of create_app(). Add the import with the others at the top of {project_name}.py:
from common import DatabaseConfigand make this the first statement inside create_app():
# Fail fast on a misconfigured deploy. Without this, a missing
# {PROJECT_NAME}_DB_PASSWORD boots "healthy" (/health never touches the DB)
# and only surfaces as 500s on the first DB request. Under gunicorn a
# ValueError here fails the worker boot, so the container exits and
# restart-count alerts fire.
#
# Validates env vars ONLY — it deliberately opens no connection. Do NOT
# replace it with service_manager.get_database(): the driver opens pool
# connections on construction, so a Postgres outage at boot would
# crash-loop the service instead of letting the resilient driver
# (postgres-setup Step 7) reconnect once Postgres is back. It would also
# make the Step 9b smoke test need a live database.
DatabaseConfig.from_env()common.py is created in Step 6.
Swagger UI enrichment (only if the user answered YES to Step 1 Q5)
If the user opted in to a Swagger UI docs endpoint, add these three
config lines to create_app() immediately after OPENAPI_VERSION, and
add the startup-log line inside the __main__ block:
# Only present when Swagger UI is opted in (Step 1 Q5).
# Vendor `swagger-ui-dist` assets under `/static/swagger-ui/` (or an
# internal mirror you control) and set OPENAPI_SWAGGER_UI_URL to the
# path you're serving them from. DO NOT point this at cdn.jsdelivr.net
# or any third-party CDN — prior skill versions did, which was both a
# supply-chain surface and a hard break in air-gapped / tailnet-only
# deployments.
app.config['OPENAPI_URL_PREFIX'] = '/'
app.config['OPENAPI_SWAGGER_UI_PATH'] = '/swagger'
app.config['OPENAPI_SWAGGER_UI_URL'] = '/static/swagger-ui/' # TODO: vendorAnd inside the if __name__ == '__main__': block, immediately after
app = create_app():
logger.info(f"Swagger UI: http://localhost:{port}/swagger")Vendoring reminder: whichever path you point OPENAPI_SWAGGER_UI_URL
at, that path must actually serve the swagger-ui-dist JS/CSS bundle. The
usual approach is pip install swagger-ui-bundle (or copy the files from
the npm package) and configure Flask to serve them from /static/swagger-ui/.
If that path 404s, /swagger renders a broken shell.
Step 4: Create Data Models
For each feature, create a models file with dataclasses that include to_dict and from_dict methods:
File: models/{feature}.py
from dataclasses import dataclass
from typing import Optional
@dataclass
class {Feature}:
"""
{Feature} data model.
Includes validation in from_dict and serialization via to_dict.
"""
id: str
name: str
created_at: int
updated_at: Optional[int] = None
def to_dict(self) -> dict:
"""Serialize to dictionary for JSON response."""
return {
"id": self.id,
"name": self.name,
"created_at": self.created_at,
"updated_at": self.updated_at
}
@classmethod
def from_dict(cls, data: dict) -> "{Feature}":
"""
Create instance from dictionary with validation.
Args:
data: Dictionary with {feature} data
Returns:
{Feature} instance
Raises:
ValueError: If required fields are missing or invalid
"""
if "id" not in data:
raise ValueError("id is required")
if "name" not in data:
raise ValueError("name is required")
if "created_at" not in data:
raise ValueError("created_at is required")
return cls(
id=str(data["id"]),
name=str(data["name"]),
created_at=int(data["created_at"]),
updated_at=int(data["updated_at"]) if data.get("updated_at") else None
)CRITICAL: Replace:
{Feature}→ PascalCase feature name (e.g., "TradableToken"){feature}→ Snake case feature name (e.g., "tradable_token")
Step 5: Create Blueprint Files
For each feature/endpoint, create a blueprint file:
File: blueprints/{feature}.py
Every blueprint uses url_prefix='/api'. This is a required convention, not a default to customize: nginx and request routing rely on one shared prefix across projects.
import logging
from flask import request, jsonify
from flask.views import MethodView
from flask_smorest import Blueprint, abort
from werkzeug.exceptions import HTTPException
from models.{feature} import {Feature}
logger = logging.getLogger(__name__)
blp = Blueprint('{feature}', __name__, url_prefix='/api', description='{Feature} API')
@blp.route('/{feature}')
class {Feature}ListResource(MethodView):
def get(self):
"""Get list of {feature}s."""
try:
limit = request.args.get('limit', 100, type=int)
offset = request.args.get('offset', 0, type=int)
# TODO: Implement logic to fetch {feature}s
items = []
return jsonify({
"data": [item.to_dict() for item in items],
"limit": limit,
"offset": offset
})
except HTTPException:
raise
except ValueError as e:
logger.warning(f"Bad request: {e}")
abort(400, message=str(e))
except Exception as e:
logger.exception(f"Error fetching {feature}s: {e}")
abort(500, message="Internal server error")
def post(self):
"""Create a new {feature}."""
try:
data = request.get_json()
if not data:
abort(400, message="Request body is required")
item = {Feature}.from_dict(data)
# TODO: Implement logic to save {feature}
return jsonify(item.to_dict()), 201
except HTTPException:
raise
except ValueError as e:
logger.warning(f"Validation error: {e}")
abort(400, message=str(e))
except Exception as e:
logger.exception(f"Error creating {feature}: {e}")
abort(500, message="Internal server error")
@blp.route('/{feature}/<string:item_id>')
class {Feature}Resource(MethodView):
def get(self, item_id: str):
"""Get a single {feature} by ID."""
try:
# TODO: Implement logic to fetch {feature} by ID
item = None
if not item:
abort(404, message=f"{Feature} not found: {item_id}")
return jsonify(item.to_dict())
except HTTPException:
raise
except Exception as e:
logger.exception(f"Error fetching {feature}: {e}")
abort(500, message="Internal server error")
def put(self, item_id: str):
"""Update a {feature}."""
try:
data = request.get_json()
if not data:
abort(400, message="Request body is required")
# TODO: Implement logic to update {feature}
return jsonify({"message": "Updated"})
except HTTPException:
raise
except ValueError as e:
logger.warning(f"Validation error: {e}")
abort(400, message=str(e))
except Exception as e:
logger.exception(f"Error updating {feature}: {e}")
abort(500, message="Internal server error")
def delete(self, item_id: str):
"""Delete a {feature}."""
try:
# TODO: Implement logic to delete {feature}
return jsonify({"message": "Deleted"})
except HTTPException:
raise
except Exception as e:
logger.exception(f"Error deleting {feature}: {e}")
abort(500, message="Internal server error")CRITICAL: Replace:
{Feature}→ PascalCase feature name (e.g., "TradableToken"){feature}→ Snake case feature name (e.g., "tradable_token")
CRITICAL: Every handler above starts its except chain with
except HTTPException: raise. This arm is REQUIRED — flask_smorest.abort()
raises werkzeug.exceptions.HTTPException, which is an Exception subclass,
so any abort(400, ...) / abort(404, ...) fired inside a try block will
otherwise be swallowed by the terminal except Exception arm and re-emitted
as abort(500, "Internal server error") — clients get 500s for their own bad
input, and logs fill with spurious logger.exception stack traces for
routine validation. When adding a new handler, keep this arm as the FIRST
except clause. Move unconditional aborts (e.g., if not data: abort(400))
outside the try when possible; the arm still catches the inevitable in-try
aborts (if not item: abort(404) after a lookup).
Step 6: Create Common Singleton Manager (If Needed)
If the project needs shared services (database, API clients, etc.), create a singleton manager:
File: common.py
"""
Singleton manager for shared service instances.
Provides centralized initialization of database connections, API clients,
and other shared resources.
"""
import os
import logging
from dataclasses import dataclass
logger = logging.getLogger(__name__)
@dataclass
class DatabaseConfig:
"""Database connection settings, read from the same env vars postgres-setup provisions against."""
host: str
port: int
name: str
user: str
password: str
@classmethod
def from_env(cls) -> "DatabaseConfig":
"""
Read and validate database settings from the environment.
Opens NO connection, so it is safe to call at startup even while
Postgres is down. create_app() calls it to fail fast on a
misconfigured deploy; get_database() calls it to build the driver.
Raises:
ValueError: If the password is missing/empty or the port is not an integer
"""
# `{PROJECT_NAME}_DB_PORT` MUST be read here — the setup script
# (postgres-setup Step 4) provisions against it, and skipping it here
# silently dials 5432 (Docker-mapped 5433 or multi-instance hosts hit
# this). Ticket 881aa10a fix — five env vars, four surfaces, one invariant.
password = os.environ.get('{PROJECT_NAME}_DB_PASSWORD')
if not password:
raise ValueError("{PROJECT_NAME}_DB_PASSWORD environment variable required")
return cls(
host=os.environ.get('{PROJECT_NAME}_DB_HOST', 'localhost'),
port=int(os.environ.get('{PROJECT_NAME}_DB_PORT', '5432')),
name=os.environ.get('{PROJECT_NAME}_DB_NAME', '{project_name}'),
user=os.environ.get('{PROJECT_NAME}_DB_USER', '{project_name}'),
password=password,
)
class ServiceManager:
"""
Singleton manager for shared service instances.
Ensures only one instance of each service is created and reused
across all blueprints.
"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(ServiceManager, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
# Initialize services
self._db = None
self._initialized = True
logger.info("ServiceManager initialized")
def get_database(self):
"""
Get database connection instance.
Returns:
Database connection instance (lazy initialization)
"""
if self._db is None:
# Import database driver — see postgres-setup Step 7 for the
# resilient implementation (pre-ping + retry; survives PG restart).
from src.{project_name}.database import Database
# Connection settings come from DatabaseConfig so startup
# validation (create_app) and the real driver read identical values.
config = DatabaseConfig.from_env()
self._db = Database(config.host, config.name, config.user, config.password, db_port=config.port)
logger.info("Database connection initialized")
return self._db
# Global singleton instance
service_manager = ServiceManager()CRITICAL: Replace:
{PROJECT_NAME}→ Uppercase project name (e.g., "MYAPP"){project_name}→ Snake case project name (e.g., "myapp")
Step 7: Create Environment Configuration
File: example.env
Create or update example.env with required environment variables:
# Server Configuration
PORT={port_number}
DEBUG=False
# Database Configuration (if applicable)
{PROJECT_NAME}_DB_HOST=localhost
{PROJECT_NAME}_DB_PORT=5432
{PROJECT_NAME}_DB_NAME={project_name}
{PROJECT_NAME}_DB_USER={project_name}
{PROJECT_NAME}_DB_PASSWORD=your_password_here
# Optional: CORS Configuration
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080CRITICAL: Replace:
{port_number}→ Actual port number (e.g., 5151){PROJECT_NAME}→ Uppercase project name (e.g., "MYAPP"){project_name}→ Snake case project name (e.g., "myapp")
File: .env (gitignored)
Instruct the user to copy example.env to .env and fill in actual values:
# Copy example.env to .env and update with actual values
cp example.env .envUpdate .gitignore
Merge .env into .gitignore if not already present. When the venv lives at the project root (python3 -m venv .), also add /.lock: uv uses it for environment locking. Preserve the existing ignore rules; uv.lock is a separate dependency lockfile and must remain trackable.
# Environment variables
.env
# uv writes this transient lock into the venv root (= project root here).
/.lockStep 8: Create Requirements File
Create requirements.txt with dependencies (no version pinning):
# Flask and API framework
Flask
flask-smorest
flask-cors
# Environment variable management
python-dotenv
# Production server (optional but recommended)
gunicorn
# Database (if needed)
psycopg2-binaryCreate dev-requirements.txt with test tooling. Keep it separate from requirements.txt: a service's requirements.txt is what gets installed into its Docker image, and test tools don't belong there (same split as the uv-supply-chain-hardening skill uses for apps):
pytestStep 9: Create Blueprints init.py
Create blueprints/__init__.py:
"""
Blueprint modules for {Project Name} API.
Each blueprint represents a distinct feature or resource endpoint.
"""Step 9b: Create pytest.ini and a Smoke Test
File: pytest.ini
[pytest]
# REQUIRED. The venv lives at the project root (bin/, lib/, include/), so a
# bare `pytest` otherwise collects the venv's own site-packages tests and dies
# on third-party collection errors before running a single project test.
testpaths = tests
# REQUIRED. The app modules ({project_name}.py, blueprints/, models/, common.py)
# live at the project root, not in an installed package. pytest's default
# import mode puts tests/ on sys.path, not the root, so without this line
# `import {project_name}` fails with ModuleNotFoundError.
pythonpath = .Use pytest.ini, not pyproject.toml: a Flask service usually has no pyproject.toml, and creating one just for pytest config invites confusion with the library skills.
File: tests/conftest.py
Build one app per test session. This also works when byteforge-prometheus-metrics is added: repeated successful factory calls would register app_info twice in the global Prometheus registry. Keep PROMETHEUS_MULTIPROC_DIR unset before starting these single-process tests. The app module must not also instantiate the factory at import time; if it intentionally exports an already-created app, return that instance from the fixture instead.
import pytest
from flask import Flask
from {project_name} import create_app
@pytest.fixture(scope='session')
def app() -> Flask:
app = create_app()
app.config['TESTING'] = True
return appFile: tests/test_app.py
from flask import Flask
def test_create_app_registers_routes(app: Flask) -> None:
# Checks wiring only — no requests, so TODO handlers cannot touch Postgres.
# Keep create_app() itself free of live-DB requirements.
rules = [rule.rule for rule in app.url_map.iter_rules()]
assert '/api/{feature}' in rulesCRITICAL: Replace:
{project_name}→ Snake case project name (e.g., "myapp"){feature}→ the first feature's snake case name (e.g., "tradable_token")
If the project uses the database (Step 3's fail-fast check is present), use this version of tests/conftest.py instead. Settings are validated without opening a connection, so a dummy password is enough — no Postgres needed. MonkeyPatch.context() works at session scope and restores the environment at teardown; the built-in monkeypatch fixture is function-scoped and cannot be injected into this fixture.
from collections.abc import Iterator
import pytest
from flask import Flask
from {project_name} import create_app
@pytest.fixture(scope='session')
def app() -> Iterator[Flask]:
with pytest.MonkeyPatch.context() as env:
env.setenv('{PROJECT_NAME}_DB_PASSWORD', 'test-not-a-real-password')
app = create_app()
app.config['TESTING'] = True
yield appKeep the route test above and append this configuration test to tests/test_app.py:
import pytest
from {project_name} import create_app
def test_create_app_fails_fast_without_db_password(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv('{PROJECT_NAME}_DB_PASSWORD', raising=False)
with pytest.raises(ValueError, match='{PROJECT_NAME}_DB_PASSWORD'):
create_app()This direct factory call is safe because Step 3 validates settings before metrics registration. Match the configuration error so a duplicate-metric error cannot make the test pass accidentally. All tests needing a successfully initialized app should request the shared fixture. Share the app, but create a fresh test client per test to keep cookies/session state isolated.
Also replace {PROJECT_NAME} → uppercase project name (e.g., "MYAPP").
Step 10: Document Usage
Create or update README.md with:
Setup
# Copy example environment file
cp example.env .env
# Edit .env and fill in actual values
# Create and activate the virtual environment (at the project root)
python3 -m venv .
source bin/activate
# Then install dependencies
pip install --upgrade -r requirements.txt
pip install --upgrade -r dev-requirements.txtRunning Tests
source bin/activate && pytestpytest.ini scopes collection to tests/ and puts the project root on the import path, so a bare pytest from the project root works.
Running the Server
# Development mode
python {project_name}.py
# Production mode with Gunicorn
gunicorn -w 4 -b 0.0.0.0:{port_number} '{project_name}:create_app()'Environment Variables
Copy example.env to .env and configure:
Server Configuration:
PORT- Server port (default: {port_number})DEBUG- Enable debug mode (default: False)
Database (if applicable):
{PROJECT_NAME}_DB_HOST- Database host (default: localhost){PROJECT_NAME}_DB_PORT- Database port (default: 5432){PROJECT_NAME}_DB_NAME- Database name (default: {project_name}){PROJECT_NAME}_DB_USER- Database user (default: {project_name}){PROJECT_NAME}_DB_PASSWORD- Database password (REQUIRED — the app refuses to start without it)
The app checks that these are set when it starts, but does not connect to Postgres until the first request that needs it — so it starts even if Postgres is temporarily down, and fails immediately if the password is missing.
API Documentation
(Only applicable if Swagger UI was opted in during Step 1 Q5.)
If enabled, access Swagger UI at:
http://localhost:{port_number}/swaggerRequires swagger-ui-dist assets to be served under
OPENAPI_SWAGGER_UI_URL; see the Swagger UI enrichment note in Step 3.
Design Principles
This pattern follows these principles:
Architecture:
- Blueprint Organization - Modular endpoint organization, one blueprint per feature
- MethodView Classes - Class-based views for HTTP methods (get, post, put, delete)
- Separation of Concerns - Routes, models, and business logic separated
- Singleton Manager - Centralized service initialization prevents duplicate connections
- Application Factory -
create_app()pattern for testing and flexibility
Data Models:
- Dataclasses - Type-safe data models using Python dataclasses
- to_dict/from_dict - Consistent serialization and deserialization pattern
- Validation in from_dict - Input validation with clear error messages
- Self-Contained - Each model handles its own validation and serialization
API Design:
- OpenAPI/Swagger - Automatic documentation via flask-smorest
- Error Handling - Consistent error responses with proper HTTP status codes
- CORS Support - Cross-origin requests for frontend consumption
- JSON Responses - All endpoints return JSON via jsonify()
Best Practices:
- Environment-Based Config - All secrets via environment variables
- Logging - Structured logging throughout
- Idempotent Operations - Safe to call multiple times
- Production Ready - Gunicorn support out of the box
- Testing Friendly - Application factory enables easy testing
Integration with Other Skills
Database Integration
If database is needed, use postgres-setup skill first:
User: "Set up postgres database for my project"Important: When running postgres-setup, also follow its Step 7 ("Create Resilient Database Driver") to scaffold src/{project_name}/database.py. The singleton manager above imports Database from that path. Skipping Step 7 and using a naïve ThreadedConnectionPool will wedge the Flask process every time Postgres restarts.
Then reference the database in your blueprints via the singleton manager:
from common import service_manager
db = service_manager.get_database()Package Structure
If publishing as a package, use python-lib-setup skill:
User: "Set up Python package for PyPI"Example Usage in Claude Code
User: "Set up Flask API server for my project"
Claude: "What is your project name?"
User: "crypto-tracker"
Claude: "What features/endpoints do you need?"
User: "prices, tokens, portfolio"
Claude: "Do you need database integration?"
User: "yes"
Claude: "What port should the server run on?"
User: "8080"
Claude:
- Creates
crypto_tracker.pywith Flask app - Creates
models/directory with dataclass models:prices.py,tokens.py,portfolio.py
- Creates
blueprints/directory with endpoint handlers:prices.py,tokens.py,portfolio.py
- Creates
common.pywith ServiceManager singleton - Creates
requirements.txtwith dependencies anddev-requirements.txtwith pytest - Creates
pytest.iniandtests/test_app.py(DB-free smoke test) - Documents environment variables needed
- Provides startup and test instructions
Optional: Docker Support
If user requests Docker, reference the flask-docker-deployment skill for production-ready containerization with automated versioning and health checks.
Reporting problems with this skill
If you find a bug or stale instruction while using this skill:
- If HiveMake is connected and
byteforge-skills-maintainer-agentis available, file a ticket with that agent. Usediscover_agentsto find its project ID. - Otherwise, open an issue at GitHub Issues. If you cannot submit a report, give the human the details and this link.
Include the skill name, plugin version if known, the instruction followed, expected and actual results, relevant errors/runtime versions, and any fix or workaround that worked. Omit credentials and private data.
Address the user's immediate problem first; report the skill defect afterward.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

