A Complete Guide to GraphQL for Backend Developers

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.

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!
}
Python

Queries: Queries are used to fetch data.

query {
  book(id: 1) {
    title
    author {
      name
    }
  }
}
Python

Mutations: Mutations are used to create, update, or delete data.

mutation {
  createBook(input: {title: "Dune", authorId: 2}) {
    book {
      id
      title
    }
  }
}
Python

Subscriptions: Subscriptions allow real-time updates over WebSocket.

subscription {
  bookAdded {
    title
    author {
      name
    }
  }
}
Python

Under-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

FeatureGraphQLREST
EndpointsSingle (/graphql)Multiple (/users, /posts, etc.)
Data FetchingClient-definedServer-defined
Nested DataHandled in one queryRequires multiple calls
Real-Time SupportNative via SubscriptionsNeeds separate WebSocket setup
VersioningSchema evolution, no versioningRequires versioning (v1, v2)
DocumentationSchema-based, auto-generatedNeeds manual docs or Swagger
CachingComplex, but possibleSimple via HTTP caching
ToolingGraphiQL, Apollo, AltairPostman, Swagger
SecurityFine-grained (per field)Route-level
Learning CurveModerate to highLow 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
Python

Code Repo

database.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()
Python

Key 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")
Python

What’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)
Python

Object 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
Python

Run the server:

python app.py
Python

Visit: http://localhost:5000/graphql

You can now interact with the API using GraphiQL.

Example Query

query {
  allBooks {
    title
    author {
      name
    }
  }
}
Python

Example Mutation

mutation {
  createAuthor(name: "George Orwell") {
    author {
      id
      name
    }
  }
}
Python

Then

mutation {
  createBook(title: "1984", authorId: 1) {
    book {
      id
      title
    }
  }
}
Python

Request 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.

Resource

Leave a Comment