Advanced Configuration

When your API grows, you might need tools for shaping traffic, offloading storage and refining responses. Beyond the basics, flarchitect offers several options for these scenarios. The following sections walk through common patterns such as rate limiting, cache configuration and response metadata.

Initialising with optional features

Architect.init_app accepts keyword arguments that toggle optional behaviour like caching, CORS handling and automatic documentation generation.

from flarchitect import Architect

architect = Architect()
architect.init_app(
    app,
    cache={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300},
    enable_cors=True,
    create_docs=True,
)

These keywords mirror their respective API_* configuration values and allow feature flags to be set programmatically during initialisation.

As traffic increases, managing how often clients can hit your API becomes critical.

Full auto mode

flarchitect enables automatic route creation by default. With FULL_AUTO = True the Architect scans your models at startup and registers CRUD routes for each one. This is convenient for new projects but may conflict with custom blueprints or hand-written views.

Disable FULL_AUTO when you need to manage routes manually or only expose a subset of models. After turning it off you must call init_api explicitly to register any automatic routes you still require.

app = Flask(__name__)
app.config["FULL_AUTO"] = False
arch = Architect(app)
arch.init_api(app=app)  # manually trigger route generation

Use this mode when integrating with existing applications or when automatic registration would create unwanted endpoints.

Rate limiting

Rate limits can be applied globally, per HTTP method or per model. For example, to shield a public search endpoint from abuse, you might allow only 100 GET requests per minute.

Global limit

class Config:
    API_RATE_LIMIT = "200 per day"

Model specific

class Book(db.Model):
    __tablename__ = "book"

    class Meta:
        rate_limit = "5 per minute"      # becomes API_RATE_LIMIT

Because limits depend on counting requests, those counts must live somewhere.

Caching backends

flarchitect can cache GET responses when API_CACHE_TYPE is set. If flask-caching is installed, any of its backends (such as Redis or Memcached) may be used. When flask-caching is not available and API_CACHE_TYPE is "SimpleCache", a bundled SimpleCache provides an in-memory fallback. This lightweight cache is cleared when the process restarts and stores data only for the current worker, making it suitable for development or tests rather than production.

Compared to flask-caching it lacks distributed backends, cache invalidation features and the broader decorator API. For deployments with multiple workers or where persistence matters, install flask-caching and configure a production-ready backend instead.

The rate limiter also stores counters in a cache backend. When initialising, flarchitect will automatically use a locally running Memcached, Redis or MongoDB instance. To point to a specific backend, supply a storage URI:

class Config:
    API_RATE_LIMIT_STORAGE_URI = "redis://redis.example.com:6379"

If no backend is available, the limiter falls back to in-memory storage with rate-limit headers enabled by default. In production, you might point to a shared Redis cluster so that multiple application servers enforce the same limits.

You can also cache GET responses by choosing a backend with API_CACHE_TYPE. When flask-caching is installed, set API_CACHE_TYPE to any supported backend such as RedisCache. If the extension is missing, specifying SimpleCache activates a small in-memory cache bundled with flarchitect; any other value will raise a RuntimeError. Use API_CACHE_TIMEOUT to control how long items remain cached.

Example RedisCache setup with a SimpleCache fallback and a cached GET request:

from flask import Flask
from flarchitect import Architect
import time

app = Flask(__name__)
try:
    import flask_caching  # requires installing ``flask-caching``
    app.config["API_CACHE_TYPE"] = "RedisCache"
    app.config["CACHE_REDIS_URL"] = "redis://localhost:6379/0"
except ModuleNotFoundError:
    app.config["API_CACHE_TYPE"] = "SimpleCache"

arch = Architect(app)

@app.get("/time")
def get_time():
    return {"now": time.time()}

with app.test_client() as client:
    client.get("/time")  # first call stored in cache
    client.get("/time")  # second call served from cache

For a runnable example demonstrating cached responses see the caching demo.

After securing throughput, you can also shape what your clients see in each payload.

Response metadata

flarchitect can attach additional metadata to every response. These keys let you toggle each field individually. Including version numbers, for example, helps client developers cache against the correct release:

Key

Default

Effect

API_DUMP_HYBRID_PROPERTIES

True

Include SQLAlchemy hybrid properties in serialised output.

API_DUMP_DATETIME

True

Append the current UTC timestamp as datetime.

API_DUMP_VERSION

True

Embed the API version string as api_version.

API_DUMP_STATUS_CODE

True

Add the HTTP status code to the payload.

API_DUMP_RESPONSE_MS

True

Include elapsed processing time in milliseconds as response_ms.

API_DUMP_TOTAL_COUNT

True

Provide a total_count field for collection endpoints.

API_DUMP_REQUEST_ID

False

Include the per-request correlation identifier as request_id. The header X-Request-ID is always sent.

API_DUMP_NULL_NEXT_URL

True

Show next_url with null when no further page exists.

API_DUMP_NULL_PREVIOUS_URL

True

Show previous_url with null when at the first page.

API_DUMP_NULL_ERRORS

True

