An Intro to GraphQL with Django
Tags: django, graphql, python
Published: 2020-02-10 09:57:50

In this post, I'll be showing us how to implement a basic GraphQL server in Django, and query for data using a built in GraphiQL IDE that comes along with it.

# Requirements

1. pipenv
2. django 2.0 or greater
3. graphene 
4. graphene-django

> Note: This post requires you to have a working knowledge of how Django works,
> if you are still into the basics check out this 
> <a href="https://docs.djangoproject.com/en/3.0/intro/tutorial01/" target="_blank">resource</a> to get more knowledge on the subject.

##### What is GraphQL ?

> GraphQL is a query language that provides a complete and understandable description of the data in your API, giving clients the power to ask for exactly what they need.

Let's lay some ground work before moving further .....
Take a look at this code snippet

```python
from django.http import JsonResponse
from car.models import Car

def list_post(request):

    cars = Car.objects.all()
    data = {
        "cars":list(cars.values())
    }

    return JsonResponse(data=data)
```

This is a basic implementation of a REST API that sends data back to client in JSON format.
Using this we can write seperate CRUD endpoints

```python
def list_post(request):

    cars = Car.objects.all()
    data = {
        "cars":list(cars.values())
    }

    return JsonResponse(data=data)

def create_post(request):

    data = request.POST
    car = Car.objects.create(id=data.id, name=data.name)

    response = {
        "car":{
            'id': car.id,
            'name': car.name
        }
    }
    return JsonResponse(data=response)

def update_post(request, id):
    data = request.POST

    car = Car.objects.get(id=id)
    car.id = data.id
    car.name = data.name
    car.save()

    response = {
        "car":{
            'id': car.id,
            'name': car.name
        }
    }
    return JsonResponse(data=response)

def delete_post(request, id):
    car = Car.objects.get(id=id)
    car.delete()
    return JsonResponse(data=dict(status="Car successfully deleted"))
```

Registering with urls.py we get....

```python
from application import views

urlpatterns = [
    # Previous code here,
    path('posts', views.post_list),
    path('post/delete/<int:id>', views.delete_post),
    path('post/create', views.create_post),
    path('post/update/<int:id>', views.update_post)
]
```

This is a simple REST API implementation giving you four distinct endpoints for performing a basic CRUD operation.

Now, I know that at first this might seem okay to some, but trust me, in the long run, managing different endpoints increases complexity.

Sure, we can try fitting everything into a single request endpoint and handle it all with if-else statements, but in the long run, consistent updates makes your code less readable, since that request endpoint is resolving to perform many roles at the same time.


What if we had a simpler way to describe our data, so when we need to perform CRUD operations, we just send in a description of what we are trying to get / change and receive back exactly what we wanted.

Using just a single endpoint and a type system, we can fetch for as many resources as we want.

This is where GraphQL comes in.......

Declare a type...

```graphql
type Image{
    name: String
    url: String
    width: Int
    height: Int
}
```

Make a request for it 
```
fetchImage(name: "Bike") {
    url
  }
}
```
`## Get exactly what you want ##`

```
{
  "image": {
    "url": "https://google.com"
  }
}
```

>Note here that we can specify other fields and receive back exactly what was been specified, as opposed to a fixed resource of REST API data of which we don't need most values all the time. That's one of the `perks` of using graphql.

Let's build our first Django GraphQL server...

<img src="https://media.giphy.com/media/11sBLVxNs7v6WA/giphy.gif" alt="Happy"/>

If you don't know how to use `pipenv` , here's a <a href="https://pipenv.kennethreitz.org/en/latest/install/#installing-pipenv" target="_blank">great guide</a> you can follow to get started with the basics.


Create project directory `graphql` and move into it.

```
mkdir graphql

cd graphql
```
Install dependencies ....
```
pipenv install django graphene graphene-django
```

> Note: Running the above command generates two files Pipfile and Pipfile.lock, make sure installation runs without errors.

Once installation is done, check if virtual environment is already activated, else run `pipenv shell` to activate it.

Start a new Django project

```
django-admin startproject server
```

Move into the django project and start the development server...

```
cd server

python manage.py runserver

```

