Skip to main content

Flask REST vs GraphQL

In modern web development, APIs (Application Programming Interfaces) are the backbone of how applications communicate. When developing Flask applications, you'll likely need to create APIs for your frontend or for third-party services. Two popular approaches for building APIs are REST and GraphQL.

This guide will explore both REST and GraphQL in the context of Flask, comparing their strengths, weaknesses, and implementation details.

Introduction to API Paradigms

Before diving into code examples, let's understand the fundamental differences between REST and GraphQL:

What is REST?

REST (Representational State Transfer) is an architectural style that uses standard HTTP methods to perform operations on resources. Each endpoint represents a specific resource or collection of resources.

Key characteristics:

  • Uses standard HTTP methods (GET, POST, PUT, DELETE)
  • Each endpoint maps to a specific resource
  • Stateless operations
  • Multiple endpoints for different resources
  • Client has no control over the data structure received

What is GraphQL?

GraphQL is a query language and runtime for APIs developed by Facebook. Unlike REST, GraphQL typically uses a single endpoint where clients can specify exactly what data they need.

Key characteristics:

  • Single endpoint for all operations
  • Clients specify the exact data they want
  • Reduced over-fetching and under-fetching of data
  • Strong typing system
  • Introspection capabilities (self-documenting)

Implementing REST APIs in Flask

Flask makes it easy to implement RESTful APIs, especially when combined with Flask-RESTful or Flask-RESTX extensions.

Basic REST API with Flask

Let's start with a simple REST API for managing books:

python
from flask import Flask, jsonify, request

app = Flask(__name__)

# In-memory database for simplicity
books = [
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg", "year": 2018},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes", "year": 2019}
]

# Get all books
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify(books)

# Get a specific book
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if book:
return jsonify(book)
return jsonify({"error": "Book not found"}), 404

# Add a new book
@app.route('/api/books', methods=['POST'])
def add_book():
if not request.json or 'title' not in request.json:
return jsonify({"error": "Invalid request"}), 400

new_book = {
"id": books[-1]["id"] + 1 if books else 1,
"title": request.json['title'],
"author": request.json.get('author', ''),
"year": request.json.get('year', 0)
}
books.append(new_book)
return jsonify(new_book), 201

# Update a book
@app.route('/api/books/<int:book_id>', methods=['PUT'])
def update_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if not book:
return jsonify({"error": "Book not found"}), 404

if not request.json:
return jsonify({"error": "Invalid request"}), 400

book['title'] = request.json.get('title', book['title'])
book['author'] = request.json.get('author', book['author'])
book['year'] = request.json.get('year', book['year'])

return jsonify(book)

# Delete a book
@app.route('/api/books/<int:book_id>', methods=['DELETE'])
def delete_book(book_id):
book_index = next((index for index, book in enumerate(books) if book["id"] == book_id), -1)
if book_index == -1:
return jsonify({"error": "Book not found"}), 404

deleted_book = books.pop(book_index)
return jsonify({"message": f"Book '{deleted_book['title']}' deleted"})

if __name__ == '__main__':
app.run(debug=True)

Using Flask-RESTful for Cleaner REST APIs

Flask-RESTful is a popular extension that simplifies creating RESTful APIs with Flask. Let's refactor our example:

python
from flask import Flask, request
from flask_restful import Resource, Api, reqparse, abort

app = Flask(__name__)
api = Api(app)

# In-memory database for simplicity
books = [
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg", "year": 2018},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes", "year": 2019}
]

parser = reqparse.RequestParser()
parser.add_argument('title', required=True, help="Title cannot be blank")
parser.add_argument('author')
parser.add_argument('year', type=int)

class BookList(Resource):
def get(self):
return books

def post(self):
args = parser.parse_args()
new_book = {
"id": books[-1]["id"] + 1 if books else 1,
"title": args['title'],
"author": args.get('author', ''),
"year": args.get('year', 0)
}
books.append(new_book)
return new_book, 201

class Book(Resource):
def get(self, book_id):
book = next((book for book in books if book["id"] == book_id), None)
if not book:
abort(404, message=f"Book {book_id} not found")
return book

