Skip to content

GraphQL

A single, precise question can bring down prey faster than a scattered chase. GraphQL lets clients ask for exactly the fields they need and nothing more, while VelociPy keeps the integration protocol-agnostic and RSGI-friendly.

VelociPy ships optional GraphQL support through strawberry-graphql. Define a schema with Python type hints, mount the endpoint, and add a WebSocket route if you need subscriptions.

Run the examples

See examples/graphql.py for a runnable demo of queries, mutations, subscriptions, file uploads, and context injection.


Installation

Install the graphql extra to pull in strawberry-graphql:

pip install 'velocipy[graphql]'

Quick start

import strawberry
from velocipy import VelociPy
from velocipy.constants import Method
from velocipy.features.graphql import GraphQL


@strawberry.type
class Query:
    @strawberry.field
    def hello(self, name: str = "world") -> str:
        return f"Hello, {name}!"


schema = strawberry.Schema(query=Query)
app = VelociPy()
app.mount("/graphql", GraphQL(schema), methods={Method.GET, Method.POST, Method.HEAD})

Run the app as usual:

granian main:app --interface rsgi --reload
# or
uvicorn main:app --reload

Queries and mutations

Both GET and POST are supported. GET reads query, variables, and operationName from the query string. POST reads them from a JSON body:

curl -X POST http://localhost:8000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ hello(name: \"VelociPy\") }"}'

Mutations work the same way:

@strawberry.type
class Mutation:
    @strawberry.mutation
    def create_user(self, name: str) -> str:
        return f"Created {name}"


schema = strawberry.Schema(query=Query, mutation=Mutation)

GraphiQL

A minimal GraphiQL IDE is served on GET /graphql when graphiql=True (default) and the request prefers text/html:

from velocipy.constants import Method

app.mount(
    "/graphql",
    GraphQL(schema, graphiql=True),
    methods={Method.GET, Method.POST, Method.HEAD},
)

Disable it in production if you do not want to expose the playground:

app.mount(
    "/graphql",
    GraphQL(schema, graphiql=False),
    methods={Method.GET, Method.POST, Method.HEAD},
)

Subscriptions

Subscriptions use WebSocket and the graphql-transport-ws subprotocol by default. Mount the HTTP endpoint and register a separate WebSocket handler:

@strawberry.type
class Subscription:
    @strawberry.subscription
    async def count(self, limit: int = 5) -> int:
        for value in range(limit):
            yield value


schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
graphql_app = GraphQL(schema)

app = VelociPy()
app.mount(
    "/graphql", graphql_app, methods={Method.GET, Method.POST, Method.HEAD}
)
app.add_websocket_route("/graphql", graphql_app.websocket_handler)

The same WebSocket connection can also execute queries and mutations (single-result operations).


Context injection

Provide a static context value or an async callable that receives the request (for HTTP) or WebSocket (for subscriptions):

from velocipy.http.requests import Request


async def get_context(request: Request) -> dict[str, object]:
    return {"request": request, "db": request.state["db"]}


graphql_app = GraphQL(schema, context=get_context)

For subscriptions, the callable receives the :class:WebSocket object and Strawberry injects the connectionParams from the client's connection_init message into the same context under the connection_params key.


File uploads

Uploads use Strawberry's Upload scalar and the GraphQL multipart request spec. The python-multipart extra is required.

from velocipy import Upload


@strawberry.type
class Mutation:
    @strawberry.mutation
    def upload(self, file: Upload) -> str:
        return file.filename

Uploads are enabled by default. Disable them with multipart_uploads_enabled=False.


WebSocket protocols

By default both the recommended graphql-transport-ws and the legacy graphql-ws subprotocols are accepted. Restrict them if your clients only support one:

from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL

graphql_app = GraphQL(schema, subscription_protocols=[GRAPHQL_TRANSPORT_WS_PROTOCOL])

OpenAPI

The mounted GraphQL routes are registered in the OpenAPI schema so the endpoint is discoverable. The detailed schema introspection is provided by GraphQL itself at /graphql.


At a glance

Setting What it does
GraphQL(schema) Wraps a Strawberry schema as an ASGI/RSGI app.
app.mount("/graphql", ..., methods={...}) Exposes queries and mutations over HTTP.
GraphQL(schema, graphiql=True) Serves the GraphiQL IDE on GET /graphql.
app.add_websocket_route(..., graphql_app.websocket_handler) Enables subscriptions over WebSocket.
context=get_context Injects the request or WebSocket into resolvers.
Upload scalar Handles GraphQL multipart file uploads.
subscription_protocols=[...] Restricts accepted WebSocket subprotocols.

Example What it shows
examples/graphql.py Queries, mutations, subscriptions, file uploads, and context injection.
  • WebSockets — keep a persistent connection open for subscriptions.
  • OpenAPI — document the mounted GraphQL endpoint alongside REST routes.
  • Uploads — multipart file handling outside of GraphQL.