Getting Started#

This document will go over the basics of building a GraphQL server. We will cover:

  1. Defining your types and schema
  2. Adding custom resolvers to your schema
  3. Executing client queries against the schema
  4. Exposing the schema behind an HTTP endpoint (using Flask)

If you haven’t done so already, please take a moment to install py-gql before continuing.

Defining your schema#

The first step in creating a GraphQL server is defining the types and schema that clients will be able to query. The GraphQL schema that we’ll implement for this example is as follow (using the schema definition language):

enum CharacterType {
    Human,
    Droid,
}

type Query {
    hero: Person!,
    characters: [Person]!,
    character(id: Int!): Person,
}

type Person {
    id: Int!,
    name: String,
    type: CharacterType,
}

The easiest way to build an executable schema for our server is through py_gql.build_schema() which does so by consuming the SDL. This is often referred to as a schema first approach to schema definition, as opposed to code first where we’d build a schema from Python objects.

from py_gql import build_schema

SDL = """
...
"""

schema = build_schema(SDL)

Interacting with your data through resolver functions#

The schema as defined above isn’t going to be very useful unless we have a way to interact with our data.

For this example we will use the following JSON dataset:

[
  {
    "type": "Droid",
    "id": 2000,
    "name": "C-3PO"
  },
  {
    "type": "Droid",
    "id": 2001,
    "name": "R2-D2"
  },
  {
    "type": "Human",
    "id": 1000,
    "name": "Luke Skywalker"
  },
  {
    "type": "Human",
    "id": 1001,
    "name": "Darth Vader"
  },
  {
    "type": "Human",
    "id": 1002,
    "name": "Han Solo"
  },
  {
    "type": "Human",
    "id": 1003,
    "name": "Leia Organa"
  },
  {
    "type": "Human",
    "id": 1004,
    "name": "Wilhuff Tarkin"
  }
]

Let’s implement our resolver functions:

@schema.resolver("Query.hero")
def resolve_hero(_root, ctx, _info):
    return ctx["db"][2000]  # R2-D2


@schema.resolver("Query.characters")
def resolve_characters(_root, ctx, _info):
    return ctx["db"].items()


@schema.resolver("Query.character")
def resolve_character(_root, ctx, _info, *, id):
    return ctx["db"].get(id, None)

While we won’t go into details concerning the resolvers signature we can already note that they receive the following parameters:

  • The resolved parent value (The server provided root value for top-level fields)
  • A context value which is where you provide any application specific data such as database connections, loggers, etc. In this case the db part of the context will be a mapping for each entry’s id to itself.
  • An info object which carries some GraphQL specific data about the current execution context.
  • The GraphQL field arguments are passed as keyword parameters.

We haven’t implemented any resolver for the field on the Character type. That is because the default behavior is to look up field of dictionaries and objects which is sufficient here.

Refer to Defining resolvers for more details on implementing resolvers.

Executing queries against the schema#

Now that we have a valid schema, we just need to provide client queries for execution. The execution is carried out by py_gql.graphql_blocking() (as all our resolvers are blocking, for different runtimes such as Python’s asyncio we’d use py_gql.graphql()) and consists of 3 steps:

  1. Parsing the client query and validating that it is a correct GraphQL document
  2. Validating the query against the schema to verify that it can be executed at all
  3. Execute the query and surface any errors or return the data

The return value is an instance of py_gql.GraphQLResult which can be serialized using py_gql.GraphQLResult.response().

We will expose this behind an HTTP API using Flask.

Note

While the transport and serialization format depend on the application and are technically irrelevant to the GraphQL runtime itself; it is pretty common to expose GraphQL APIs behind an HTTP server, traditionally under POST /graphql and JSON encode the request and response which is what we’re doing here.

app = flask.Flask(__name__)


@app.route("/graphql", methods=("POST",))
def graphql_route():
    data = flask.request.json

    result = graphql_blocking(
        schema,
        data["query"],
        variables=data.get("variables", {}),
        operation_name=data.get("operation_name"),
        context=dict(db=database),
    )

    return flask.jsonify(result.response())

Pulling together all that we’ve seen so far you should have a working GraphQL server that you can test using any HTTP client.

Using HTTPie for example (note that the response might be reordered due to how HTTPie prints json, use --pretty=none to see the raw response):

$ http --json POST :5000/graphql query='{ hero { id name type } }'
HTTP/1.0 200 OK
...

{
    "data": {
        "hero": {
            "id": 2000,
            "name": "C-3PO",
            "type": "Droid"
        }
    }
}

$ http --json POST :5000/graphql query='{ character(id: 10) { name } }'
HTTP/1.0 200 OK
...

{
    "data": {
        "character": null
    }
}

$ http --json POST :5000/graphql query='{ character(id: 1000) { name } }'
HTTP/1.0 200 OK
...

{
    "data": {
        "character": {
            "name": "Luke Skywalker"
        }
    }
}

$ http --json POST :5000/graphql query='{ character(id: 1000) }'
HTTP/1.0 200 OK
...

{
    "errors": [
        {
            "locations": [
                {
                    "column": 3,
                    "line": 1
                }
            ],
            "message": "Field \"character\" of type \"Person\" must have a subselection"
        }
    ]
}

Complete code#

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
# -*- coding: utf-8 -*-
"""
Minimal getting started example with Flask.

To run this, make sure Flask and py_gql are installed and run:

    FLASK_APP=getting-started.py flask run
"""

import json

import flask

from py_gql import build_schema, graphql_blocking


SDL = """
enum CharacterType {
    Human,
    Droid,
}

type Query {
    hero: Person!,
    characters: [Person]!,
    character(id: Int!): Person,
}

type Person {
    id: Int!,
    name: String,
    type: CharacterType,
}
"""

DATA = """
[
  {
    "type": "Droid",
    "id": 2000,
    "name": "C-3PO"
  },
  {
    "type": "Droid",
    "id": 2001,
    "name": "R2-D2"
  },
  {
    "type": "Human",
    "id": 1000,
    "name": "Luke Skywalker"
  },
  {
    "type": "Human",
    "id": 1001,
    "name": "Darth Vader"
  },
  {
    "type": "Human",
    "id": 1002,
    "name": "Han Solo"
  },
  {
    "type": "Human",
    "id": 1003,
    "name": "Leia Organa"
  },
  {
    "type": "Human",
    "id": 1004,
    "name": "Wilhuff Tarkin"
  }
]
"""

schema = build_schema(SDL)
database = {row["id"]: row for row in json.loads(DATA)}


@schema.resolver("Query.hero")
def resolve_hero(_root, ctx, _info):
    return ctx["db"][2000]  # R2-D2


@schema.resolver("Query.characters")
def resolve_characters(_root, ctx, _info):
    return ctx["db"].items()


@schema.resolver("Query.character")
def resolve_character(_root, ctx, _info, *, id):
    return ctx["db"].get(id, None)


app = flask.Flask(__name__)


@app.route("/graphql", methods=("POST",))
def graphql_route():
    data = flask.request.json

    result = graphql_blocking(
        schema,
        data["query"],
        variables=data.get("variables", {}),
        operation_name=data.get("operation_name"),
        context=dict(db=database),
    )

    return flask.jsonify(result.response())