def put(self, book_id):
book = next((book for book in books if book["id"] == book_id), None)
if not book:
abort(404, message=f"Book {book_id} not found")

args = parser.parse_args()
book['title'] = args.get('title', book['title'])
book['author'] = args.get('author', book['author'])
book['year'] = args.get('year', book['year'])
return book

def delete(self, book_id):
book_index = next((index for index, book in enumerate(books) if book["id"] == book_id), -1)
if book_index == -1:
abort(404, message=f"Book {book_id} not found")

deleted_book = books.pop(book_index)
return {"message": f"Book '{deleted_book['title']}' deleted"}

# Register resources
api.add_resource(BookList, '/api/books')
api.add_resource(Book, '/api/books/<int:book_id>')

if __name__ == '__main__':
app.run(debug=True)

Implementing GraphQL in Flask

Now, let's implement the same book management functionality using GraphQL with the Ariadne library, which is a Python implementation of GraphQL.

Setting up a GraphQL API with Flask and Ariadne

First, install Ariadne:

bash
pip install ariadne

Now, let's create our GraphQL implementation:

python
from flask import Flask, request, jsonify
from ariadne import ObjectType, QueryType, MutationType, gql, make_executable_schema
from ariadne.constants import PLAYGROUND_HTML
from ariadne.asgi import GraphQL

app = Flask(__name__)

# In-memory database for simplicity
books = [
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg", "year": 2018},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes", "year": 2019}
]

# Define GraphQL schema
type_defs = gql("""
type Book {
id: ID!
title: String!
author: String
year: Int
}

type Query {
books: [Book]
book(id: ID!): Book
}

type Mutation {
addBook(title: String!, author: String, year: Int): Book
updateBook(id: ID!, title: String, author: String, year: Int): Book
deleteBook(id: ID!): String
}
""")

# Define resolver functions
query = QueryType()
mutation = MutationType()

@query.field("books")
def resolve_books(*_):
return books

@query.field("book")
def resolve_book(*_, id):
book = next((book for book in books if book["id"] == int(id)), None)
return book

@mutation.field("addBook")
def resolve_add_book(*_, title, author=None, year=None):
new_book = {
"id": books[-1]["id"] + 1 if books else 1,
"title": title,
"author": author or "",
"year": year or 0
}
books.append(new_book)
return new_book

@mutation.field("updateBook")
def resolve_update_book(*_, id, title=None, author=None, year=None):
book = next((book for book in books if book["id"] == int(id)), None)
if not book:
return None

if title is not None:
book["title"] = title
if author is not None:
book["author"] = author
if year is not None:
book["year"] = year
return book

@mutation.field("deleteBook")
def resolve_delete_book(*_, id):
book_index = next((index for index, book in enumerate(books) if book["id"] == int(id)), -1)
if book_index == -1:
return "Book not found"

deleted_book = books.pop(book_index)
return f"Book '{deleted_book['title']}' deleted"

# Create executable schema
schema = make_executable_schema(type_defs, query, mutation)

# GraphQL endpoint
@app.route("/graphql", methods=["GET"])
def graphql_playground():
# Serve GraphQL Playground for GET requests
return PLAYGROUND_HTML, 200

@app.route("/graphql", methods=["POST"])
def graphql_server():
# Serve GraphQL API for POST requests
data = request.get_json()
success, result = GraphQL(schema).execute_sync(
data["query"],
variable_values=data.get("variables"),
context_value={"request": request}
)
return jsonify(result)

if __name__ == "__main__":
app.run(debug=True)

Using the GraphQL API

Once the GraphQL API is running, you can access the GraphQL Playground at http://localhost:5000/graphql. Here are some example queries and mutations you can try:

Query all books:

graphql
query {
books {
id
title
author
year
}
}

Query a specific book:

graphql
query {
book(id: 1) {
title
author
}
}

Add a new book:

graphql
mutation {
addBook(title: "Python for Data Science", author: "Jake VanderPlas", year: 2016) {
id
title
author
year
}
}

