Full Stack CRUD API¶
Trigger: HTTP | State: stateless | Guarantee: request-response | Difficulty: intermediate | Showcase: Full Toolkit
Overview¶
This showcase recipe demonstrates a complete REST CRUD API implemented in
examples/apis-and-ingress/full_stack_crud_api/.
It combines the Azure Functions Python DX Toolkit packages into one runnable Azure Functions Python v2 app:
azure-functions-db-python, azure-functions-validation-python, azure-functions-openapi-python,
azure-functions-logging-python, plus workflow guidance for azure-functions-doctor-python and
azure-functions-scaffold-python.
The sample exposes a full items resource with GET list pagination, GET by id, POST, PUT, and
DELETE. It is designed as the canonical “everything working together” ingress sample for toolkit users
who want one recipe that shows request contracts, OpenAPI metadata, structured logs, SQLAlchemy models,
and local project setup in one place.
Integration Matrix¶
- db:
azure-functions-db-pythonmanages the shared SQLAlchemy engine lifecycle. - validation:
azure-functions-validation-pythonenforces query and body contracts. - openapi:
azure-functions-openapi-pythondocuments the CRUD endpoints. - logging:
azure-functions-logging-pythonemits structured request telemetry. - doctor:
azure-functions-doctor-pythonis the recommended pre-deploy health check. - scaffold:
azure-functions-scaffold-pythoncan generate the starting project structure.
When to Use¶
- You want one end-to-end sample that demonstrates the full toolkit on a realistic HTTP API.
- You need synchronous CRUD over relational data with a compact SQLAlchemy model.
- You want decorator-based validation and OpenAPI metadata on the same functions.
- You want structured logs and reusable DB wiring without building a large framework layer.
When NOT to Use¶
- You need async workflows, long-running jobs, or eventual consistency instead of request-response APIs.
- You need cursor pagination, partial updates, or advanced domain modeling beyond a compact showcase.
- You need repository abstractions, service layering, or multi-table transactions not necessary for a recipe.
- You are building an event-driven ingestion pipeline rather than an HTTP-facing CRUD API.
Architecture¶
flowchart LR
client[Client] --> http[HTTP Trigger]
http --> validation[Validation]
validation --> openapi[OpenAPI Metadata]
openapi --> db[DB Session + SQLAlchemy]
db --> response[JSON Response]
Behavior¶
sequenceDiagram
autonumber
participant Client
participant API as Azure Function
participant Validation
participant DB as SQLAlchemy + Database
Client->>API: POST /api/items
API->>Validation: validate_http(body)
Validation-->>API: Validated payload
API->>DB: INSERT item
DB-->>API: Created row
API-->>Client: 201 created item
Client->>API: GET /api/items?page=1&page_size=20
API->>Validation: validate_http(query)
Validation-->>API: Validated pagination
API->>DB: SELECT page + total count
DB-->>API: Items page
API-->>Client: 200 paginated list
Client->>API: PUT /api/items/1
API->>Validation: validate_http(body)
Validation-->>API: Validated replacement
API->>DB: UPDATE item
DB-->>API: Updated row
API-->>Client: 200 updated item
Client->>API: DELETE /api/items/1
API->>DB: DELETE item
DB-->>API: Commit
API-->>Client: 204 no content
Prerequisites¶
- Python 3.10+
- Azure Functions Core Tools v4
- A SQLAlchemy-compatible database URL in
DB_URL(SQLite works locally by default) - Dependencies from
pyproject.toml, includingazure-functions-db-python,azure-functions-validation-python,azure-functions-openapi-python,azure-functions-logging-python,sqlalchemy, andpydantic
Implementation¶
The example keeps the app compact while still showing the toolkit integration points clearly.
@app.route(route="items", methods=["GET"])
@openapi(summary="List items with pagination", response={200: ItemListResponse}, tags=["items"])
@validate_http(query=PaginationQuery, response_model=ItemListResponse)
def list_items(req: func.HttpRequest, query: PaginationQuery) -> func.HttpResponse:
...
Key implementation details:
- Canonical decorator order:
@app.route→@openapi→@validate_http. - db:
EngineProvidercreates one shared engine andsessionmakerprovides short-lived sessions. - validation: the list endpoint validates pagination query parameters and the create/update endpoints validate JSON bodies before database work starts.
- openapi: each CRUD route publishes summary, request-body, and response metadata.
- logging: every operation emits structured fields like
operation,item_id,page, andtotal. - SQLAlchemy model:
models.pydefines theItementity and timestamp fields. - doctor: run
azure-functions-doctor-pythonbefore shipping to catch missing settings or packaging drift. - scaffold: use
azure-functions-scaffold-pythonto bootstrap a new Function App, then apply this recipe.
CRUD Surface¶
| Method | Route | Purpose |
|---|---|---|
GET |
/api/items |
List items with page and page_size |
GET |
/api/items/{id} |
Fetch one item |
POST |
/api/items |
Create an item |
PUT |
/api/items/{id} |
Replace an existing item |
DELETE |
/api/items/{id} |
Delete an item |
Project Structure¶
examples/apis-and-ingress/full_stack_crud_api/
├── function_app.py
├── host.json
├── local.settings.json.example
├── models.py
├── README.md
└── pyproject.toml
Configuration¶
Set these values in local.settings.json:
| Setting | Purpose |
|---|---|
AzureWebJobsStorage |
Azure Functions runtime storage |
FUNCTIONS_WORKER_RUNTIME |
Must be python |
DB_URL |
SQLAlchemy connection string for the shared engine |
Example:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "python",
"DB_URL": "sqlite:///./items.db"
}
}
Run Locally¶
cd examples/apis-and-ingress/full_stack_crud_api
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp local.settings.json.example local.settings.json
func start
The sample auto-creates the items table on startup for the default SQLite configuration.
Expected Output¶
POST /api/items {"name":"Widget","description":"Starter item","price":9.99}
-> 201 {"id":1,"name":"Widget","description":"Starter item","price":9.99,...}
GET /api/items?page=1&page_size=20
-> 200 {
"page": 1,
"page_size": 20,
"total": 1,
"items": [
{"id":1,"name":"Widget","description":"Starter item","price":9.99,...}
],
"next_link": null
}
DELETE /api/items/1
-> 204
Production Considerations¶
- Pagination: offset pagination is simple and good for admin-style APIs, but cursor pagination is more stable for very large tables.
- Validation bounds: keep
page_sizecapped and body fields bounded to avoid oversized requests. - Error handling: extend the sample with standardized 404/409/problem-details responses if your API contract requires them.
- Observability: keep structured logging fields consistent so CRUD operations are easy to trace in Application Insights.
- Database portability: the local SQLite setup maps cleanly to Azure SQL or other SQLAlchemy-supported backends.
- Project hygiene: run
azure-functions-doctor-pythonduring CI or before deployment to detect environment issues early.