Building GraphQL APIs with Graphene-Django

python backend django graphql

REST has served us well, but it has limitations. Over-fetching, under-fetching, multiple round trips. GraphQL promises to solve these problems with a single, flexible endpoint.

Here’s how to add GraphQL to your Django project with Graphene.

Why GraphQL?

Consider a mobile app showing a user profile with their recent posts:

REST approach:

GET /api/users/123/          → User data
GET /api/users/123/posts/    → User's posts
GET /api/posts/456/comments/ → Comments on each post

Three requests minimum. And you’re probably fetching fields you don’t need.

GraphQL approach:

query {
  user(id: 123) {
    name
    email
    posts(first: 5) {
      title
      commentCount
    }
  }
}

One request. Exactly the fields you need.

Setting Up Graphene-Django

pip install graphene-django
# settings.py
INSTALLED_APPS = [
    ...
    'graphene_django',
]

GRAPHENE = {
    'SCHEMA': 'myapp.schema.schema',
}
# urls.py
from django.urls import path
from graphene_django.views import GraphQLView

urlpatterns = [
    path('graphql/', GraphQLView.as_view(graphiql=True)),
]

Defining Types

GraphQL types map to your Django models:

# schema.py
import graphene
from graphene_django import DjangoObjectType
from .models import Author, Book

class AuthorType(DjangoObjectType):
    class Meta:
        model = Author
        fields = ('id', 'name', 'email', 'books')

class BookType(DjangoObjectType):
    class Meta:
        model = Book
        fields = ('id', 'title', 'published_date', 'author')

Queries

class Query(graphene.ObjectType):
    all_books = graphene.List(BookType)
    book = graphene.Field(BookType, id=graphene.Int(required=True))
    all_authors = graphene.List(AuthorType)
    
    def resolve_all_books(self, info):
        return Book.objects.select_related('author').all()
    
    def resolve_book(self, info, id):
        try:
            return Book.objects.get(pk=id)
        except Book.DoesNotExist:
            return None
    
    def resolve_all_authors(self, info):
        return Author.objects.prefetch_related('books').all()

schema = graphene.Schema(query=Query)

Now you can query:

query {
  allBooks {
    title
    author {
      name
    }
  }
}

Mutations

Create, update, delete operations:

class CreateBook(graphene.Mutation):
    class Arguments:
        title = graphene.String(required=True)
        author_id = graphene.Int(required=True)
    
    book = graphene.Field(BookType)
    
    def mutate(self, info, title, author_id):
        author = Author.objects.get(pk=author_id)
        book = Book.objects.create(title=title, author=author)
        return CreateBook(book=book)

class Mutation(graphene.ObjectType):
    create_book = CreateBook.Field()

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

Usage:

mutation {
  createBook(title: "New Book", authorId: 1) {
    book {
      id
      title
    }
  }
}

Filtering and Pagination

Use django-filter for filtering:

pip install django-filter
from graphene_django.filter import DjangoFilterConnectionField

class BookType(DjangoObjectType):
    class Meta:
        model = Book
        fields = '__all__'
        filter_fields = {
            'title': ['exact', 'icontains'],
            'published_date': ['gte', 'lte'],
            'author__name': ['exact', 'icontains'],
        }
        interfaces = (graphene.relay.Node,)

class Query(graphene.ObjectType):
    books = DjangoFilterConnectionField(BookType)

Query with filters:

query {
  books(title_Icontains: "python", first: 10) {
    edges {
      node {
        title
        publishedDate
      }
    }
  }
}

Authentication

Access the request in resolvers:

def resolve_my_profile(self, info):
    user = info.context.user
    if user.is_anonymous:
        raise Exception('Not authenticated')
    return user

# Or use decorators
from graphql_jwt.decorators import login_required

@login_required
def resolve_secret_data(self, info):
    return SecretData.objects.all()

For JWT authentication, add graphql-jwt:

pip install django-graphql-jwt

N+1 Query Problem

GraphQL can cause N+1 queries if you’re not careful:

# Bad - N+1 queries
class BookType(DjangoObjectType):
    class Meta:
        model = Book

# Good - use select_related/prefetch_related
def resolve_all_books(self, info):
    return Book.objects.select_related('author').all()

# Better - use dataloaders
from promise import Promise
from promise.dataloader import DataLoader

class AuthorLoader(DataLoader):
    def batch_load_fn(self, author_ids):
        authors = {a.id: a for a in Author.objects.filter(id__in=author_ids)}
        return Promise.resolve([authors.get(id) for id in author_ids])

Error Handling

class CreateBook(graphene.Mutation):
    class Arguments:
        title = graphene.String(required=True)
    
    book = graphene.Field(BookType)
    errors = graphene.List(graphene.String)
    
    def mutate(self, info, title):
        if len(title) < 3:
            return CreateBook(book=None, errors=['Title too short'])
        
        book = Book.objects.create(title=title)
        return CreateBook(book=book, errors=[])

GraphQL vs REST: When to Choose

Choose GraphQL when:

Stick with REST when:

Final Thoughts

GraphQL isn’t a REST replacement—it’s an alternative for specific use cases. Graphene-Django makes it easy to add GraphQL alongside your existing REST APIs.

Start with a single endpoint. See if it improves your frontend development velocity. GraphQL shines when client needs drive API design.


Let clients ask for what they need.

All posts