Update a book:

graphql
mutation {
updateBook(id: 2, year: 2020) {
id
title
author
year
}
}

Delete a book:

graphql
mutation {
deleteBook(id: 1)
}

Comparing REST and GraphQL in Real-World Scenarios

Now that we've seen both approaches, let's compare them in common real-world scenarios:

Scenario 1: Mobile App with Limited Bandwidth

Let's say you're building a mobile app that needs to display a list of books with just their titles and authors.

REST Approach:

python
# Client requests:
GET /api/books

# Server responds with full book objects:
[
{"id": 1, "title": "Flask Web Development", "author": "Miguel Grinberg", "year": 2018},
{"id": 2, "title": "Python Crash Course", "author": "Eric Matthes", "year": 2019}
]

# Client must discard the 'year' field it doesn't need

GraphQL Approach:

graphql
# Client requests exactly what it needs:
query {
books {
title
author
}
}

# Server responds with exactly what was requested:
{
"data": {
"books": [
{"title": "Flask Web Development", "author": "Miguel Grinberg"},
{"title": "Python Crash Course", "author": "Eric Matthes"}
]
}
}

Scenario 2: Dashboard Needing Multiple Resources

Imagine you're building a dashboard that needs information about books, authors, and reviews in a single view.

REST Approach:

python
# Client needs to make multiple requests:
GET /api/books
GET /api/authors
GET /api/reviews

# Then client must stitch the data together

GraphQL Approach:

graphql
# Client can get all needed data in one request:
query {
books {
id
title
author {
id
name
bio
}
reviews {
rating
comment
}
}
}

When to Use REST vs GraphQL

Choose REST when:

  1. Simple APIs: Your API has a simple structure with clear resource boundaries
  2. Caching requirements: You need to take advantage of HTTP caching
  3. File uploads: REST handles file uploads more naturally
  4. Stable API contract: Your API doesn't change frequently
  5. Team familiarity: Your team is already experienced with REST

Choose GraphQL when:

  1. Mobile applications: You need to minimize data transfer
  2. Complex data relationships: Your data has many interconnected relationships
  3. Rapidly changing requirements: Client needs change frequently
  4. Multiple client applications: Different clients need different data from the same backend
  5. Aggregating multiple services: You need to combine data from multiple sources

Best Practices

REST Best Practices

  1. Use proper HTTP methods (GET, POST, PUT, DELETE)
  2. Return appropriate status codes
  3. Use plural nouns for collections (/books instead of /book)
  4. Version your API (/api/v1/books)
  5. Implement pagination for large collections
  6. Use filtering, sorting and searching parameters

GraphQL Best Practices

  1. Use a schema-first approach
  2. Implement proper error handling
  3. Add pagination to list queries
  4. Use input types for complex mutations
  5. Consider query complexity analysis to prevent abuse
  6. Implement dataloaders to avoid the N+1 query problem

Summary

REST and GraphQL both have their place in modern API development with Flask:

  • REST is mature, widely understood, and easy to implement in Flask, making it a solid choice for simple APIs with clear resource boundaries.
  • GraphQL offers more flexibility, allowing clients to request exactly what they need and reducing over-fetching issues. It excels in complex applications with many interrelated entities.

The choice between REST and GraphQL should be based on your specific project requirements, team expertise, and the types of clients consuming your API.

Additional Resources

  1. Flask-RESTful Documentation
  2. Ariadne GraphQL Documentation
  3. RESTful API Design Best Practices
  4. GraphQL Official Documentation
  5. Apollo GraphQL Client (for frontend integration)

Exercises

  1. Extend the REST API to include an endpoint for filtering books by author or year
  2. Add pagination to the REST API to handle large collections of books
  3. Extend the GraphQL schema to include an Author type with a relationship to Books
  4. Implement field-level permissions in your GraphQL API to restrict access to certain fields
  5. Create a simple web interface that consumes both the REST and GraphQL APIs, and compare the implementation complexity

By mastering both REST and GraphQL in Flask, you'll be well-equipped to design and implement APIs that meet the unique requirements of your projects.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)