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. When True, the formatter wraps raw dicts with the supplied schema_to_select. When False, raw dictionaries are returned regardless of schema presence.
  • nest_joins — Available only for join-related calls. When True, related rows are nested into sub-objects; when False, 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_multi invokes format_multi_response at lines 1414-1416.
  • FastCRUD.get_joined invokes format_joined_response at lines 12090-12098.
  • FastCRUD.create applies 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.py that convert database rows into the desired output structure.
  • Three parameters control all formatting: schema_to_select defines the shape, return_as_model toggles between dicts and Pydantic instances, and nest_joins controls hierarchical nesting for joined queries.
  • Raw dictionaries are returned automatically when schema_to_select is omitted, while validated models require both a schema and return_as_model=True.
  • Pagination metadata is constructed by create_paginated_response_data when return_total_count=True is passed to multi-row methods.
  • Joined responses can be flattened or nested based on the nest_joins boolean passed to get_multi_joined or get_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →