GraphQL APIs with Graphene
Reviewed & published by Brayan K
GraphQL is a query language for your API that lets clients ask for exactly the data they need from a single endpoint, and Graphene with graphene-django is the standard way to add it to a Django project — mapping your models to GraphQL types and letting you serve precise, nested responses.
Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll expose a model with DjangoObjectType, build a Query class with resolvers, add a mutation that changes data, wire GraphQLView into urls.py, and explore it all in the GraphiQL browser IDE.
What You'll Learn in This Lesson
- How GraphQL differs from REST
- Mapping a model with DjangoObjectType
- Building a Query class and a Schema
- Writing resolvers like resolve_all_posts
- Adding a mutation to change data
- Wiring GraphQLView and using GraphiQL
1 GraphQL vs REST, and DjangoObjectType
A REST API has many endpoints, each returning a fixed shape. With GraphQL there is a single endpoint, and the client sends a query naming exactly the fields it wants — no over-fetching, no under-fetching. In Django you add this with the graphene-django library.
The first building block is DjangoObjectType. Subclass it and point its inner Meta class at a model, and Graphene generates the matching GraphQL type automatically.
# blog/schema.py
import graphene
from graphene_django import DjangoObjectType
from blog.models import Post
class PostType(DjangoObjectType):
class Meta:
model = Post # maps the Post model to a GraphQL type
fields = ("id", "title", "body")That is the GraphQL counterpart of DRF's ModelSerializer: a few lines turn a model into something the API can return.
# A GraphQL client asks for ONLY the fields it wants. REST usually
# returns everything. We'll model both to see the difference.
post = {"id": 1, "title": "Hello", "body": "Full body text here", "author": "Alice"}
# REST: a fixed endpoint returns the whole object
def rest_get_post():
return post
# GraphQL: the client names the fields, and gets exactly those
def graphql_query(fields):
return {f: post[f] for f in fields if f in post}
print("REST returns: ", rest_get_post())
print("GraphQL (title):", graphql_query(["title"]))
print("GraphQL (id,body):", graphql_query(["id", "body"]))
# ✅ Expected output:
# REST returns: {'id': 1, 'title': 'Hello', 'body': 'Full body text here', 'author': 'Alice'}
# GraphQL (title): {'title': 'Hello'}
# GraphQL (id,body): {'id': 1, 'body': 'Full body text here'}2 Query, Resolvers, and the Schema
Next you define a root Query class. Each field on it is a read entry point, and each field is backed by a resolver — a method named resolve_<field_name> that returns the data. Finally you build a Schema from the query.
# blog/schema.py
import graphene
from graphene_django import DjangoObjectType
from blog.models import Post
class PostType(DjangoObjectType):
class Meta:
model = Post
fields = ("id", "title", "body")
class Query(graphene.ObjectType):
all_posts = graphene.List(PostType)
def resolve_all_posts(root, info):
# The resolver returns the data for the all_posts field
return Post.objects.all()
schema = graphene.Schema(query=Query)A client could then run this query against the endpoint:
query {
allPosts {
id
title
}
}# Graphene finds the resolver for a field by name: field "all_posts"
# is served by the method "resolve_all_posts". Let's simulate that.
posts = [
{"id": 1, "title": "Hello"},
{"id": 2, "title": "GraphQL is great"},
]
class Query:
def resolve_all_posts(self, info):
# In Django this would be Post.objects.all()
return posts
def run_field(query_obj, field_name):
# Graphene maps "all_posts" -> "resolve_all_posts"
resolver = getattr(query_obj, "resolve_" + field_name)
return resolver(info=None)
result = run_field(Query(), "all_posts")
for p in result:
print(p["id"], p["title"])
# ✅ Expected output:
# 1 Hello
# 2 GraphQL is great3 Mutations and Wiring Up GraphiQL
Queries read data; mutations change it. A mutation declares its inputs as Arguments, runs a mutate method, and returns the affected object. Then you wire the endpoint into urls.py with GraphQLView, enabling graphiql=True for the in-browser IDE.
# blog/schema.py
class CreatePost(graphene.Mutation):
class Arguments:
title = graphene.String(required=True)
body = graphene.String(required=True)
post = graphene.Field(PostType)
def mutate(root, info, title, body):
post = Post.objects.create(title=title, body=body)
return CreatePost(post=post)
class Mutation(graphene.ObjectType):
create_post = CreatePost.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)# project/urls.py
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from blog.schema import schema
urlpatterns = [
# graphiql=True serves the in-browser GraphiQL IDE at /graphql/
path("graphql/", csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))),
]In GraphiQL you could now run the mutation:
mutation {
createPost(title: "New", body: "From GraphiQL") {
post { id title }
}
}# A mutation takes arguments, changes data, and returns the result.
# Here our "database" is a list and the mutation appends to it.
posts = []
next_id = 1
def create_post(title, body):
"""Mimics a Graphene mutation's mutate() method."""
global next_id
post = {"id": next_id, "title": title, "body": body}
posts.append(post)
next_id += 1
return post # the mutation returns the created object
created = create_post("New", "From GraphiQL")
print("Created:", created)
print("Total posts now:", len(posts))
create_post("Second", "Another body")
print("All titles:", [p["title"] for p in posts])
# ✅ Expected output:
# Created: {'id': 1, 'title': 'New', 'body': 'From GraphiQL'}
# Total posts now: 1
# All titles: ['New', 'Second']🎯 Your Turn: Name the Resolver
Fill in the blank so the resolver method follows Graphene's naming convention for the field all_posts.
# 🎯 YOUR TURN: rename the method so Graphene can resolve the
# "all_posts" field. The convention is resolve_<field_name>.
posts = [{"id": 1, "title": "Hello"}]
class Query:
# Replace ___ so this becomes the resolver for "all_posts"
def ___(self, info):
return posts
# Graphene would call resolve_all_posts; we call it the same way here
print(getattr(Query(), "resolve_all_posts")(info=None))
# EXPECTED OUTPUT:
# [{'id': 1, 'title': 'Hello'}]Common Errors (And How to Fix Them)
❌ Cannot query field 'allPosts' on type 'Query'
The field is not declared on your Query class, or you queried the snake_case name instead of the camelCased one.
✅ Fix: declare all_posts = graphene.List(PostType) and query it as allPosts.
❌ My resolver never runs / returns null
The method name does not match the field, so Graphene falls back to a default resolver that finds nothing.
✅ Fix: name it exactly resolve_<field_name>, e.g. resolve_all_posts.
❌ CSRF or 403 when posting a query
The GraphQLView rejected the request because of CSRF protection.
✅ Fix: wrap the view with csrf_exempt(...) in development, or send a proper CSRF token from your client.
📋 Quick Reference
| Piece | What It Does |
|---|---|
| DjangoObjectType | Maps a Django model to a GraphQL type |
| Query | Declares the read entry points (fields) |
| resolve_<field> | Resolver method returning a field's data |
| graphene.Mutation | Defines an operation that changes data |
| GraphQLView.as_view(graphiql=True) | Serves the endpoint and the GraphiQL IDE |
🏆 Mini Challenge: A Field-Selecting Resolver
Build a tiny query engine: a resolver returns all posts, and a client requests only the fields it wants — the heart of how GraphQL serves precise responses.
# 🏆 Mini Challenge: a resolver plus a client-driven field selection.
class Query:
def resolve_all_posts(self, info):
return [
{"id": 1, "title": "Hello", "body": "First", "author": "Alice"},
{"id": 2, "title": "GraphQL", "body": "Second", "author": "Bob"},
]
def run_query(query_obj, requested_fields):
rows = query_obj.resolve_all_posts(info=None)
# Return only the fields the client asked for
return [{f: row[f] for f in requested_fields} for row in rows]
print(run_query(Query(), ["id", "title"]))
print(run_query(Query(), ["title", "author"]))
# EXPECTED:
# [{'id': 1, 'title': 'Hello'}, {'id': 2, 'title': 'GraphQL'}]
# [{'title': 'Hello', 'author': 'Alice'}, {'title': 'GraphQL', 'author': 'Bob'}]❓ Frequently Asked Questions
Lesson complete — your data is now queryable with GraphQL!
You can map a model with DjangoObjectType, build a Query and Schema, write resolvers like resolve_all_posts, add a Mutation that changes data, and wire GraphQLView.as_view(graphiql=True) into urls.py to explore it in GraphiQL.
Practice quiz
Which library adds GraphQL support to Django by integrating with Graphene?
- graphene-django
- django-rest-framework
- django-graphql-jwt only
- django-ariadne
Answer: graphene-django. graphene-django is the official integration that connects the Graphene GraphQL library to Django models and views.
What is the main advantage of GraphQL over a typical REST endpoint?
- It is always faster than REST regardless of the query
- It removes the need for a database
- The client asks for exactly the fields it needs in a single request, avoiding over- and under-fetching
- It only works with PostgreSQL
Answer: The client asks for exactly the fields it needs in a single request, avoiding over- and under-fetching. A GraphQL client specifies precisely which fields it wants, so it avoids over-fetching and under-fetching common with fixed REST responses.
Which Graphene class do you subclass to expose a Django model as a GraphQL type?
- graphene.ObjectType
- DjangoObjectType
- ModelSerializer
- DjangoModelType
Answer: DjangoObjectType. DjangoObjectType from graphene_django maps a Django model's fields onto a GraphQL type automatically.
Inside a DjangoObjectType, where do you name the Django model it maps to?
- In a model_name attribute
- By passing it to __init__
- In the urls.py route
- In an inner Meta class via model = ...
Answer: In an inner Meta class via model = .... You set the mapped model in an inner Meta class, for example class Meta: model = Post.
What is the role of the root Query class in a Graphene schema?
- It defines the read entry points (fields) clients can query
- It renders HTML templates
- It configures middleware
- It runs database migrations
Answer: It defines the read entry points (fields) clients can query. The Query class declares the fields that serve as the read entry points of your API.
What is a resolver in Graphene?
- A database index
- A method that returns the data for a particular field
- A serializer validator
- A URL routing rule
Answer: A method that returns the data for a particular field. A resolver is a method like resolve_all_posts that returns the data for the field it backs.
By convention, what is the resolver method for a field named all_posts called?
- get_all_posts
- all_posts_resolver
- field_all_posts
- resolve_all_posts
Answer: resolve_all_posts. Graphene looks for a method named resolve_<field_name>, so all_posts is resolved by resolve_all_posts.
In GraphQL, what is an operation that changes data (create, update, delete) called?
- A transaction
- A signal
- A mutation
- A migration
Answer: A mutation. Write operations are defined as mutations, separate from read-only queries.
What does the GraphiQL interface provide?
- A production caching layer
- An in-browser IDE for writing and running GraphQL queries against your schema
- An automatic admin site
- A migration generator
Answer: An in-browser IDE for writing and running GraphQL queries against your schema. GraphiQL is an interactive in-browser tool for exploring the schema and running queries while developing.
How is the GraphQL endpoint typically wired into urls.py?
- path('graphql/', GraphQLView.as_view(graphiql=True))
- include('graphene.urls')
- path('graphql/', schema.run)
- GraphQLView is added to MIDDLEWARE
Answer: path('graphql/', GraphQLView.as_view(graphiql=True)). You map a path to GraphQLView.as_view(graphiql=True), passing your schema so the view can execute queries.
Continue this course
- Previous: Internationalization (i18n & l10n)
- Next: Full-Text Search & Docker Deployment — Postgres SearchVector/SearchRank, GIN indexes, then ship with a Dockerfile + gunicorn
- Quick reference: Django cheat sheet
Frequently asked questions
How is GraphQL different from a REST API?
With REST you have many fixed endpoints, and each one returns a predetermined shape of data. If you only need a post's title you still receive the whole post object (over-fetching), and if you need related data you often have to call several endpoints (under-fetching). GraphQL exposes a single endpoint and lets the client send a query describing exactly the fields it wants, nested as deeply as needed, and the server returns precisely that shape in one round trip. This makes GraphQL especially handy for mobile clients and complex UIs where bandwidth and request count matter.
What does DjangoObjectType do?
DjangoObjectType, from the graphene_django package, is a bridge between a Django model and a GraphQL type. When you subclass it and point its inner Meta class at a model, Graphene inspects that model's fields and automatically generates the matching GraphQL fields, so you do not have to declare each one by hand. It is the GraphQL equivalent of DRF's ModelSerializer: write a few lines, and your model becomes queryable through the schema.
What is a resolver and when do I need to write one?
A resolver is the function that produces the data for a particular field in your schema. Graphene looks for a method named resolve_<field_name> on your Query (or type) class. For a field called all_posts, it calls resolve_all_posts, which typically returns a queryset like Post.objects.all(). For simple fields that map directly to a model attribute, Graphene supplies a default resolver automatically, so you only write resolvers when you need custom logic, filtering, or a queryset that is not a plain attribute lookup.
How do clients actually try out my GraphQL API?
When you mount GraphQLView with graphiql=True, visiting the endpoint in a browser opens GraphiQL, an interactive IDE built into the page. It shows your schema's documentation, autocompletes field names as you type, and lets you run queries and mutations and see the JSON response immediately. It is the fastest way to explore an API while you build it. In production you usually disable GraphiQL and have your frontend send queries to the same endpoint programmatically.