How to Configure Response Formatting in FastCRUD: A Complete Guide
FastCRUD controls response output through three parameters—schema_to_select, return_as_model, and nest_joins—which delegate to dedicated formatting utilities in fastcrud/core/data/formatting.py to produce either raw dictionaries or validated Pydantic models.
FastCRUD is an async CRUD operations library for FastAPI and SQLAlchemy that separates data retrieval from response rendering. When you configure response formatting in FastCRUD, you are controlling how raw SQLAlchemy rows become structured output. All transformation logic lives in a pure, side-effect-free formatting module, exposed through consistent arguments across the FastCRUD class methods.
The Core Formatting Architecture
FastCRUD delegates every response transformation to four specialized functions located in fastcrud/core/data/formatting.py. These utilities handle conversion from database rows to the final output shape without touching the database layer.
| Function | Purpose | Source Location |
|---|---|---|
format_single_response |
Converts a single row dictionary into a Pydantic model instance or returns it as a plain dict. | Lines 37-46 |
format_multi_response |
Applies the same conversion to a list of row dictionaries. | Lines 67-76 |
format_joined_response |
Handles nested or joined results, optionally injecting pagination metadata and total counts. | Lines 90-106 |
create_paginated_response_data |
Builds the generic paginated envelope containing data, total_count, and has_more flags. |
Lines 111-124 |
These utilities are stateless and depend only on the input data and configuration flags passed from the calling CRUD method.
Response Formatting Parameters
Every public CRUD method (create, get, get_multi, get_joined) exposes three knobs that determine the final response structure:
schema_to_select— A Pydantic schema class defining which columns are returned and their types. When omitted, raw column dictionaries are returned.return_as_model— A boolean flag. WhenTrue, the formatter wraps raw dicts with the suppliedschema_to_select. WhenFalse, raw dictionaries are returned regardless of schema presence.nest_joins— Available only for join-related calls. WhenTrue, related rows are nested into sub-objects; whenFalse, they are flattened into the parent dictionary with prefixes.
According to the source code in fastcrud/crud/fast_crud.py, these parameters are wired directly into the formatting calls at specific execution points:
FastCRUD.get_multiinvokesformat_multi_responseat lines 1414-1416.FastCRUD.get_joinedinvokesformat_joined_responseat lines 12090-12098.FastCRUD.createapplies formatting logic at lines 618-626 to return either a dict or model.
Practical Configuration Examples
Returning Pydantic Models from Create Operations
To enforce strict type contracts on create endpoints, pass a read schema and set return_as_model=True:
from fastcrud import FastCRUD
from fastapi import FastAPI, Depends
from myapp.models import User
from myapp.schemas import UserCreate, UserRead
app = FastAPI()
user_crud = FastCRUD[User, UserCreate, None, None, None, UserRead](User)
@app.post("/users", response_model=UserRead)
async def create_user(db=Depends(get_db), payload: UserCreate):
# return_as_model=True triggers format_single_response with schema validation
return await user_crud.create(
db,
payload,
schema_to_select=UserRead,
return_as_model=True
)
Paginated Lists with Raw Dictionaries
For maximum flexibility in list endpoints, omit schema_to_select to receive raw dictionaries with automatic pagination metadata:
@app.get("/users")
async def list_users(db=Depends(get_db), offset: int = 0, limit: int = 20):
# No schema_to_select → format_multi_response returns raw dicts
res = await user_crud.get_multi(
db,
offset=offset,
limit=limit,
return_total_count=True # Triggers create_paginated_response_data
)
# Response shape: {"data": [...], "total_count": 123, "has_more": true}
return res
Nested Joined Responses
When querying related tables, use nest_joins=True to transform flat join results into hierarchical objects:
from fastcrud.core.config.crud_configs import JoinConfig
from myapp.models import Order, Product
from myapp.schemas import OrderRead, ProductRead
order_crud = FastCRUD[Order, None, None, None, None, OrderRead](Order)
join_cfg = [
JoinConfig(
model=Product,
join_on=Order.product_id == Product.id,
join_prefix="product_",
schema_to_select=ProductRead,
)
]
@app.get("/orders")
async def list_orders(db=Depends(get_db)):
return await order_crud.get_multi_joined(
db,
schema_to_select=OrderRead,
join_configs=join_cfg,
nest_joins=True, # format_joined_response creates nested "product" objects
return_as_model=True, # Returns OrderRead instances with nested ProductRead
return_total_count=True,
)
Custom Response Shapes with Field Exclusion
Define compact schemas to limit exposed fields without modifying the database model:
from pydantic import BaseModel
class UserReadCompact(BaseModel):
id: int
username: str
@app.get("/users/compact")
async def compact_users(db=Depends(get_db)):
# Only id and username appear in the response
return await user_crud.get_multi(
db,
schema_to_select=UserReadCompact,
return_as_model=True,
return_total_count=False,
)
Key Source Files for Response Formatting
Understanding the internal wiring helps debug formatting behavior:
| File | Responsibility | Key Components |
|---|---|---|
fastcrud/core/data/formatting.py |
Pure formatting utilities | format_single_response, format_multi_response, format_joined_response, create_paginated_response_data |
fastcrud/crud/fast_crud.py |
High-level CRUD methods | create (lines 618-626), get_multi (lines 1414-1416), get_multi_joined (lines 12090-12098) |
fastcrud/core/config/crud_configs.py |
Join configuration | JoinConfig class that influences how joins are built and later formatted |
fastcrud/types.py |
Type definitions | Type aliases like SelectSchemaType used throughout formatting signatures |
Summary
- Response formatting in FastCRUD is handled by pure functions in
fastcrud/core/data/formatting.pythat convert database rows into the desired output structure. - Three parameters control all formatting:
schema_to_selectdefines the shape,return_as_modeltoggles between dicts and Pydantic instances, andnest_joinscontrols hierarchical nesting for joined queries. - Raw dictionaries are returned automatically when
schema_to_selectis omitted, while validated models require both a schema andreturn_as_model=True. - Pagination metadata is constructed by
create_paginated_response_datawhenreturn_total_count=Trueis passed to multi-row methods. - Joined responses can be flattened or nested based on the
nest_joinsboolean passed toget_multi_joinedorget_joined.
Frequently Asked Questions
What happens if I omit schema_to_select in a FastCRUD method call?
When you omit schema_to_select, the formatting functions return raw Python dictionaries containing all columns from the query. In fastcrud/core/data/formatting.py, the format_single_response and format_multi_response functions detect the absence of a schema and pass the row dict through unchanged, providing maximum flexibility for dynamic queries.
How does the nest_joins parameter affect joined query responses?
The nest_joins parameter determines the structure of related data in get_joined and get_multi_joined responses. When set to True, format_joined_response organizes related rows into nested sub-objects (e.g., an order containing a product object). When False, related fields are flattened into the parent dictionary with prefixes defined in JoinConfig, resulting in a single-level structure like {"id": 1, "product_name": "Widget"}.
Can I mix Pydantic models and raw dictionaries in different endpoints of the same application?
Yes. FastCRUD allows per-call configuration of response formatting. One endpoint can use return_as_model=True with a strict Pydantic schema for API contracts, while another can omit both schema_to_select and return_as_model to receive raw dictionaries for internal processing. The FastCRUD instance itself stores no formatting state; all behavior is determined by the arguments passed to each method invocation in fastcrud/crud/fast_crud.py.
Where does FastCRUD add pagination metadata like total_count?
Pagination metadata is injected by create_paginated_response_data in fastcrud/core/data/formatting.py (lines 111-124) whenever return_total_count=True is passed to get_multi or get_multi_joined. The resulting envelope includes keys for data, total_count, and has_more, allowing frontend clients to implement pagination controls without calculating totals themselves.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →