SaltyCrane Blog — Notes on JavaScript and web development

Creating a GraphQL API with Python, Graphene, and Postgres

Here are my notes on creating a GraphQL API with Python, Django, Graphene, and Postgres. I learned almost everything from the excellent GraphQL Python tutorial at howtographql.com. Optimistically, I'll write a series of posts about how to make a React Native app that no one will use. Realistically, I won't.

Contents

Install Python 3.7.2

$ brew install python

Create project directory and virtualenv

$ # make project directory
$ mkdir travelog-api
$ cd travelog-api
$ # make virtualenv
$ python3 -m venv venv
$ # activate virtualenv
$ source venv/bin/activate
$ # upgrade pip
$ pip install --upgrade pip

Install Django and create a Django project

(Still in the travelog-api directory with virtualenv activated)

  • Install Django 2.1.7:
    $ pip install Django 
    
  • Create Django project:
    $ django-admin startproject travelog_api ./
    
  • Run migrations and run the server:
    $ ./manage.py migrate
    $ ./manage.py runserver
    $ # go to http://localhost:8000 in the browser
    

Run Postgres in Docker

(Still in the travelog-api directory with virtualenv activated)

  • Install Docker for Mac
  • Create a new file, travelog-api/docker-compose.yml:
    version: "3.7"
    services:
      db:
        image: "postgres:11.2"
        container_name: "travelog_postgres1"
        ports:
          - "54321:5432"
        volumes:
          - postgres_data1:/var/lib/postgresql/data
    volumes:
      postgres_data1:
        name: travelog_postgres_data1
  • Start Postgres
    $ docker-compose up -d
    $ docker-compose logs
    

Create a database

  • Start psql:
    $ docker exec -it travelog_postgres1 psql -U postgres
    
  • Create a database (be sure to include the semicolon):
    postgres=# create database travelog;
  • Create user:
    postgres=# create user traveloguser with password 'mypassword';
    postgres=# grant all privileges on database travelog to traveloguser;
  • Exit psql:
    postgres=# \q

Configure Django to use Postgres

(Still in the travelog-api directory with virtualenv activated)

  • Install psycopg2 2.7.7:
    $ pip install psycopg2-binary
    
  • Edit travelog-api/travelog_api/settings.py:
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.postgresql_psycopg2",
            "NAME": "travelog",
            "USER": "traveloguser",
            "PASSWORD": "mypassword,
            "HOST": "localhost",
            "PORT": "54321",
        }
    }
    
  • Run database migrations and run the server:
    $ ./manage.py migrate
    $ ./manage.py runserver
    $ # go to http://localhost:8000 in the browser
    

Install and configure Graphene

(Still in the travelog-api directory with virtualenv activated)

  • Install graphene-django 2.2.0
    $ pip install graphene-django
    
  • Edit the INSTALLED_APPS setting in travelog-api/travelog_api/settings.py:
    INSTALLED_APPS = (
        # After the default packages
        "graphene_django",
    )
    

Create a new Django app and add a model

(Still in the travelog-api directory with virtualenv activated)

  • Create a new Django app:
    $ ./manage.py startapp geo
    
  • Edit travelog_api/settings.py:
    INSTALLED_APPS = (
        # After the default packages
        'graphene_django',
        'geo',
    )
    
  • Edit travelog-api/geo/models.py:
    from django.db.models import DateTimeField, FloatField, Model, TextField
    
    class Location(Model):
        created_at = DateTimeField(auto_now_add=True)
        lat = FloatField()
        lon = FloatField()
        name = TextField(blank=True)
        updated_at = DateTimeField(auto_now=True)
    
  • Make and run migrations:
    $ ./manage.py makemigrations
    $ ./manage.py migrate
    

GraphQL all the things

(Still in the travelog-api directory with virtualenv activated)

  • Create a new file travelog-api/geo/schema.py:
    import graphene
    from graphene_django.types import DjangoObjectType
    from .models import Location
    
    class LocationType(DjangoObjectType):
        class Meta:
            model = Location
    
    class Query(object):
        all_locations = graphene.List(LocationType)
    
        def resolve_all_locations(self, info, **kwargs):
            return Location.objects.all()
    
    class CreateLocation(graphene.Mutation):
        location = graphene.Field(LocationType)
    
        class Arguments:
            lat = graphene.Float()
            lon = graphene.Float()
            name = graphene.String()
    
        def mutate(self, info, lat, lon, name):
            loc = Location(lat=lat, lon=lon, name=name)
            loc.save()
            return CreateLocation(location=loc)
    
    class Mutation(graphene.ObjectType):
        create_location = CreateLocation.Field()
    
  • Create a new file travelog-api/travelog_api/schema.py:
    import graphene
    import geo.schema
    
    class Query(geo.schema.Query, graphene.ObjectType):
        pass
    
    class Mutation(geo.schema.Mutation, graphene.ObjectType):
        pass
    
    schema = graphene.Schema(query=Query, mutation=Mutation)
    
  • Edit travelog-api/travelog_api/urls.py:
    from django.contrib import admin
    from django.urls import path
    from graphene_django.views import GraphQLView
    from .schema import schema
    
    urlpatterns = [
        path("admin/", admin.site.urls),
        path("graphql/", GraphQLView.as_view(graphiql=True, schema=schema)),
    ]
    

Try it using the GraphiQL explorer

(Still in the travelog-api directory with virtualenv activated)

  • Run the server:
    $ ./manage.py runserver
    
  • Go to http://localhost:8000/graphql/ and you should see the GraphiQL interactive GraphQL explorer.
  • Create a location. Enter this mutation in the left pane and hit CTRL+ENTER:
    mutation {
      createLocation(name: "my first location", lat: 1, lon: 2) {
        location {
          id
        }
      }
    }
    See the response:
    {
      "data": {
        "createLocation": {
          "location": {
            "id": "1"
          }
        }
      }
    }
  • Query all locations:
    query {
      allLocations {
        createdAt
        id
        lat
        lon
        name
      }
    }
    See the response:
    {
      "data": {
        "allLocations": [
          {
            "createdAt": "2019-02-22T06:39:08.512197+00:00",
            "id": "1",
            "lat": 1,
            "lon": 2,
            "name": "my first location"
          }
        ]
      }
    }

References / See also

Comments