GraphQL is a query language and runtime for APIs developed by Facebook in 2012 and open-sourced in 2015. It provides a more efficient, flexible, and powerful alternative to REST. Instead of multiple endpoints, clients access all data through a single endpoint and explicitly define what data they need.
This article offers a complete overview of GraphQL: its principles, structure, advantages, use cases, and how it compares with REST. It also covers its real-world implementation using Python.
Table of Contents
What Is GraphQL?
GraphQL is an API query language and runtime that allows clients to specify the structure of the response they want. It eliminates problems like over-fetching (getting too much data) and under-fetching (getting too little data and needing multiple requests).
Key Features
- Client-driven Queries: Clients specify exactly which fields they need.
- Single Endpoint: All requests are routed through a single GraphQL endpoint.
- Strongly Typed Schema: GraphQL uses a well-defined schema that is introspectable and self-documenting.
- Real-time Support: Through subscriptions, GraphQL supports real-time data updates.
- Nested and Related Data: Query deeply nested resources in a single request.
Core Concepts
Schema: A GraphQL schema defines the structure of your API and includes types, queries, mutations, and subscriptions.
Types: GraphQL includes scalar types (Int, Float, String, Boolean, ID) and supports custom object types.
type Book {
id: ID!
title: String!
author: Author!
}
type Author {
id: ID!
name: String!
}
PythonQueries: Queries are used to fetch data.
query {
book(id: 1) {
title
author {
name
}
}
}
PythonMutations: Mutations are used to create, update, or delete data.
mutation {
createBook(input: {title: "Dune", authorId: 2}) {
book {
id
title
}
}
}
PythonSubscriptions: Subscriptions allow real-time updates over WebSocket.
subscription {
bookAdded {
title
author {
name
}
}
}
PythonUnder-Fetching and Over-Fetching in REST
One of the major motivations behind GraphQL is eliminating under- and over-fetching, which are common in REST APIs.
Over-Fetching: Occurs when a REST endpoint returns more data than needed. For example, fetching user data may return the entire user profile, even if the client only needs the name and avatar.
Under-Fetching: Occurs when a single REST endpoint doesn’t return enough data, requiring additional requests. For example, fetching a book and then making separate requests for each author.
GraphQL Solution: In GraphQL, the client defines the data shape, solving both under- and over-fetching in one request.
Comparison: GraphQL vs REST
Feature | GraphQL | REST |
---|---|---|
Endpoints | Single (/graphql ) | Multiple (/users , /posts , etc.) |
Data Fetching | Client-defined | Server-defined |
Nested Data | Handled in one query | Requires multiple calls |
Real-Time Support | Native via Subscriptions | Needs separate WebSocket setup |
Versioning | Schema evolution, no versioning | Requires versioning (v1 , v2 ) |
Documentation | Schema-based, auto-generated | Needs manual docs or Swagger |
Caching | Complex, but possible | Simple via HTTP caching |
Tooling | GraphiQL, Apollo, Altair | Postman, Swagger |
Security | Fine-grained (per field) | Route-level |
Learning Curve | Moderate to high | Low to moderate |
Why Choose GraphQL?
GraphQL is ideal for modern, data-driven applications where flexibility and efficiency are crucial. Below are strong reasons to adopt it:
- Precise data retrieval: Reduces network payload, especially on mobile.
- Efficient nested queries: Minimizes round trips for related data.
- Better frontend-backend decoupling: Frontend developers control what they need.
- Self-documenting APIs: The schema is introspectable and usable with tools like GraphQL Playground.
- Improved developer experience: Strong typing, single endpoint, and live query testing.
- Real-time capability: Subscriptions make real-time apps much easier to build.
When Not to Use GraphQL
While powerful, GraphQL is not always the right tool. Consider avoiding it in these cases:
- Simple CRUD-only apps: REST is simpler and faster to implement.
- Public APIs with CDN caching: REST supports HTTP caching more effectively.
- High security/low control environments: GraphQL requires complex validation for introspection, cost, and depth.
- Large file uploads: REST handles multipart/form-data more natively.
Building a GraphQL API with Python
The Python ecosystem offers several libraries to build GraphQL APIs:
- Graphene: Most popular GraphQL library for Python.
- Ariadne: Schema-first, lightweight alternative.
- Strawberry: Modern, type-annotated GraphQL support using Python 3.6+ features.
Common Structure
- schema.py: Contains queries, mutations, subscriptions
- models.py: SQLAlchemy models
- resolvers.py: Logic for each GraphQL field
- auth.py: JWT-based authentication and context setup
Real-World Scenarios
Use GraphQL If:
- Your app shows nested or related data frequently
- You have multiple frontends (mobile, web) with different needs
- You need real-time updates
- You want to evolve your API without breaking clients
Example Applications
- Content platforms (media, blogs)
- E-commerce backends
- SaaS dashboards with analytics
- Messaging or collaboration tools
Project: Simple Bookstore API (GraphQL + Python)
Directory structure:
bookstore/
├── app.py
├── models.py
├── schema.py
└── database.py
Pythondatabase.py – SQLAlchemy Setup
This file sets up the SQLAlchemy base, engine, and session factory.
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
engine = create_engine("sqlite:///books.db")
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()
PythonKey Concepts:
- engine: Connects to a SQLite database (you can change to MySQL/PostgreSQL here).
- SessionLocal: Creates a database session for each request.
- Base: Used for declaring SQLAlchemy models (your table schemas).
models.py – Book and Author Models
Defines the database tables: Author and Book.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Author(Base):
__tablename__ = "authors"
id = Column(Integer, primary_key=True)
name = Column(String)
books = relationship("Book", back_populates="author")
class Book(Base):
__tablename__ = "books"
id = Column(Integer, primary_key=True)
title = Column(String)
author_id = Column(Integer, ForeignKey("authors.id"))
author = relationship("Author", back_populates="books")
PythonWhat’s Happening:
- These classes define your tables.
- Each Book is linked to an Author via ForeignKey.
- relationship() sets up a two-way connection so you can do book.author or author.books.
schema.py – GraphQL Schema
This is where you connect GraphQL with SQLAlchemy.
import graphene
from graphene_sqlalchemy import SQLAlchemyObjectType
from models import Book, Author
from database import SessionLocal
# Object Types
class AuthorType(SQLAlchemyObjectType):
class Meta:
model = Author
class BookType(SQLAlchemyObjectType):
class Meta:
model = Book
# Queries
class Query(graphene.ObjectType):
all_books = graphene.List(BookType)
book = graphene.Field(BookType, id=graphene.Int(required=True))
def resolve_all_books(parent, info):
db = SessionLocal()
return db.query(Book).all()
def resolve_book(parent, info, id):
db = SessionLocal()
return db.query(Book).filter(Book.id == id).first()
# Mutations
class CreateAuthor(graphene.Mutation):
class Arguments:
name = graphene.String(required=True)
author = graphene.Field(lambda: AuthorType)
def mutate(self, info, name):
db = SessionLocal()
new_author = Author(name=name)
db.add(new_author)
db.commit()
return CreateAuthor(author=new_author)
class CreateBook(graphene.Mutation):
class Arguments:
title = graphene.String(required=True)
author_id = graphene.Int(required=True)
book = graphene.Field(lambda: BookType)
def mutate(self, info, title, author_id):
db = SessionLocal()
new_book = Book(title=title, author_id=author_id)
db.add(new_book)
db.commit()
return CreateBook(book=new_book)
class Mutation(graphene.ObjectType):
create_author = CreateAuthor.Field()
create_book = CreateBook.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
PythonObject Types
- SQLAlchemyObjectType auto-generates GraphQL types from SQLAlchemy models.
- These types are used to define fields returned in queries/mutations.
Queries
- all_books: Returns a list of all books.
- book(id): Returns a single book by ID.
- resolve_* methods are resolvers — they run when those fields are queried.
Mutations
- CreateAuthor
- This mutation creates a new author.
- Arguments are the inputs.
- It returns the new author as output.
- CreateBook
- Same idea — creates a book with a title and author reference.
- Mutation
- This exposes the mutation fields.
app.py – Flask App with GraphQL View
from flask import Flask
from flask_graphql import GraphQLView
from schema import schema
from database import Base, engine
app = Flask(__name__)
# Create DB Tables
Base.metadata.create_all(bind=engine)
# Add GraphQL endpoint
app.add_url_rule(
"/graphql",
view_func=GraphQLView.as_view(
"graphql",
schema=schema,
graphiql=True # Enable GraphiQL UI
),
)
if __name__ == "__main__":
app.run(debug=True)
Python- This sets up the /graphql endpoint.
- graphiql=True enables the GraphiQL interface where you can test queries in the browser.
Running the App
Install dependencies:
pip install flask graphene graphene-sqlalchemy flask-graphql sqlalchemy
PythonRun the server:
python app.py
PythonVisit: http://localhost:5000/graphql
You can now interact with the API using GraphiQL.
Example Query
query {
allBooks {
title
author {
name
}
}
}
PythonExample Mutation
mutation {
createAuthor(name: "George Orwell") {
author {
id
name
}
}
}
PythonThen
mutation {
createBook(title: "1984", authorId: 1) {
book {
id
title
}
}
}
PythonRequest Lifecycle
When you make a GraphQL query from the UI:
- Flask handles the HTTP request.
- GraphQLView parses the query and routes it to the correct resolver (resolve_*).
- Resolvers use SQLAlchemy to talk to the database.
- The result is serialized into the GraphQL response and returned.
Conclusion
GraphQL is a powerful and modern API design approach that brings flexibility and efficiency to both clients and servers. It is especially valuable in scenarios with complex relationships, real-time data needs, and multiple frontend clients.
While REST is still an excellent choice for many use cases, GraphQL shines where REST becomes limiting. For developers using Python, libraries like Graphene or Ariadne make implementing GraphQL APIs seamless and production-ready.
Whether you’re building a mobile-first app, a real-time dashboard, or just want more control and structure in your APIs, GraphQL is worth learning and applying.