<!-- skill: Flask Microservice | version: 1.0.0 | description: Build Flask microservices with blueprints, JWT, SQLAlchemy and Docker | category: Python & Data -->
## Instructions
- App factory pattern always: `create_app(config)` in `app/__init__.py`, blueprints per resource (`auth_bp`, `items_bp`), config from env classes (`Dev/Prod/TestConfig`).
- Blueprints: `Blueprint("items", __name__, url_prefix="/api/items")`, register in factory; version APIs (`/api/v1/...`) from day one.
- SQLAlchemy via Flask-SQLAlchemy: one `db = SQLAlchemy()` + `db.init_app(app)`; models with `id = db.Column(db.Integer, primary_key=True)`; Alembic/Flask-Migrate for every schema change.
- Validation: marshmallow or pydantic schemas at the boundary — never trust `request.json` directly; return `400 {"errors": ...}` with field-level messages.
- JWT: Flask-JWT-Extended, short access TTL (15 min) + refresh rotation; `@jwt_required()`, `get_jwt_identity()`; store tokens in memory (not localStorage) guidance for frontends.
- Error handlers: `@app.errorhandler(404/400/500)` returning JSON consistently (`{"error": code, "message": ...}`); log tracebacks server-side, never leak internals to clients.
- Config: `SECRET_KEY`, `JWT_SECRET_KEY`, `SQLALCHEMY_DATABASE_URI` from env; `SQLALCHEMY_TRACK_MODIFICATIONS=False`; 12-factor, no config files with secrets.
- Structure: `app/{__init__,models,schemas,routes/,services,extensions}.py` + `migrations/` + `tests/`; services hold business logic, routes stay thin.
- Testing: pytest + Flask test client fixtures (`app.test_client()`), per-test transaction rollback or SQLite `:memory:`; test auth + 401/403 paths explicitly.
- Production server: gunicorn (`gunicorn "app:create_app()" -w 4 -k gthread`), never `app.run()` in prod; `WORKERS = 2*CPU+1` starting point.
- Docker: slim python base, non-root user, `pip install --no-cache-dir`, `HEALTHCHECK CMD curl -f /healthz`, gunicorn as CMD; multi-stage if compiling deps.
- Observability: `/healthz` (deep: DB ping) + `/readyz`, request-id middleware, structured logs (`%(request_id)s`), expose `/metrics` if Prometheus is used.
## Examples
- Items CRUD service: `Item` model, marshmallow schema, `items_bp` with GET/POST/PATCH/DELETE, JWT on writes, Alembic migration, Docker image.
- Auth service: register/login → access+refresh tokens, `@jwt_required` profile route, password hashing with Werkzeug/bcrypt, rate-limit login endpoint.
- Worker-facing API: service key header check, paginated list (`?page&per_page`, max 100), consistent error envelope, gunicorn+Docker deploy.
## Workflows
1. Scaffold factory + extensions + first blueprint + health route.
2. Add model + migration (`flask db migrate/migrate upgrade`) → schema validation.
3. Implement routes (thin) + services (logic) + error handlers.
4. Wire JWT + tests (happy path + 401/400 cases) → green suite.
5. Dockerfile + gunicorn CMD → build, run, hit `/healthz` + versioned endpoints.
6. Ship: env config, log check, migration run in deploy step.
## Templates
- App factory + blueprint:
```python
# app/__init__.py
import os
from flask import Flask, jsonify
from .extensions import db, jwt, migrate
from .routes.items import items_bp
from .routes.auth import auth_bp
def create_app():
    app = Flask(__name__)
    app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
    app.config["JWT_SECRET_KEY"] = os.environ["JWT_SECRET_KEY"]
    app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"]
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    db.init_app(app); jwt.init_app(app); migrate.init_app(app, db)
    app.register_blueprint(items_bp); app.register_blueprint(auth_bp)
    @app.get("/healthz")
    def healthz():
        db.session.execute(db.text("SELECT 1"))
        return {"status": "ok"}
    @app.errorhandler(404)
    def nf(e): return jsonify(error="not_found", message=str(e)), 404
    return app
# app/routes/items.py
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required
from ..extensions import db
from ..models import Item
from ..schemas import ItemSchema
items_bp = Blueprint("items", __name__, url_prefix="/api/v1/items")
schema = ItemSchema()
@items_bp.get("/")
def list_items():
    page = min(int(request.args.get("page", 1)), 1000)
    per = min(int(request.args.get("per_page", 20)), 100)
    q = db.paginate(db.select(Item).order_by(Item.id), page=page, per_page=per)
    return jsonify(items=schema.dump(q.items, many=True), total=q.total)
@items_bp.post("/")
@jwt_required()
def create_item():
    data = schema.load(request.get_json(force=True))
    item = Item(**data); db.session.add(item); db.session.commit()
    return jsonify(schema.dump(item)), 201
```
- Dockerfile:
```dockerfile
FROM python:3.12-slim
RUN useradd -m app && mkdir /app && chown app:app /app
WORKDIR /app
USER app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
HEALTHCHECK --interval=30s CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')"
CMD ["gunicorn", "app:create_app()", "-w", "4", "-b", "0.0.0.0:8000"]
```
## Tools
- SARAH shell_run for flask CLI, pytest, Alembic migrations, docker build/run, gunicorn.
- SARAH file_write for factory/blueprint/model/schema scaffolds; web_fetch for extension docs.
## Advanced
- Pagination + filtering: capped `per_page`, `order_by` whitelist, cursor pagination for large tables; ETag/`If-None-Match` on hot GETs.
- Rate limiting with Flask-Limiter (login + POST routes), CORS via Flask-CORS locked to known origins, security headers via Talisman.
- OpenAPI: flasgger/flask-smorest auto docs from schemas; contract tests against spec in CI.
## Documentation
- https://flask.palletsprojects.com/
- https://flask-sqlalchemy.palletsprojects.com/
- https://flask-jwt-extended.readthedocs.io/
- https://flask-migrate.readthedocs.io/
- https://docs.docker.com/
