Introduction to GraphQL and Graphene
GraphQL — is a modern query language for APIs and a runtime, developed by Facebook in 2012 to solve the problems of traditional REST APIs. Graphene — is a powerful Python library that provides a full implementation of the GraphQL specification with support for typed schemas, mutations, and integration with popular ORM systems.
What is GraphQL and why you need it
GraphQL is a declarative approach to data fetching, where the client precisely describes the data it needs. Unlike REST, where the server defines the response structure, GraphQL allows the client to shape the request and receive exactly the data required.
Key advantages of GraphQL over REST
Single entry point
GraphQL uses a single URL endpoint for all operations, simplifying API architecture and eliminating the need for versioning via URLs.
Query flexibility
The client defines the shape of the response, requesting only the necessary fields and related data.
Elimination of data redundancy
Over-fetching (getting unnecessary data) and under-fetching (getting insufficient data) are resolved at the query level.
Combining related data
Ability to retrieve data from multiple related entities in a single request, reducing the number of server round‑trips.
Strong typing
The GraphQL schema is statically typed, providing request validation and IDE auto‑completion.
Automatic documentation
The schema serves as live API documentation, accessible via introspection.
Installation and setup of Graphene
Basic installation
pip install graphene
Integration with web frameworks
For Flask:
pip install flask-graphql
For Django:
pip install graphene-django
For FastAPI:
pip install starlette-graphene3
Database integration
For SQLAlchemy:
pip install graphene-sqlalchemy
For Django ORM:
pip install graphene-django
Creating a basic GraphQL server
Flask example
from flask import Flask
from flask_graphql import GraphQLView
import graphene
app = Flask(__name__)
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="world"))
def resolve_hello(self, info, name):
return f"Hello, {name}!"
schema = graphene.Schema(query=Query)
app.add_url_rule('/graphql', view_func=GraphQLView.as_view(
'graphql',
schema=schema,
graphiql=True # Enables the GraphiQL interface
))
if __name__ == '__main__':
app.run(debug=True)
Django example
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'graphene_django',
'myapp',
]
GRAPHENE = {
'SCHEMA': 'myproject.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 and schemas in Graphene
Creating custom types
import graphene
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
email = graphene.String()
age = graphene.Int()
is_active = graphene.Boolean()
# Computed fields
full_name = graphene.String()
def resolve_full_name(self, info):
return f"{self.first_name} {self.last_name}"
class Post(graphene.ObjectType):
id = graphene.ID()
title = graphene.String()
content = graphene.String()
author = graphene.Field(User)
created_at = graphene.DateTime()
Defining the query schema
class Query(graphene.ObjectType):
user = graphene.Field(User, id=graphene.ID(required=True))
users = graphene.List(User, limit=graphene.Int(default_value=10))
def resolve_user(self, info, id):
# Logic for fetching a user by ID
return User(id=id, name="Ivan Petrov", email="ivan@example.com")
def resolve_users(self, info, limit):
# Logic for fetching a list of users
return [User(id=i, name=f"User {i}") for i in range(1, limit+1)]
Queries: structure and handling
Simple queries
{
user(id: "1") {
id
name
email
}
}
Queries with aliases
{
firstUser: user(id: "1") {
name
}
secondUser: user(id: "2") {
name
}
}
Nested queries
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
posts = graphene.List(lambda: Post)
def resolve_posts(self, info):
# Fetch user posts
return [Post(title="First post"), Post(title="Second post")]
{
user(id: "1") {
name
posts {
title
content
}
}
}
Mutations: modifying data
Creating a mutation
class CreateUser(graphene.Mutation):
class Arguments:
name = graphene.String(required=True)
email = graphene.String(required=True)
# Return fields
ok = graphene.Boolean()
user = graphene.Field(User)
errors = graphene.List(graphene.String)
def mutate(self, info, name, email):
# Validation
if not email or '@' not in email:
return CreateUser(ok=False, errors=["Invalid email"])
# Create user
user = User(name=name, email=email)
return CreateUser(ok=True, user=user)
class UpdateUser(graphene.Mutation):
class Arguments:
id = graphene.ID(required=True)
name = graphene.String()
email = graphene.String()
user = graphene.Field(User)
def mutate(self, info, id, name=None, email=None):
# Update logic
user = User(id=id, name=name, email=email)
return UpdateUser(user=user)
class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
update_user = UpdateUser.Field()
Executing a mutation
mutation {
createUser(name: "Ivan Petrov", email: "ivan@example.com") {
ok
user {
id
name
email
}
errors
}
}
Working with arguments in queries and mutations
Argument types
class Query(graphene.ObjectType):
users = graphene.List(
User,
first=graphene.Int(default_value=10),
skip=graphene.Int(default_value=0),
search=graphene.String(),
is_active=graphene.Boolean(default_value=True)
)
def resolve_users(self, info, first, skip, search=None, is_active=True):
# Filtering and pagination logic
users = [] # Fetch from database
if search:
users = [u for u in users if search.lower() in u.name.lower()]
if is_active is not None:
users = [u for u in users if u.is_active == is_active]
return users[skip:skip+first]
Using InputObjectType
class UserInput(graphene.InputObjectType):
name = graphene.String(required=True)
email = graphene.String(required=True)
age = graphene.Int()
class CreateUserWithInput(graphene.Mutation):
class Arguments:
user_data = UserInput(required=True)
user = graphene.Field(User)
def mutate(self, info, user_data):
user = User(**user_data)
return CreateUserWithInput(user=user)
Resolvers: data handling logic
Standard resolvers
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
email = graphene.String()
posts_count = graphene.Int()
def resolve_posts_count(self, info):
# Count user posts
return len(getattr(self, 'posts', []))
def resolve_email(self, info):
# Access control
user = info.context.get('user')
if user and user.id == self.id:
return self.email
return None # Hide email from other users
Asynchronous resolvers
import asyncio
class Query(graphene.ObjectType):
user = graphene.Field(User, id=graphene.ID())
async def resolve_user(self, info, id):
# Asynchronous data fetching
await asyncio.sleep(0.1) # Simulate DB call
return User(id=id, name="Async user")
Database connectivity
Integration with SQLAlchemy
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from graphene_sqlalchemy import SQLAlchemyObjectType
Base = declarative_base()
class UserModel(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
created_at = Column(DateTime)
class User(SQLAlchemyObjectType):
class Meta:
model = UserModel
interfaces = (graphene.relay.Node,)
class Query(graphene.ObjectType):
users = graphene.List(User)
def resolve_users(self, info):
query = User.get_query(info)
return query.all()
Integration with Django ORM
from django.db import models
from graphene_django import DjangoObjectType
class UserModel(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
created_at = models.DateTimeField(auto_now_add=True)
class User(DjangoObjectType):
class Meta:
model = UserModel
fields = ("id", "name", "email", "created_at")
posts_count = graphene.Int()
def resolve_posts_count(self, info):
return self.posts.count()
Working with nested objects and relationships
Defining relationships
class Post(graphene.ObjectType):
id = graphene.ID()
title = graphene.String()
content = graphene.String()
author = graphene.Field(lambda: User)
tags = graphene.List(lambda: Tag)
def resolve_author(self, info):
return User(id=self.author_id, name="Post author")
def resolve_tags(self, info):
return [Tag(name="Python"), Tag(name="GraphQL")]
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
posts = graphene.List(Post)
def resolve_posts(self, info):
return [Post(id=1, title="First post", author_id=self.id)]
Solving the N+1 query problem
from promise import Promise
from promise.dataloader import DataLoader
class UserLoader(DataLoader):
def batch_load_fn(self, user_ids):
# Load all users in a single query
users = UserModel.objects.filter(id__in=user_ids)
user_map = {user.id: user for user in users}
return Promise.resolve([user_map.get(user_id) for user_id in user_ids])
class Post(graphene.ObjectType):
author = graphene.Field(User)
def resolve_author(self, info):
return info.context['user_loader'].load(self.author_id)
Using Graphene‑Django
Project setup
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'graphene_django',
'myapp',
]
GRAPHENE = {
'SCHEMA': 'myproject.schema.schema',
'MIDDLEWARE': [
'graphene_django.debug.DjangoDebugMiddleware',
],
}
Creating the schema
# schema.py
import graphene
from graphene_django import DjangoObjectType
from .models import User, Post
class UserType(DjangoObjectType):
class Meta:
model = User
fields = "__all__"
class PostType(DjangoObjectType):
class Meta:
model = Post
fields = "__all__"
class Query(graphene.ObjectType):
users = graphene.List(UserType)
posts = graphene.List(PostType)
def resolve_users(self, info):
return User.objects.all()
def resolve_posts(self, info):
return Post.objects.select_related('author').all()
schema = graphene.Schema(query=Query)
Integration with Flask and FastAPI
Flask with GraphQL
from flask import Flask, request, jsonify
from flask_graphql import GraphQLView
import graphene
app = Flask(__name__)
class Query(graphene.ObjectType):
hello = graphene.String()
def resolve_hello(self, info):
user = info.context.get('user')
return f"Hello, {user.name if user else 'guest'}!"
schema = graphene.Schema(query=Query)
def get_context():
return {'user': getattr(request, 'user', None)}
app.add_url_rule('/graphql', view_func=GraphQLView.as_view(
'graphql',
schema=schema,
graphiql=True,
get_context=get_context
))
FastAPI with GraphQL
from fastapi import FastAPI
from starlette_graphene3 import GraphQLApp
import graphene
app = FastAPI()
class Query(graphene.ObjectType):
hello = graphene.String()
def resolve_hello(self, info):
return "Hello from FastAPI!"
schema = graphene.Schema(query=Query)
app.mount("/graphql", GraphQLApp(schema=schema))
Authorization and authentication
Passing context
from flask import g
from functools import wraps
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not g.user:
raise Exception("Authentication required")
return f(*args, **kwargs)
return decorated_function
class Query(graphene.ObjectType):
my_profile = graphene.Field(User)
@login_required
def resolve_my_profile(self, info):
user = info.context.get('user')
return User(id=user.id, name=user.name)
Field‑level access control
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
email = graphene.String()
def resolve_email(self, info):
current_user = info.context.get('user')
if current_user and (current_user.id == self.id or current_user.is_admin):
return self.email
return None
Error handling and validation
Custom exceptions
class ValidationError(Exception):
def __init__(self, message, field=None):
self.message = message
self.field = field
super().__init__(self.message)
class CreateUser(graphene.Mutation):
class Arguments:
name = graphene.String(required=True)
email = graphene.String(required=True)
user = graphene.Field(User)
errors = graphene.List(graphene.String)
def mutate(self, info, name, email):
errors = []
if len(name) < 2:
errors.append("Name must contain at least 2 characters")
if '@' not in email:
errors.append("Invalid email format")
if errors:
return CreateUser(errors=errors)
try:
user = User(name=name, email=email)
return CreateUser(user=user)
except Exception as e:
return CreateUser(errors=[str(e)])
Global error handling
class CustomMiddleware:
def resolve(self, next, root, info, **args):
try:
return next(root, info, **args)
except ValidationError as e:
return {"error": e.message, "field": e.field}
except Exception as e:
return {"error": "Internal server error"}
Testing GraphQL APIs
Testing with pytest
import pytest
import json
from graphene.test import Client
def test_user_query():
client = Client(schema)
query = '''
{
user(id: "1") {
id
name
}
}
'''
result = client.execute(query)
assert result['data']['user']['id'] == "1"
assert result['data']['user']['name'] == "Test user"
def test_create_user_mutation():
client = Client(schema)
mutation = '''
mutation {
createUser(name: "New User", email: "test@example.com") {
ok
user {
name
email
}
}
}
'''
result = client.execute(mutation)
assert result['data']['createUser']['ok'] == True
assert result['data']['createUser']['user']['name'] == "New User"
Testing with Flask
import pytest
from flask import Flask
from flask_graphql import GraphQLView
@pytest.fixture
def app():
app = Flask(__name__)
app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema))
return app
def test_graphql_endpoint(app):
client = app.test_client()
query = {
"query": "{ user(id: \"1\") { name } }"
}
response = client.post('/graphql',
data=json.dumps(query),
content_type='application/json')
assert response.status_code == 200
data = json.loads(response.data)
assert 'data' in data
Performance and optimization
Pagination
class Query(graphene.ObjectType):
users = graphene.List(
User,
first=graphene.Int(default_value=10),
skip=graphene.Int(default_value=0)
)
def resolve_users(self, info, first, skip):
return User.objects.all()[skip:skip+first]
Relay‑compatible pagination
from graphene import relay
class User(graphene.ObjectType):
class Meta:
interfaces = (relay.Node,)
id = graphene.ID()
name = graphene.String()
class Query(graphene.ObjectType):
users = relay.ConnectionField(User)
def resolve_users(self, info, **args):
return User.objects.all()
Caching
from functools import lru_cache
class Query(graphene.ObjectType):
users = graphene.List(User)
@lru_cache(maxsize=128)
def resolve_users(self, info):
return User.objects.all()
Tools and IDEs for working with GraphQL
GraphiQL
Built‑in interface for testing queries:
# Flask
GraphQLView.as_view('graphql', schema=schema, graphiql=True)
# Django
GraphQLView.as_view(graphiql=True)
Additional tools
- GraphQL Playground – modern alternative to GraphiQL
- Postman – GraphQL request support
- Insomnia – specialized REST/GraphQL client
- Apollo Client DevTools – browser extension
- VSCode GraphQL – extensions for auto‑completion and syntax highlighting
Practical use‑case examples
Unified API for different clients
class Query(graphene.ObjectType):
# Full data for web client
user_detailed = graphene.Field(User, id=graphene.ID())
# Minimal data for mobile app
user_mobile = graphene.Field(User, id=graphene.ID())
def resolve_user_detailed(self, info, id):
return User.objects.select_related('profile', 'settings').get(id=id)
def resolve_user_mobile(self, info, id):
return User.objects.only('id', 'name', 'avatar').get(id=id)
Aggregating data from multiple sources
class Query(graphene.ObjectType):
dashboard = graphene.Field(DashboardType, user_id=graphene.ID())
def resolve_dashboard(self, info, user_id):
# Data from the database
user = User.objects.get(id=user_id)
# Data from an external API
external_data = fetch_external_data(user_id)
# Data from cache
cached_stats = get_cached_stats(user_id)
return DashboardType(
user=user,
external_info=external_data,
statistics=cached_stats
)
Complete description of the Graphene library
Main modules and components
| Module | Description |
|---|---|
graphene.ObjectType |
Base class for creating GraphQL objects |
graphene.Schema |
Main schema combining queries, mutations, and subscriptions |
graphene.Field |
Field definition within an object |
graphene.List |
List of objects of a specific type |
graphene.NonNull |
Field that cannot be null |
graphene.Mutation |
Base class for mutations |
graphene.InputObjectType |
Type for input data |
graphene.Interface |
Interface for shared fields |
graphene.Union |
Union of multiple types |
graphene.Enum |
Enumeration of possible values |
Scalar types
| Type | Description | Python type |
|---|---|---|
graphene.String |
String | str |
graphene.Int |
Integer | int |
graphene.Float |
Floating‑point number | float |
graphene.Boolean |
Boolean | bool |
graphene.ID |
Unique identifier | str |
graphene.Date |
Date | datetime.date |
graphene.DateTime |
Date and time | datetime.datetime |
graphene.Time |
Time | datetime.time |
graphene.Decimal |
Decimal number | decimal.Decimal |
graphene.JSONString |
JSON string | dict |
Decorators and utilities
| Decorator / Utility | Description |
|---|---|
@staticmethod |
Static resolver |
@classmethod |
Class resolver |
graphene.resolve_only_args |
Simplified decorator for resolvers |
graphene.Context |
Request execution context |
graphene.ResolveInfo |
Information about the current request |
Schema methods
| Method | Description |
|---|---|
schema.execute(query, context=None, variables=None) |
Execute a GraphQL query |
schema.execute_async(query, context=None, variables=None) |
Asynchronous query execution |
schema.get_type(name) |
Retrieve a type by name |
schema.get_graphql_type(name) |
Retrieve the GraphQL type |
Integration packages
| Package | Description |
|---|---|
graphene-django |
Integration with Django |
graphene-sqlalchemy |
Integration with SQLAlchemy |
graphene-mongo |
Integration with MongoDB |
flask-graphql |
Integration with Flask |
starlette-graphene3 |
Integration with FastAPI/Starlette |
Frequently asked questions
What is Graphene and how does it differ from other GraphQL libraries?
Graphene is the most mature and feature‑rich library for building GraphQL APIs in Python. It stands out for its declarative schema definition approach, extensive ecosystem of integrations, and an active developer community.
How does Graphene solve the N+1 query problem?
Graphene supports the DataLoader pattern for batching requests and integrates with ORMs to optimize database access via select_related() and prefetch_related() in Django or eager loading in SQLAlchemy.
Can Graphene be used with asynchronous frameworks?
Yes, Graphene supports asynchronous resolvers and can work with FastAPI, Starlette, and other async frameworks through the appropriate integrations.
How to secure a GraphQL API?
It is recommended to use query depth limits, timeouts, input validation, resolver‑level authentication, and query complexity analysis to prevent DoS attacks.
Does Graphene support GraphQL subscriptions?
Yes, Graphene supports subscriptions via graphene.ObjectType using WebSocket or Server‑Sent Events for real‑time updates.
How to test a GraphQL API built with Graphene?
Graphene provides a built‑in test client graphene.test.Client that allows executing queries without an HTTP server. Standard testing tools of the underlying frameworks can also be used.
Can Graphene be used to build a microservice architecture?
Yes, Graphene is suitable for microservices. You can create a GraphQL gateway that aggregates data from multiple services or use GraphQL Federation to combine schemas.
How to migrate from a REST API to GraphQL using Graphene?
Migrations can be incremental: first create GraphQL endpoints alongside REST, then gradually move clients to GraphQL. Graphene makes it easy to wrap existing business logic in GraphQL resolvers.
Full reference of Graphene methods and functions
| Category | Component / Method | Description | Usage example |
|---|---|---|---|
| Main classes | graphene.ObjectType |
Base class for GraphQL objects | class User(graphene.ObjectType): pass |
graphene.Schema |
Main API schema | schema = graphene.Schema(query=Query) |
|
graphene.Field |
Field definition | name = graphene.Field(graphene.String) |
|
graphene.List |
List of objects | users = graphene.List(User) |
|
graphene.NonNull |
Required field | id = graphene.NonNull(graphene.ID) |
|
| Scalar types | graphene.String |
String type | name = graphene.String() |
graphene.Int |
Integer type | age = graphene.Int() |
|
graphene.Float |
Floating‑point number | price = graphene.Float() |
|
graphene.Boolean |
Boolean type | is_active = graphene.Boolean() |
|
graphene.ID |
Unique identifier | id = graphene.ID() |
|
graphene.Date |
Date | birth_date = graphene.Date() |
|
graphene.DateTime |
Date and time | created_at = graphene.DateTime() |
|
graphene.JSONString |
JSON string | metadata = graphene.JSONString() |
|
| Mutations | graphene.Mutation |
Base mutation class | class CreateUser(graphene.Mutation): pass |
Arguments |
Mutation arguments | class Arguments: name = graphene.String() |
|
mutate() |
Mutation execution method | def mutate(self, info, **args): pass |
|
| Input types | graphene.InputObjectType |
Input data type | class UserInput(graphene.InputObjectType): pass |
graphene.Argument |
Field argument | user = graphene.Field(User, id=graphene.Argument(graphene.ID)) |
|
| Interfaces and unions | graphene.Interface |
Interface | class Node(graphene.Interface): pass |
graphene.Union |
Union of types | class SearchResult(graphene.Union): pass |
|
graphene.Enum |
Enumeration | class Status(graphene.Enum): ACTIVE = 1 |
|
| Resolvers | resolve_<field>() |
Field resolver | def resolve_name(self, info): return self.name |
info.context |
Request context | user = info.context.get('user') |
|
info.field_name |
Current field name | field = info.field_name |
|
info.parent_type |
Parent type | parent = info.parent_type |
|
| Query execution | schema.execute() |
Synchronous execution | result = schema.execute(query) |
schema.execute_async() |
Asynchronous execution | result = await schema.execute_async(query) |
|
| Relay | graphene.relay.Node |
Relay Node interface | class User(graphene.ObjectType): class Meta: interfaces = (graphene.relay.Node,) |
graphene.relay.Connection |
Relay Connection | users = graphene.relay.ConnectionField(User) |
|
graphene.relay.ClientIDMutation |
Relay mutation | class CreateUser(graphene.relay.ClientIDMutation): pass |
|
| Subscriptions | graphene.ObjectType |
Subscriptions | class Subscription(graphene.ObjectType): pass |
| Middleware | Middleware classes | Intermediate processing layer | class AuthMiddleware: def resolve(self, next, root, info, **args): pass |
| Utilities | graphene.is_type() |
Type checking | if graphene.is_type(obj, User): pass |
graphene.get_type() |
Retrieve a type | user_type = graphene.get_type(User) |
|
| Validation | Custom validators | User‑defined validation | def validate_email(email): return '@' in email |
| Caching | Field‑level caching | Caching at field level | @lru_cache(maxsize=128) |
| Batching | DataLoader pattern | Batching queries | user_loader = DataLoader(batch_load_users) |
| Introspection | Schema introspection | Schema introspection | schema.get_type_map() |
| Debugging | Debug middleware | Debugging tool | DjangoDebugMiddleware |
Graphene provides a powerful and flexible toolkit for building modern GraphQL APIs in Python, supporting all major GraphQL specification features and offering convenient integrations with popular web frameworks and ORM systems.
The Future of AI in Mathematics and Everyday Life: How Intelligent Agents Are Already Changing the Game
Experts warned about the risks of fake charity with AI
In Russia, universal AI-agent for robots and industrial processes was developed