# How to Configure Response Formatting in FastCRUD: A Complete Guide

> Learn how to configure response formatting in FastCRUD using schema_to_select return_as_model and nest_joins for raw dictionaries or Pydantic models.

- Repository: [Benav Labs/fastcrud](https://github.com/benavlabs/fastcrud)
- Tags: how-to-guide
- Published: 2026-02-26

---

**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`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/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`:

```python
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:

```python
@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:

```python
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:

```python
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`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/fastcrud/core/config/crud_configs.py) | Join configuration | `JoinConfig` class that influences how joins are built and later formatted |
| [`fastcrud/types.py`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/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`](https://github.com/benavlabs/fastcrud/blob/main/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.