Building GraphQL APIs with Graphene-Django
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:
- Mobile apps with bandwidth constraints
- Complex data relationships
- Multiple clients with different data needs
- Rapid frontend iteration
Stick with REST when:
- Simple CRUD operations
- Caching is critical (REST caching is simpler)
- Team is unfamiliar with GraphQL
- Public APIs (more standard tooling)
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.