pythongraphqlstrawberry-graphql

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 ?


Solution

  • 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:

    Additional Benefits

    FraiseQL also provides:

    Install with:

    pip install fraiseql
    

    Documentation - work in progress | GitHub

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