Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimizing Strawberry GraphQL API Performance: How to bypass object Instantiation for Pre-Formatted JSON from PostgreSQL?

I'm developing a GraphQL API using Strawberry and FastAPI, where I directly extract and shape data from a PostgreSQL database into JSON, formatted as per the GraphQL schema.

The data extraction is performed with SQL queries that utilize the selected fields as well as PostgreSQL's JSON capabilities, allowing the data to be shaped exactly as needed for the GraphQL response.

My goal now is to bypass the Python object validation in Strawberry for this pre-formatted JSON to improve performance.

In my current setup, I have various GraphQL types defined in Strawberry, resembling the following:

import strawberry

@strawberry.type
class Player:
    name: str
    age: int
    # ... more fields ...

@strawberry.type
class Team:
    city: str
    players: list[Player]

I have resolvers that are supposed to return instances of these types. However, given that the data retrieved from PostgreSQL is already structured appropriately (thanks to SQL's JSON shaping features), I am looking for a way to bypass the conversion and validation of these JSON objects into Strawberry instances.

Example resolver structure:

@strawberry.type
class Query:
    @strawberry.field
    def teams_with_player(self, info) -> list[Team]:
        formatted_json = query_postgresql_for_formatted_json(info.selected_fields)
        # The above function returns JSON directly in the structure expected by the GraphQL schema
        return formatted_json

The query_postgresql_for_formatted_json function fetches the JSON data to align with the GraphQL schema and the selected fields.

for instance, with the following query:

query {
  teamsWithPlayer {
    city
    players {
      name
    }
  }
}

the function parses the selected fields and the database returns the following data:

[
  {
    "city": "Abuja",
    "players": [
      {"name": "Player1"},
      {"name": "Player2"}
    ]
  },
  {
    "city": "Djakarta",
    "players": [
      {"name": "Player3"},
      {"name": "Player4"}
    ]
  }
  // ... more teams ...
]

how can I return this json without instanciating the Strawberry objects ?

like image 651
Lionel Hamayon Avatar asked Dec 28 '25 21:12

Lionel Hamayon


2 Answers

I ended up creating a framework to solve this exact problem - FraiseQL

After struggling with the performance overhead of Python object instantiation in Strawberry/GraphQL, I developed FraiseQL - a lightweight GraphQL-to-PostgreSQL query builder that bypasses Python object validation entirely.

The key insight was that if PostgreSQL is already returning properly formatted JSON via JSONB, why waste cycles converting it to Python objects just to serialize it back to JSON for the GraphQL response?

How FraiseQL solves this

Instead of the traditional flow:


PostgreSQL JSON → Python Objects → Validation → JSON Response

FraiseQL uses:


PostgreSQL JSONB → Direct JSON Response

Here's how the example from my question works with FraiseQL:

import fraiseql

@fraiseql.type
class Player:
    name: str
    age: int

@fraiseql.type
class Team:
    city: str
    players: list[Player]

@fraiseql.query
async def teams_with_player(info) -> list[Team]:
    # No object instantiation - data flows directly from DB to response
    return await info.context["db"].find("team_view")

# Create the FastAPI app
app = fraiseql.create_app(
    database_url="postgresql://...",
    types=[Player, Team],
    queries=[teams_with_player]
)

The corresponding PostgreSQL view:

CREATE VIEW team_view AS
SELECT 
    jsonb_build_object(
        'city', t.city,
        'players', COALESCE(
            jsonb_agg(
                jsonb_build_object('name', p.name)
                ORDER BY p.name
            ) FILTER (WHERE p.id IS NOT NULL), 
            '[]'::jsonb
        )
    ) as data
FROM teams t
LEFT JOIN players p ON p.team_id = t.id
GROUP BY t.id, t.city;

Performance Results

This approach achieved:

  • 10-50x faster response times for complex queries
  • Near-zero memory overhead for large result sets
  • Direct streaming from PostgreSQL to HTTP response

Additional Benefits

FraiseQL also provides:

  • Type-safe WHERE filtering: Automatically generates filter types from your models
  • ORDER BY support: Including nested field ordering
  • No N+1 queries: Everything is resolved in a single SQL query
  • Production features: Authentication, monitoring, caching, etc.

Install with:

pip install fraiseql

Documentation | GitHub | PyPi | fraiseql.dev

This solution specifically addresses the performance bottleneck of object instantiation when PostgreSQL can already provide perfectly formatted JSON for GraphQL responses.

like image 150
Lionel Hamayon Avatar answered Dec 31 '25 09:12

Lionel Hamayon


You can define a custom Scalar based on the strawberry JSON scalar and use that as the return type in your GraphQL resolver instead of using a strictly type checked response as shown in your code snippet. Example

# define a custom scalar type
CUSTOM_JSON = strawberry.scalar(
    NewType("JSON", object),
    description="The `JSON` scalar type represents JSON values as specified by ECMA-404",
    serialize=lambda v: v,
    parse_value=lambda v: v,
)

Now use the custom scalar as your resolver's return type instead of a strictly typed one. This should allow you to handle any JSON output.

@strawberry.type
class Query:
    @strawberry.field
    def teams_with_player(self, info) -> CUSTOM_JSON:
        formatted_json = query_postgresql_for_formatted_json(info.selected_fields)
        # The above function returns JSON directly in the structure expected by the GraphQL schema
        return formatted_json
like image 33
TeaCoder Avatar answered Dec 31 '25 09:12

TeaCoder



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!