Always include an errors field, defaulting to null.

Example

With metadata enabled (defaults):

{
    "data": [...],
    "datetime": "2024-01-01T00:00:00Z",
    "api_version": "0.0.0",
    "status_code": 200,
    "response_ms": 15,
    "total_count": 1,
    "next_url": null,
    "previous_url": null,
    "errors": null
}

Disabling all metadata:

class Config:
    API_DUMP_DATETIME = False
    API_DUMP_VERSION = False
    API_DUMP_STATUS_CODE = False
    API_DUMP_RESPONSE_MS = False
    API_DUMP_TOTAL_COUNT = False
    API_DUMP_NULL_NEXT_URL = False
    API_DUMP_NULL_PREVIOUS_URL = False
    API_DUMP_NULL_ERRORS = False

{
    "data": [...]
}

Nested model creation

Nested writes are disabled by default. Enable them globally with

API_ALLOW_NESTED_WRITES or per model via Meta.allow_nested_writes.

class Config:
    API_ALLOW_NESTED_WRITES = True

class Parent(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    children = db.relationship("Child", back_populates="parent")

    class Meta:
        allow_nested_writes = True

class Child(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    parent_id = db.Column(db.Integer, db.ForeignKey("parent.id"))
    parent = db.relationship("Parent", back_populates="children")

    class Meta:
        allow_nested_writes = True

With this configuration a nested object can be created in the same request:

POST /api/parent
{
    "name": "Jane",
    "children": [{"name": "Junior"}]
}

Depth limits

Once enabled, AutoSchema can deserialise nested relationship data during POST or PUT requests. Each related model must also opt in with Meta.allow_nested_writes and nesting is capped at two levels to avoid unbounded recursion. Any relationships beyond this depth are ignored.

Validation errors

Errors raised within nested objects bubble up under their relationship path. In the following request, the invalid email on the author is reported in the error response:

POST /api/book
{
    "title": "My Book",
    "author": {"email": "not-an-email"}
}

{
    "errors": {"author": {"email": ["Not a valid email address."]}}
}

Example: multiple nested levels

With nested writes enabled you can create several related objects at once, up to two levels deep:

{
    "title": "My Book",
    "isbn": "12345",
    "publication_date": "2024-01-01",
    "author": {
        "first_name": "John",
        "last_name": "Doe",
        "publisher": {
            "name": "Acme Publishing"
        }
    }
}

To partially update a nested relationship, send only the fields you want to change in a PATCH request:

PATCH /books/1
{
    "author": {
        "id": 1,
        "biography": "Updated bio"
    }
}

The nested author object is deserialised into an Author instance while responses continue to use the configured serialisation type (URL, JSON, or dynamic).

Soft delete

flarchitect can mark records as deleted without removing them from the database. This allows you to hide data from normal queries while retaining it for auditing or future restoration.

Configuration

Enable soft deletes and define how records are flagged:

class Config:
    API_SOFT_DELETE = True
    API_SOFT_DELETE_ATTRIBUTE = "deleted"
    API_SOFT_DELETE_VALUES = (False, True)

API_SOFT_DELETE_ATTRIBUTE names the column that stores the deleted flag. API_SOFT_DELETE_VALUES is a tuple where the first value represents an active record and the second marks it as deleted.

Example model

Add a boolean column to your base model so every table can inherit the flag:

from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Boolean, DateTime
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class BaseModel(DeclarativeBase):
    created: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
    updated: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    deleted: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)

db = SQLAlchemy(model_class=BaseModel)

class Book(db.Model):
    __tablename__ = "books"
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column()

Example queries

Soft deleted rows are hidden from normal requests:

GET /api/books        # returns rows where deleted=False

Include the include_deleted query parameter to return all rows:

GET /api/books?include_deleted=true

Issuing a DELETE request marks the record as deleted. To remove it permanently, supply cascade_delete=1:

DELETE /api/books/1             # sets deleted=True
DELETE /api/books/1?cascade_delete=1  # removes row from database

CORS

To enable Cross-Origin Resource Sharing (CORS) for your API, set API_ENABLE_CORS to True in the application configuration. When active, CORS headers are applied to matching routes defined in CORS_RESOURCES.

CORS_RESOURCES accepts a mapping of URL patterns to their respective options, mirroring the format used by Flask-CORS.

class Config:
    API_ENABLE_CORS = True
    CORS_RESOURCES = {
        r"/api/*": {"origins": "*"}
    }

If flask-cors is installed, these settings are passed through to that extension. Without it, flarchitect compiles the patterns in CORS_RESOURCES and adds an Access-Control-Allow-Origin header for matching requests. Only origin checking is performed; other CORS headers are left untouched.

flask-cors-free minimal configuration:

class Config:
    API_ENABLE_CORS = True
    CORS_RESOURCES = {r"/api/*": {"origins": ["https://example.com"]}}

Example

The following snippet enables CORS for all API routes:

from flask import Flask
from flarchitect import Architect

app = Flask(__name__)
app.config["API_ENABLE_CORS"] = True
app.config["CORS_RESOURCES"] = {r"/api/*": {"origins": "*"}}

architect = Architect(app)

if __name__ == "__main__":
    app.run()

See the configuration page for the full list of available CORS settings.

Query parameter controls

flarchitect can expose several query parameters that let clients tailor responses. These toggles may be disabled to enforce fixed behaviour.

Filtering

Filtering is enabled by default and lets clients constrain results using <field>__<operator>=<value> predicates (e.g. title__ilike=python). Disable it globally or per model with API_ALLOW_FILTERS.

See Filtering for the complete syntax, supported operators, OR conditions via or[ ... ], and how to filter on joined models using table.column qualifications.

Ordering

Activate API_ALLOW_ORDER_BY to allow sorting via order_by:

GET /api/books?order_by=-published_date

Selecting fields

API_ALLOW_SELECT_FIELDS lets clients whitelist response columns with the fields parameter:

GET /api/books?fields=title,author_id

See configuration for detailed descriptions of API_ALLOW_FILTERS, API_ALLOW_ORDER_BY and API_ALLOW_SELECT_FIELDS.

Grouping and aggregation

API_ALLOW_GROUPBY enables the groupby parameter for SQL GROUP BY clauses. Use API_ALLOW_AGGREGATION alongside it to compute aggregates. Aggregates are expressed by appending a label and function to a field name:

GET /api/books?groupby=author_id&id|book_count__count=1

See Grouping & Aggregation for more end-to-end examples, supported functions and response shapes.

Cascade deletes

When removing a record, related rows may block the operation. These settings let flarchitect clean up relationships automatically when explicitly requested.

API_ALLOW_CASCADE_DELETE permits clients to trigger cascading removal by adding ?cascade_delete=1 to the request. Without this flag or query parameter, deletes that would orphan related records raise 409 Conflict instead of proceeding:

DELETE /api/books/1?cascade_delete=1
class Config:
    API_ALLOW_CASCADE_DELETE = True

API_ALLOW_DELETE_RELATED governs whether child objects referencing the target can be removed automatically. Disable it to require manual cleanup of related rows:

class Book(db.Model):
    class Meta:
        delete_related = False  # API_ALLOW_DELETE_RELATED

API_ALLOW_DELETE_DEPENDENTS covers dependent objects such as association table entries. Turning it off forces clients to delete those records explicitly:

class Book(db.Model):
    class Meta:
        delete_dependents = False  # API_ALLOW_DELETE_DEPENDENTS

See configuration for default values and additional context on these options.

Case conventions

flarchitect can reshape field and schema names to match different case conventions. These options keep the API’s payloads, schemas and endpoints consistent with the style used by your clients.

API_FIELD_CASE

Controls the casing for fields in JSON responses. By default, field names use snake case. Setting API_FIELD_CASE changes the output to match other naming styles:

class Config:
    API_FIELD_CASE = "camel"
{
    "statusCode": 200,
    "value": {
        "publicationDate": "2024-05-10"
    }
}

Switching to kebab case instead renders the same field as publication-date. Supported options include snake, camel, pascal, kebab and screaming_snake.

API_SCHEMA_CASE

Defines the naming convention for generated schema names in the OpenAPI document. The default, camel, produces schema identifiers such as apiCalls. Other styles are also available:

class Config:
    API_SCHEMA_CASE = "screaming_snake"

Interplay with API_ENDPOINT_CASE

API_ENDPOINT_CASE controls the casing of the generated URL paths. To maintain a consistent style across paths, schemas and payloads, combine API_ENDPOINT_CASE with the appropriate API_FIELD_CASE and API_SCHEMA_CASE values. For example, selecting kebab endpoint casing pairs naturally with kebab field names.

Extensions, validators and hooks

flarchitect offers several extension points for tailoring behaviour beyond configuration files. These hooks let you alter request handling, apply additional field validation and tweak responses on a per-route basis.

Response callbacks

Return callbacks run after database operations but before the response is serialised. Use them to adjust the output or append metadata.

from datetime import datetime

def add_timestamp(model, output, **kwargs):
    output["generated"] = datetime.utcnow().isoformat()
    return {"output": output}

class Config:
    API_RETURN_CALLBACK = add_timestamp

See flarchitect.core.routes.create_route_function() for details on how responses are constructed.

Custom validators

Attach validators to SQLAlchemy columns via the info mapping. Validators are looked up in flarchitect.schemas.validators and applied automatically.

class User(db.Model):
    email = db.Column(
        db.String,
        info={"validator": "email", "validator_message": "Invalid email"},
    )

See Validation for the full list of available validators.

Per-route hooks

Execute custom logic before or after a specific route by defining setup or return callbacks in configuration or on a model’s Meta class.

from flask import abort
from flask_login import current_user

def ensure_admin(model, **kwargs):
    if not current_user.is_admin:
        abort(403)
    return kwargs

class Book(db.Model):
    class Meta:
        return_callback = add_timestamp

class Config:
    API_SETUP_CALLBACK = ensure_admin

For more examples see the Extensions page.