```
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work 
properly until you apply the migrations for app(s): admin,
 auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

January 21, 2020 - 03:40:47
Django version 3.0.2, using settings 'server.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

Navigate to `localhost:8000` on your browser, you should see the following

![Django welcome page](https://thepracticaldev.s3.amazonaws.com/i/rzdl8c7iwu6zzia99y5g.png)

Hit `Ctrl-C` to shutdown server.

Now in order for us to make requests to our graphql server, we must declare a schema file, this is how graphql knows what we are querying for or what we are trying to change.

Start a new application

```
python manage.py startapp car
```

In the new app, create a new file called schema.py, and write the following code.

```python
import graphene
from graphene import ObjectType, Schema

class Query(ObjectType):
    car = graphene.String()

    def resolve_car(self, info, root):
        return f"Mercedes Benz | Model:23qwer | Color: Black"

schema = Schema(query=Query)
```

In the code snippet above, we defined a car field that returns a string when queried, note that for the query to work it has to be resolved through a function that takes in the data and gives an output.

Run `python manage.py shell` to open a python shell and type the following:

```python

>>> from car.schema import schema

>>> query = "{ car }"
>>> result = schema.execute(query)
>>> print(result.data['car'])

>>> 'Mercedes Benz | Model:23qwer | Color: Black'
```

This is a basic schema implementation that defines only one field `car` and returns a string when that field is queried on client side.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/cjmgi7qebjhr0q7uuwg7.png)

Create a schema.py file in server folder where settings.py is located and include the following:

```python

from graphene import ObjectType, Schema

from car.schema import schema


class Query(schema.Query, ObjectType):
    pass


schema = Schema(query=Query)

```

Edit settings.py

```python

# Previous code here .....

GRAPHENE = {
    'SCHEMA': 'server.schema.schema',
    'MIDDLEWARE': (
        'graphene_django.debug.DjangoDebugMiddleware',
    )
}
```
Add `car` and `graphene_django` to INSTALLED_APPS, then update urls.py in server folder

```python
from django.contrib import admin
from django.urls import path

from graphene_django.views import GraphQLView

urlpatterns = [
    # Previous code here ....
    path('graphql', GraphQLView.as_view(graphiql=True)),
]
```

In your console, migrate and then start the development server


```bash
python manage.py migrate

python manage.py runserver
```

Navigate to `localhost:8000/graphql`, you'll be presented with a graphql IDE.

Let's make a query.

```graphql
query{
    car
}
```

Click on the play button, the server returns a response containing the exact string gotten earlier when we queried it with the shell.

```
{
  "data": {
    "car": "Mercedes Benz | Model:23qwer | Color: Black"
  }
}
```

With this knowledge, let's build a basic car model in django, write queries and test it out in graphql IDE.

Edit models.py file in the car application

```python
class Car(models.Model):
    name = models.CharField(max_length=40)
    model = models.CharField(max_length=40)
    price = models.IntegerField()
```

Create a folder named fixtures in car application folder, add data.json to it and paste the following code snippet.

```json
[
  {
    "model":"car.Car",
    "pk":1,
    "fields":{
        "name": "Tesla Motors",
        "model": "Model S",
	"price": 80000
    }
  },
  {
    "model":"car.Car",
    "pk":2,
    "fields":{
        "name": "Aston Martin",
        "model": "Vantage N430",
	"price": 90000
    }
  },
  {
    "model":"car.Car",
    "pk":3,
    "fields":{
        "name": "General Motors",
        "model": "Cadillac Escalade",
	"price": 79000
    }
  },
  {
    "model":"car.Car",
    "pk":3,
    "fields":{
        "name": "Toyota",
        "model": "Prius V",
	"price": 31700
    }
  }
]
```

Migrate application

```python manage.py makemigrations```

```python manage.py migrate```

Load the fixtures into database

```python manage.py loadfixtures data.json```

Now, let's create a new field to fetch all cars from the database, edit your car application schema.py file


```python
# Previous code here

from graphene_django import DjangoObjectType
from car.models import Car


class CarType(DjangoObjectType):
    class Meta:
        model = Car

class Query(ObjectType):
    # Previous fields here ...
    all_cars = graphene.List(CarType)

    # Previous resolver function
    def resolve_all_cars(self, info, **kwargs):
        return Car.objects.all() 
