π REST APIs in Flask: Building Your Digital Pizza Delivery Service
Imagine youβre running a pizza shop. Customers canβt just walk into your kitchen and make their own pizza. Instead, they tell the cashier what they want, the cashier writes it down, sends it to the kitchen, and then brings back the pizza. REST APIs work exactly like this!
Your Flask app is the pizza shop. The API is the cashierβthe friendly middleman between hungry customers (other apps, websites, phones) and your delicious data kitchen.
π― What is a RESTful API?
REST stands for REpresentational State Transfer. Fancy words, simple idea!
Think of it like a library system:
- You want a book? Tell the librarian the title β They GET it for you
- Want to donate a book? Give it to them β They POST it to the shelf
- Book has wrong info? Tell them β They PUT the corrected version
- Remove a book? Ask nicely β They DELETE it
graph TD A["π± Client App"] -->|Request| B["π REST API"] B -->|Response| A B -->|Read/Write| C["πΎ Database"]
The 4 Magic Words (HTTP Methods)
| Method | What it does | Pizza Example |
|---|---|---|
| GET | Fetch data | βShow me the menu!β |
| POST | Create new data | βI want to order a new pizza!β |
| PUT | Update existing data | βChange my order to extra cheese!β |
| DELETE | Remove data | βCancel my order!β |
π£οΈ API Endpoints Design: Creating Your Menu Board
An endpoint is like an address where customers go to get what they want. Good addresses are easy to understand and predict.
π¨ The Art of Good Endpoint Design
Bad Design (Confusing!)
/getAllPizzas
/createNewPizza
/updatePizzaById
Good Design (Clear!)
GET /pizzas β List all pizzas
POST /pizzas β Create a pizza
GET /pizzas/1 β Get pizza #1
PUT /pizzas/1 β Update pizza #1
DELETE /pizzas/1 β Delete pizza #1
Flask Example: Your First Endpoints
from flask import Flask, jsonify
app = Flask(__name__)
pizzas = [
{"id": 1, "name": "Margherita"},
{"id": 2, "name": "Pepperoni"}
]
# GET all pizzas
@app.route('/pizzas', methods=['GET'])
def get_pizzas():
return jsonify(pizzas)
# GET one pizza
@app.route('/pizzas/<int:id>',
methods=['GET'])
def get_pizza(id):
pizza = next(
(p for p in pizzas if p['id'] == id),
None
)
return jsonify(pizza)
π Golden Rules for Endpoints
- Use nouns, not verbs:
/usersnot/getUsers - Plural names:
/pizzasnot/pizza - Lowercase:
/ordersnot/Orders - Hyphens for spaces:
/order-itemsnot/orderItems
π HTTP Status Codes: The Emoji Language of APIs
When you text a friend, they reply with π or π’. APIs do the same with status codes!
graph TD A["Status Codes"] --> B["2xx β Success"] A --> C["4xx β Your Fault"] A --> D["5xx π₯ Our Fault"] B --> E["200 OK"] B --> F["201 Created"] C --> G["400 Bad Request"] C --> H["404 Not Found"] D --> I["500 Server Error"]
The Most Important Codes
| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK! | Everything worked perfectly |
| 201 | Created! | New thing was made |
| 400 | Bad Request | You sent garbage data |
| 404 | Not Found | That thing doesnβt exist |
| 500 | Server Error | Oops, we broke something |
Flask Example: Returning Status Codes
from flask import Flask, jsonify
@app.route('/pizzas', methods=['POST'])
def create_pizza():
# Created successfully!
return jsonify({
"message": "Pizza created!"
}), 201 # β Status code!
@app.route('/pizzas/<int:id>')
def get_pizza(id):
pizza = find_pizza(id)
if not pizza:
return jsonify({
"error": "Pizza not found"
}), 404 # β Not found!
return jsonify(pizza), 200
π₯ JSON Request Handling: Understanding Customer Orders
When a customer orders a pizza, they fill out a form. In APIs, that form is JSON (JavaScript Object Notation).
What JSON Looks Like
{
"name": "Supreme Pizza",
"size": "large",
"toppings": ["pepperoni", "mushrooms"]
}
Itβs like a recipe card that both humans and computers can read!
Flask Example: Reading JSON Requests
from flask import Flask, request, jsonify
@app.route('/pizzas', methods=['POST'])
def create_pizza():
# Get the JSON data
data = request.get_json()
# Read the values
name = data.get('name')
size = data.get('size')
toppings = data.get('toppings', [])
# Create the pizza
new_pizza = {
"id": len(pizzas) + 1,
"name": name,
"size": size,
"toppings": toppings
}
pizzas.append(new_pizza)
return jsonify(new_pizza), 201
π Key Functions
| Function | What it does |
|---|---|
request.get_json() |
Gets all JSON data |
data.get('key') |
Gets one value safely |
data.get('key', default) |
Gets value or default |
π€ JSON Response Formatting: Sending Back the Receipt
After making a pizza, you give the customer a receipt. APIs send back JSON responses.
Flask Example: Beautiful Responses
from flask import jsonify
@app.route('/pizzas/<int:id>')
def get_pizza(id):
pizza = {
"id": 1,
"name": "Margherita",
"price": 12.99,
"ready": True
}
return jsonify(pizza)
Output:
{
"id": 1,
"name": "Margherita",
"price": 12.99,
"ready": true
}
π Response Best Practices
Include helpful metadata:
@app.route('/pizzas')
def get_pizzas():
return jsonify({
"data": pizzas,
"count": len(pizzas),
"success": True
})
Wrap single items too:
@app.route('/pizzas/<int:id>')
def get_pizza(id):
return jsonify({
"data": pizza,
"success": True
})
β οΈ API Error Responses: When Things Go Wrong
Even the best pizza shop runs out of cheese sometimes. Good APIs explain problems clearly!
Flask Example: Helpful Error Messages
@app.route('/pizzas/<int:id>')
def get_pizza(id):
pizza = find_pizza(id)
if not pizza:
return jsonify({
"error": "Pizza not found",
"message": f"No pizza with id {id}",
"code": 404
}), 404
return jsonify(pizza)
π Error Response Structure
{
"error": "Short error name",
"message": "Human-readable explanation",
"code": 404,
"details": {}
}
Global Error Handler
@app.errorhandler(404)
def not_found(error):
return jsonify({
"error": "Not Found",
"message": "Resource doesn't exist"
}), 404
@app.errorhandler(500)
def server_error(error):
return jsonify({
"error": "Server Error",
"message": "Something went wrong"
}), 500
β Request Validation: Checking the Order Form
Before making a pizza, you check if the order makes sense. Validation ensures incoming data is correct.
Simple Validation Example
@app.route('/pizzas', methods=['POST'])
def create_pizza():
data = request.get_json()
# Check if data exists
if not data:
return jsonify({
"error": "No data provided"
}), 400
# Check required fields
if 'name' not in data:
return jsonify({
"error": "Name is required"
}), 400
# Check data types
if not isinstance(data['name'], str):
return jsonify({
"error": "Name must be text"
}), 400
# All good! Create pizza
# ...
π― Validation Checklist
graph TD A["Request Arrives"] --> B{Data exists?} B -->|No| C["β 400 Error"] B -->|Yes| D{Required fields?} D -->|Missing| C D -->|Present| E{Correct types?} E -->|Wrong| C E -->|Right| F["β Process Request"]
Pro Tip: Create a Validation Helper
def validate_pizza(data):
errors = []
if not data.get('name'):
errors.append("Name required")
if not data.get('size'):
errors.append("Size required")
valid_sizes = ['small', 'medium', 'large']
if data.get('size') not in valid_sizes:
errors.append("Invalid size")
return errors
@app.route('/pizzas', methods=['POST'])
def create_pizza():
data = request.get_json()
errors = validate_pizza(data)
if errors:
return jsonify({
"errors": errors
}), 400
# Create pizza...
ποΈ Class-Based Views: Organizing Your Kitchen
Imagine your pizza shop grows. Instead of one person doing everything, you have specialized stations. Class-based views organize your code the same way!
The Old Way (Function Views)
@app.route('/pizzas', methods=['GET'])
def get_pizzas():
return jsonify(pizzas)
@app.route('/pizzas', methods=['POST'])
def create_pizza():
# create logic...
@app.route('/pizzas/<int:id>', methods=['GET'])
def get_pizza(id):
# get one logic...
The New Way (Class-Based Views)
from flask.views import MethodView
class PizzaAPI(MethodView):
def get(self, id=None):
if id is None:
# Return all pizzas
return jsonify(pizzas)
# Return one pizza
pizza = find_pizza(id)
return jsonify(pizza)
def post(self):
data = request.get_json()
# Create pizza logic
return jsonify(new_pizza), 201
def put(self, id):
data = request.get_json()
# Update pizza logic
return jsonify(updated)
def delete(self, id):
# Delete pizza logic
return '', 204
Registering Class-Based Views
# Create the view
pizza_view = PizzaAPI.as_view('pizza_api')
# Register routes
app.add_url_rule(
'/pizzas',
view_func=pizza_view,
methods=['GET', 'POST']
)
app.add_url_rule(
'/pizzas/<int:id>',
view_func=pizza_view,
methods=['GET', 'PUT', 'DELETE']
)
π― Why Use Class-Based Views?
| Benefit | Explanation |
|---|---|
| Organization | All pizza code in one place |
| Inheritance | Share logic between views |
| Clarity | Method names match HTTP methods |
| Reusability | Easy to extend and modify |
graph TD A["PizzaAPI Class"] --> B["get - Read pizzas"] A --> C["post - Create pizza"] A --> D["put - Update pizza"] A --> E["delete - Remove pizza"]
π Putting It All Together
Letβs build a complete mini pizza API!
from flask import Flask, request, jsonify
from flask.views import MethodView
app = Flask(__name__)
# Our pizza database
pizzas = []
next_id = 1
class PizzaAPI(MethodView):
def get(self, id=None):
if id is None:
return jsonify({
"data": pizzas,
"count": len(pizzas)
}), 200
pizza = next(
(p for p in pizzas
if p['id'] == id), None
)
if not pizza:
return jsonify({
"error": "Not found"
}), 404
return jsonify(pizza), 200
def post(self):
global next_id
data = request.get_json()
if not data or 'name' not in data:
return jsonify({
"error": "Name required"
}), 400
pizza = {
"id": next_id,
"name": data['name']
}
next_id += 1
pizzas.append(pizza)
return jsonify(pizza), 201
def delete(self, id):
global pizzas
pizzas = [p for p in pizzas
if p['id'] != id]
return '', 204
# Register routes
view = PizzaAPI.as_view('pizza')
app.add_url_rule('/pizzas',
view_func=view,
methods=['GET', 'POST'])
app.add_url_rule('/pizzas/<int:id>',
view_func=view,
methods=['GET', 'DELETE'])
if __name__ == '__main__':
app.run(debug=True)
π You Did It!
Youβve learned how to:
- β Design clean API endpoints
- β Use the right HTTP status codes
- β Handle JSON requests and responses
- β Create helpful error messages
- β Validate incoming data
- β Organize code with class-based views
Youβre now ready to build APIs that are:
- π Easy to understand
- π Easy to use
- π Easy to maintain
Go forth and build amazing things! π
