December 13, 2022

Add Python to your GraphQL API with GraphOS and Strawberry GraphQL

Patrick Arminio

A supergraph helps you share responsibility for your organization’s data across multiple backend services (these are known as subgraphs). Different teams can work on different subgraphs in parallel, and better yet, each subgraph can use an entirely different language and framework!

There are loads of subgraph-compatible libraries to choose from. In this post, we’ll look at how to create a subgraph in Python using the Strawberry GraphQL library. Python is an extremely popular language (and deservedly so), and it’s a great choice for building subgraphs.

What’s Strawberry GraphQL?

Strawberry is a code-first GraphQL server library that uses Python type annotations to define GraphQL types.

💡GraphQL server libraries come in two general “flavors”: code-first and schema-first. 

  • With a schema-first library, you create a static schema file using GraphQL’s schema definition language (SDL). You then define resolvers for your fields in your code.
  • With a code-first library (such as Strawberry), you instead define your schema’s types and fields programmatically, using features of the library and language.

Let’s look at an example of generating GraphQL types with Strawberry. The left snippet below shows three object types defined using SDL. The right snippet shows those same three types defined using Strawberry:

These two snippets look pretty similar! Python’s syntax and type hints provide a code-first experience that’s very “SDL-like”.

 Note the following about the Strawberry snippet:

  • We annotate each type with @strawberry.type. This is specifically the annotation for defining object types. You can use @strawberry.input and @strawberry.interface for input types and interfaces, respectively.
  • As mentioned above, Strawberry takes advantage of Python type hints. For example, when writing buyer: User, we’re specifying that this class has a field called buyer of type User. This information is then used by Strawberry to create the GraphQL type.
  • Strawberry automatically converts snake_case field names to camelCase in your schema, so you can use Python casing conventions in your code.
  • When defining fields in SDL, we have to explicitly mark non-nullable fields using ! (for example, buyer: User!). Python’s type system uses the opposite convention, meaning every field is non-nullable by default.To designate a field as nullable with Strawberry, we use the | operator with None, which indicates that the field’s value can also be None.

Federated directives with Strawberry

If you’re familiar with Apollo Federation, you know that many of its features rely on schema directives (@key for entities, @shareable for sharing fields across subgraphs, @override for overriding another subgraph’s field and so on). Code-first GraphQL libraries like Strawberry need a mechanism to apply these directives, because there isn’t a static schema file to add them to. 

Strawberry provides built-in support for applying schema directives. For example, the following Python code:

import strawberry

from strawberry.federation.schema_directives import Key

@strawberry.type(directives=[Key(fields="id")])
class Product:
    id: strawberry.ID

Will generate the following GraphQL type:

type Product @key(fields: "id", resolvable: true) {
  id: ID!
}

In addition to this base support, Strawberry provides useful shortcuts for some federation-specific directives. For example, the snippet above can be simplified like so:

import strawberry

@strawberry.federation.type(keys=[“id”])
class Product:
    id: strawberry.ID

Which is much cleaner!

Ok! Now that we have some understanding of Strawberry and Federation, let’s see what we are building!

Extending an existing API into a supergraph 

Let’s say we have an existing Node.js GraphQL server that’s deployed as a monolith. This monolith provides an API for an e-commerce store that enables clients to fetch orders, products, and the current user’s cart.

Now let’s say we want to extend this API to provide the shipping cost for each order. Our team wants to create a separate service that manages shipping data, and they want to implement that service in Python instead of Node.js.

Let’s see how we can do that using Strawberry, Apollo Federation, and GraphOS!

Historically, we’ve needed to self-host a gateway to combine multiple GraphQL APIs using Apollo Federation.

Now with GraphOS, we can set up a cloud supergraph  that helps us jump into Federation much more quickly. There’s a lot in GraphOS, check out our docs to learn more.

Let’s look at how we can add our monolith to a cloud supergraph.

Setting up a cloud supergraph

To get the most out of Apollo Federation, we usually need to make minor changes to our monolith’s schema. In this blog post we’ll be using a monolith with a schema that’s already been updated for Federation. To learn how you can update your monolith to support federation, read this tech note from the Apollo Solutions Architect team

And feel free to join our Discord server and ask any questions you have there. We’ll be happy to guide you!

The schema for our example monolith already has the Federation directives, to make it easier to test the flow.

Our monolith is currently deployed here: https://hack-the-supergraph-ecommerce.fly.dev/

We can go to Apollo Studio and create a new cloud supergraph that will use our monolith as its first subgraph. This will enable us to deploy our new service as the second subgraph.

Walkthrough of creating a Cloud Supergraph

Once we’ve successfully created our cloud supergraph, we should be able to query data through its GraphOS-hosted router.

Let’s try the following query:

query OrderById {
 order(id: "1") {
   buyer {
     username
   }
   items {
     title
     price {
       amount
       currency
     }
   }
   total {
     amount
     currency
   }
 }
}

Feel free to take some time to explore the other fields as well!

Extending our Graph 

As mentioned above, we want to add the shipping cost to the `Order` type. Let’s see how the type currently looks in our existing API:

type Order @key(fields: "id") {
 id: ID!
 buyer: User!
 items: [Product!]!
 total: Money!
}

And this is what we want our updated `Order` type to look like:

type Order @key(fields: "id") {
 id: ID!
 buyer: User!
 items: [Product!]!
 total: Money!
 shippingCost: Float!
}

To achieve this, we need to also define the Order type in our new subgraph and include the new field, like so:

# in our shipping service:
type Order @key(fields: "id") {
 id: ID!
 shippingCost: Float!
}

Note: if you’re familiar with Apollo Federation 1, you might wonder about the lack of the `extend` keyword here This is not required anymore in Apollo Federation 2! Read more about Federation 2 here.

Let’s go and implement this using Strawberry. 

Implementing our subgraph in Python

Creating a new subgraph always involves some boilerplate code. We need to set up our web server, install dependencies, define a basic schema, and so on. 

To help with all of this boilerplate, we can use a subgraph template from the Rover CLI!

In a new project directory, let’s create our subgraph using the Strawberry GraphQL template:  

rover template use --template subgraph-python-strawberry-fastapi

After applying our template, next we should create a virtualenv and install the dependencies: 

python -m venv .virtualenv
source .virtualenv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt

We’re ready to build! Let’s head over to api/schema.py and start making our changes. Feel free to delete everything from that file, because we’ll implement a completely different schema.  

Remember that our goal is to add a shipping cost field to our Order type. Let’s see how we can achieve this in our subgraph schema using Strawberry:

import strawberry

def calculate_shipping_cost() -> float:
    return 42.0


@strawberry.federation.type(keys=["id"])
class Order:
    id: strawberry.ID
    shipping_cost: float = strawberry.federation.field(resolver=calculate_shipping_cost)

schema = strawberry.federation.Schema(
    enable_federation_2=True,
    types=[Order],
)

The code above includes three definitions: a resolver, an Order type, and the schema itself. Let’s walk through each of those:

Resolvers in Strawberry are Python functions that are called whenever a corresponding field is requested. For simplicity, this resolver always returns 42.0 for now.

To define the Order type, we create a Python class with two fields: id and shipping_cost. We decorate this class with @strawberry.federation.type, which indicates that it represents a federated object type. We’re using the federation version of this decorator to make it easier to add the key directive. We also use strawberry.federation.field to attach our resolver to the shipping_cost field.

We create the schema itself using strawberry.federation.Schema. We make sure to enable Federation 2 features and pass our Order type to the types parameter. 

Trying our subgraph locally

Now that we have a complete schema we can run the API locally to make sure everything starts up successfully:

uvicorn main:app --reload

To verify that the resolvers are functioning properly, we have two options. One is to use rover dev to develop and test our supergraph locally. The other is to use Apollo Sandbox or the GraphiQL interface to interact directly with our subgraph.

Using Sandbox, we can see we get the Query and Mutation types (standard GraphQL), as well as the special _service and _entities fields on Query to support Federation.

Sandbox Explorer's Schema view showing the Query type

Deploying our subgraph

Now that we have a ready GraphQL API we should deploy it and add it to our Cloud Router. To keep things simple I’ve prepared a repo with the code above that can be hosted on Railway in a few clicks:

https://github.com/patrick91/strawberry-graphos-shipping

Adding our subgraph to the supergraph

Once we have deployed our subgraph, we can add it to the supergraph, run the following command to get the schema and add it to the supergraph:

rover subgraph introspect URL | \
  rover subgraph publish GRAPH_REF \
    --name shipping \
    --schema - \
    --routing-url URL

Replace URL and GRAPH_REF with your deployed url and the Graph Ref from Apollo Studio.

If everything was fine, you should see a message like this:

The 'shipping' subgraph in 'patrick91-supergraph-n86wv8@main' was updated
The supergraph schema for 'patrick91-supergraph-n86wv8@main' was updated, composed from the updated 'shipping' subgraph

Awesome! It’s time to test our supergraph, let’s update our previous query and request the shipping cost:

query OrderWithShippingCost {
 order(id: "1") {
   buyer {
     username
   }
   items {
     title
     price {
       amount
       currency
     }
   }
   total {
     amount
     currency
   }
   shippingCost
 }
}

After running it we should get this ouput:

{
  "data": {
    "order": {
      "buyer": {
        "username": "test_1"
      },
      "items": [
        {
          "title": "Product 1",
          "price": {
            "amount": 1,
            "currency": "USD"
          }
        },
        {
          "title": "Product 2",
          "price": {
            "amount": 2,
            "currency": "USD"
          }
        }
      ],
      "total": {
        "amount": 3,
        "currency": "USD"
      },
      "shippingCost": 11.5
    }
  }
}

And there it is: our shipping cost field!

Recap

GraphOS enables us to extend and build supergraphs using your preferred language. In this blog post we have seen how to create Cloud supergraph and how to extend an existing monolith using Python and Strawberry GraphQL.

To learn more about GraphOS head over to the GraphOS docs page. If you want to learn more about building GraphQL APIs in Python see Strawberry GraphQL’s website.

Have questions about GraphOS or building GraphQL APIs? Join our discord and chat with us!

Written by

Patrick Arminio

Stay in our orbit!

Become an Apollo insider and get first access to new features, best practices, and community events. Oh, and no junk mail. Ever.

Make this article better!

Was this post helpful? Have suggestions? Consider so we can improve it for future readers ✨.

Similar posts

January 4, 2023

Add Rust to your GraphQL API with GraphOS

by Dylan Anthony

Company