```


Django comes with it's own ObjectType so displaying and resolving model fields would be made easier. 
We could create our own ObjectType to mimic fields in django models, but constant updates to model fields require updating our defined ObjectType and that becomes a hassle in the long run.

Note we defined a field `all_cars` which returns a list of all cars in our database. 
To view more scalar types we could use in our graphql application, go <a target="_blank" href="https://docs.graphene-python.org/en/latest/types/">here</a>. 

Start your development server and navigate to `localhost:8000/graphql`

Let's query for all the cars in the database.

```graphql
query{
  allCars{
    name
    model
    price
  }
}
```
```
{
  "data": {
    "allCars": [
      {
        "name": "Tesla Motors",
        "model": "Model S",
        "price": 80000
      },
      {
        "name": "Aston Martin",
        "model": "Vantage N430",
        "price": 90000
      },
      {
        "name": "General Motors",
        "model": "Cadillac Escalade",
        "price": 79000
      },
      {
        "name": "Toyota",
        "model": "Prius V",
        "price": 31700
      }
    ]
  }
}
```

Let's update our schema file, update car field and resolver method to return a single car type

```python
class Query(ObjectType):
    car = graphene.Field(CarType, id=graphene.Int())
    all_cars = graphene.List(CarType)

    def resolve_car(self, info, **kwargs):
        id = kwargs.get("id")
        if id is not None:
            return Car.objects.get(id=id)

    def resolve_all_cars(self, info, **kwargs):
        return Car.objects.all()
```

Now query for a car in the database.

```graphql
query{
  car(id: 1){
    name
    model
    price
  }
}
```

It gives you a single car object.


Query this same endpoint multiple times with different data types, it gives you exactly what you asked for..

That's the power of graphql


## Mutation

Mutations in graphql are just like queries but they alter data.

Create an object, Update an object, Delete an object, all these are mutations we are making to our data.


Let's add a Mutation to add new Car type to our database

```python
class CarInput(graphene.InputObjectType):
    name = graphene.String()
    model = graphene.String()
    price = graphene.Int()


class AddCar(graphene.Mutation):
    class Arguments:
        input = CarInput(required=True)

    car = graphene.Field(CarType)

    @staticmethod
    def mutate(root, info, input=None):
        car = Car(
            name=input.name, 
            model=input.model, 
            price=input.price)
        car.save()
        return AddCar(car=car)

class Mutation(graphene.Mutation):
    add_car = AddCar.Field()

schema = Schema(query=Query, mutation=Mutation)
```

Update schema.py in project directory ...

```python

class Mutation(schema.Mutation, graphene.ObjectType):
	pass


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

```

Start development server, let's add a new car to our database.


```graphql
mutation {
  addCar(input: {name: "Volkswagen", model: "RED23X", price: 50000}) {
    car {
      name
      model
      price
    }
  }
}

```

In the mutation specified above, you can see that the addCar field accepted just one argument, `input` as specified in the `Arguments` class subclassed in the AddCar Mutation Type. 

If there are no errors, you would see the car you just created returned back as an object.

Now if we query for that same car, we should see the data returned back as an object.

```graphql
query{
  car(id: 4){
    name
    model
    price
  }
}
```


> GraphQL is a very powerful query language for APIs, it's simplicity and the way you are able comprehend data in your API is off the charts.

You ask for a cat, you get exactly that, you ask for a dog with 2 tails, hell you get that too 😂, you can send multiple queries in one query and still graphql finds a way to resolve your data and give you exactly what you asked for, no growing `endpoints`, no need for `redundant` data, no stress 😎.

REST is good, GraphQL is better 😏.

Graphql has it's own minor problems, but that's how it is with api patterns,feel free to explore and search for better ways to improve your data structure and fetching. 

Thank you for staying till the end of this article, I really appreciate you taking out your time to go through this basic tutorial and I hope you are able to implement a basic GraphQL server on your own using django.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/48ut3sod2gvsuhqv9vyp.gif)

## Resources

1. <a href="https://docs.graphene-python.org/projects/django/en/latest/">Graphene Django</a>

2. <a href="https://graphql.org">GraphQL</a>