GraphQL Introduction for Comparison
In this tutorial, you'll learn about Graphql fundamentals. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
GraphQL is a query language and runtime for APIs where clients declaratively request exactly the data they need from a single endpoint.
What You'll Learn
By the end of this lesson, you will understand GraphQL's schema-driven design, write basic queries and mutations, and know how GraphQL's single-endpoint approach differs from REST.
Why It Matters
GraphQL addresses fundamental REST limitations: over-fetching, under-fetching, and multiple round trips. It excels for complex UIs and mobile applications.
Real-World Use
GitHub's public API v4 is GraphQL. Clients request exactly the data they need: query { Repository(owner:"facebook", name:"react") { name stargazerCount } }.
GraphQL Request Flow
sequenceDiagram
Client->>Server: POST /graphql { "query": "user(id:1){name email}" }
Server->>Server: Validate query against schema
Server->>Server: Resolve fields
Server->>Client: {"data": {"name":"Alice","email":"alice@x.com"}}
GraphQL Schema and Resolvers
# graphql_basics.py
from typing import Any, Dict, List
class GraphQLSchema:
def __init__(self):
self.types: Dict[str, Dict] = {}
self.resolvers: Dict[str, callable] = {}
def define_type(self, name: str, fields: Dict[str, str]):
self.types[name] = fields
def resolver(self, path: str):
def decorator(fn):
self.resolvers[path] = fn
return fn
return decorator
def execute(self, query: str) -> Dict:
data = {}
errors = []
lines = query.strip().split("\n")
operation = lines[0].strip().split(" ")[0].lower()
if "{" not in query:
return {"error": "Syntax error"}
brace_content = query[query.index("{") + 1:query.rindex("}")].strip()
fields = [f.strip() for f in brace_content.split() if f.strip() and not f.startswith("(")]
for field in fields:
resolver_key = f"{operation}.{field}"
resolver = self.resolvers.get(resolver_key)
if resolver:
try:
data[field] = resolver()
except Exception as e:
errors.append(f"Cannot resolve '{field}': {e}")
else:
data[field] = None
result = {"data": data}
if errors:
result["errors"] = errors
return result
from graphql_basics import GraphQLSchema
schema = GraphQLSchema()
schema.define_type("User", {"id": "ID!", "name": "String!", "email": "String"})
@schema.resolver("query.users")
def resolve_users():
return [{"id": "1", "name": "Alice", "email": "alice@x.com"}]
@schema.resolver("query.user")
def resolve_user():
return {"id": "1", "name": "Alice", "email": "alice@x.com"}
result = schema.execute("{ users user }")
print(result)
Expected output:
{'data': {'users': [{'id': '1', 'name': 'Alice', 'email': 'alice@x.com'}], 'user': {'id': '1', 'name': 'Alice', 'email': 'alice@x.com'}}}
GraphQL vs REST Endpoint Comparison
# rest_vs_graphql_endpoints.py
from typing import Any, Dict, List
class RESTEndpoint:
def get_user(self, user_id: int) -> Dict:
return {"id": user_id, "name": "Alice", "email": "a@x.com", "posts": 5}
def get_user_posts(self, user_id: int) -> List[Dict]:
return [{"id": 10, "title": "Post", "user_id": user_id}]
def get_user_followers(self, user_id: int) -> List[Dict]:
return [{"id": 2, "name": "Bob"}]
class GraphQLEndpoint:
def query(self, selection: List[str], user_id: int) -> Dict:
user = {"id": user_id, "name": "Alice", "email": "a@x.com", "posts": 5}
result = {}
for field in selection:
if field in user:
result[field] = user[field]
return {"data": result}
rest = RESTEndpoint()
graphql = GraphQLEndpoint()
# REST requires multiple calls
rest_result = {
"user": rest.get_user(1),
"posts": rest.get_user_posts(1),
}
print(f"REST (3 calls): {list(rest_result.keys())}")
# GraphQL single call
gql_result = graphql.query(["name", "email"], 1)
print(f"GraphQL (1 call): {gql_result}")
Expected output:
REST (3 calls): ['user', 'posts']
GraphQL (1 call): {'data': {'name': 'Alice', 'email': 'alice@x.com'}}
Common Mistakes
1. Not Defining a Schema First
GraphQL is schema-driven. Without a schema, there is no GraphQL. Always define types before resolvers.
2. N+1 Query Problem
Resolving a list of items triggers one query per item. Use DataLoader or batch resolvers to avoid N+1.
3. No Input Validation
GraphQL validates types but not business rules. Add validation in resolvers for required fields and constraints.
4. Overly Deep Nesting
Deeply nested queries can cause performance issues. Set query depth limits and complexity limits.
5. Ignoring Authentication
GraphQL endpoints are a single surface area. Secure all fields. Do not expose sensitive data in the schema.
Practice Questions
1. What problem does GraphQL solve?
Clients request exactly the data they need, eliminating over-fetching and under-fetching.
2. How many endpoints does a GraphQL API have?
One single endpoint, typically /graphql.
3. What is a resolver in GraphQL?
A function that fetches the data for a specific field in the schema.
4. What is the N+1 Problem?
Resolving a list of N items triggers N+1 database queries (1 for the list + N for each item's nested fields).
Challenge
Design a GraphQL schema for a blog with users, posts (with pagination), comments, and tags. Write resolvers for each type.
FAQ
Mini Project: Simple GraphQL Runner
# simple_graphql.py
from typing import Any, Dict
class SimpleGraphQL:
def __init__(self):
self.schema = {}
self.resolvers = {}
def type(self, name: str, fields: Dict):
self.schema[name] = fields
def resolve(self, name: str):
def wrap(fn):
self.resolvers[name] = fn
return fn
return wrap
def query(self, fields: list) -> Dict:
result = {}
for field in fields:
fn = self.resolvers.get(field)
if fn:
result[field] = fn()
return {"data": result}
gql = SimpleGraphQL()
gql.type("User", {"id": "ID", "name": "String"})
@gql.resolve("users")
def users():
return [{"id": 1, "name": "Alice"}]
print(gql.query(["users"]))
Expected output:
{'data': {'users': [{'id': 1, 'name': 'Alice'}]}}
What's Next
You understand GraphQL basics. Next, explore data fetching differences, then compare over-fetching in REST vs GraphQL.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro