Table of Contents
What Is an API (In Practice)?
In this chapter you’ll build very simple APIs with Python and Flask.
You’ve already seen how to create a basic web server and use routes and templates. An API (Application Programming Interface) in web development is just:
A way for programs to talk to your server and get data, usually in a structured format like JSON, instead of a full HTML page.
Key ideas for this chapter:
- We’ll focus on HTTP APIs (often called “web APIs” or “REST-like APIs”).
- We’ll return data, not HTML.
- We’ll use JSON as the data format.
HTML vs JSON Responses
Until now, your Flask routes probably returned HTML strings or templates:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template("index.html")An API route usually returns JSON:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/api/hello")
def api_hello():
data = {"message": "Hello from the API!"}
return jsonify(data)/might be for a human using a browser (HTML)./api/hellois for another program or script (JSON).
If you open /api/hello in your browser, you’ll see something like:
{"message": "Hello from the API!"}Returning JSON with `jsonify`
Flask has a helper called jsonify that:
- Turns Python data into JSON.
- Sets the correct Content-Type header (
application/json).
You pass it standard Python data structures:
- dictionaries:
{"key": "value"} - lists:
[1, 2, 3] - nested combinations of these
Example:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/api/user")
def get_user():
user = {
"id": 1,
"name": "Alice",
"is_active": True
}
return jsonify(user)You can also return a list:
@app.route("/api/users")
def get_users():
users = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"}
]
return jsonify(users)A Minimal API-Only Flask App
Here is a tiny app that exposes only APIs (no HTML pages):
from flask import Flask, jsonify
app = Flask(__name__)
# Simple "status" endpoint
@app.route("/api/status")
def status():
return jsonify({"status": "ok"})
# Some dummy data
BOOKS = [
{"id": 1, "title": "Python Basics"},
{"id": 2, "title": "Flask for Beginners"},
]
@app.route("/api/books")
def get_books():
return jsonify(BOOKS)
if __name__ == "__main__":
app.run(debug=True)Start this app and visit in your browser (or with curl):
http://127.0.0.1:5000/api/statushttp://127.0.0.1:5000/api/books
You’ll see JSON responses.
Using Path Parameters
APIs often include values inside the URL, like /api/books/2.
Flask lets you capture parts of the URL as parameters:
from flask import Flask, jsonify
app = Flask(__name__)
BOOKS = [
{"id": 1, "title": "Python Basics"},
{"id": 2, "title": "Flask for Beginners"},
]
@app.route("/api/books/<int:book_id>")
def get_book(book_id):
for book in BOOKS:
if book["id"] == book_id:
return jsonify(book)
# Book not found
return jsonify({"error": "Book not found"}), 404Notes:
<int:book_id>means “capture this part of the path as an integer namedbook_id”.- You can return
(...), 404to set the HTTP status code to 404.
Try:
/api/books/1→ returns a book/api/books/99→ returns an error with status 404
Using Query Parameters (`?key=value`)
Another way to pass data to an API is via query parameters, e.g.:
/api/search?term=python&page=2
You read them with request.args:
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/api/echo")
def echo():
# Get the 'text' query parameter: /api/echo?text=hello
text = request.args.get("text", "") # default to empty string
return jsonify({"you_sent": text})Visit:
/api/echo?text=hello/api/echo?text=Python+is+fun
You’ll see the value you sent in the URL.
Basic HTTP Methods: GET vs POST
So far, all routes used the default GET method (usually to get data).
APIs also commonly use POST to send data to the server (e.g. creating something).
In Flask, you specify allowed methods:
from flask import Flask, jsonify, request
app = Flask(__name__)
messages = []
@app.route("/api/messages", methods=["GET"])
def list_messages():
return jsonify(messages)
@app.route("/api/messages", methods=["POST"])
def create_message():
text = request.form.get("text", "")
if not text:
return jsonify({"error": "Missing 'text'"}), 400
message = {"id": len(messages) + 1, "text": text}
messages.append(message)
return jsonify(message), 201GET /api/messagesreturns the list.POST /api/messagescreates a new message.
For now, you can test POSTs using tools like:
curlin the terminal- Postman or similar API tools
Example with curl (optional):
curl -X POST -F "text=Hello API" http://127.0.0.1:5000/api/messagesSending and Receiving JSON in POST
Instead of sending form data, many APIs send JSON in the request body.
In Flask, you can access JSON data with request.get_json():
from flask import Flask, jsonify, request
app = Flask(__name__)
todos = []
@app.route("/api/todos", methods=["GET"])
def get_todos():
return jsonify(todos)
@app.route("/api/todos", methods=["POST"])
def add_todo():
data = request.get_json()
if not data or "title" not in data:
return jsonify({"error": "JSON with 'title' required"}), 400
todo = {
"id": len(todos) + 1,
"title": data["title"],
"done": False
}
todos.append(todo)
return jsonify(todo), 201Example JSON you might send:
{"title": "Learn basic APIs"}Basic Status Codes
An API response has:
- Body (JSON we return)
- Status code (numeric HTTP code)
Common ones you’ll use:
200– OK (success)201– Created (successfully created something new)400– Bad Request (client sent something invalid)404– Not Found (resource doesn’t exist)500– Internal Server Error (server crashed / bug)
In Flask, you set the status code by returning a tuple:
return jsonify({"error": "Item not found"}), 404
Keep it simple for now: use 200 or 201 on success, 400 or 404 on basic errors.
A Tiny “Todo API” Example
Here’s a small but complete example combining everything:
from flask import Flask, jsonify, request
app = Flask(__name__)
todos = [
{"id": 1, "title": "Learn Flask", "done": False},
]
@app.route("/api/todos", methods=["GET"])
def list_todos():
return jsonify(todos)
@app.route("/api/todos/<int:todo_id>", methods=["GET"])
def get_todo(todo_id):
for todo in todos:
if todo["id"] == todo_id:
return jsonify(todo)
return jsonify({"error": "Todo not found"}), 404
@app.route("/api/todos", methods=["POST"])
def create_todo():
data = request.get_json()
if not data or "title" not in data:
return jsonify({"error": "JSON with 'title' required"}), 400
new_id = max([t["id"] for t in todos], default=0) + 1
todo = {"id": new_id, "title": data["title"], "done": False}
todos.append(todo)
return jsonify(todo), 201
if __name__ == "__main__":
app.run(debug=True)This is not production-ready, but:
- It shows GET and POST.
- It uses path parameters.
- It returns JSON and sets status codes.
How Another Program Could Use Your API
Imagine a Python script using your API (instead of calling functions directly). It might use the requests library like this:
import requests
response = requests.get("http://127.0.0.1:5000/api/status")
data = response.json()
print(data["status"])You don’t need to build this script now, but it shows what APIs are for:
Your Flask app becomes a service that other programs can call.
Practice Ideas
Try adding and testing these simple API features:
- Add
/api/timethat returns the current time as JSON. - Extend the books API:
/api/books/search?title=python– return only matching books.- Add a field update route for todos:
- e.g.
/api/todos/<id>/completethat marks a todo as done (you can usePOSTfor now).
Keep things small: a few routes, clear JSON, simple code. The goal here is to feel comfortable with:
- Returning
jsonify(...) - Using path and query parameters
- Reading simple JSON from requests
- Setting basic status codes