Flask for Beginners: Build Simple APIs

Computer Programming 2

APIs and Flask

  • API: A way for programs to talk to each other over HTTP.
  • REST API: Uses URLs and HTTP methods (GET, POST, PUT, DELETE) to access data.
  • Flask: A lightweight Python web framework to build APIs quickly.

By the end of this lesson, you will be able to:

  • Create a simple Flask API
  • Handle GET and POST requests
  • Return JSON responses
  • Test your API with curl or Postman

Setup: Python, Virtual Env, Install Flask

  • Use a virtual environment to isolate dependencies
  • Install Flask from PyPI

Practice: Create a folder for your API project and set up your environment.

# 1) Create project folder
mkdir flask-beginners-api
cd flask-beginners-api

# 2) Create and activate virtual environment (macOS/Linux)
python3 -m venv .venv
source .venv/bin/activate

# On Windows (PowerShell)
# python -m venv .venv
# .venv\Scripts\Activate.ps1

# 3) Install Flask
pip install Flask

# 4) (Optional) Save dependencies
pip freeze > requirements.txt

Your First Flask App

Create app.py with a basic route that returns text.

Practice: Create the file and run it.

from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Hello, Flask API!"

if __name__ == "__main__":
    app.run(debug=True)  # debug=True restarts on code changes

Run and Test

  • Run your app and open the URL in a browser
  • Use curl to test from the terminal

Practice: Start the server and test the root route.

# Run the app
python app.py
# Flask will show: Running on http://127.0.0.1:5000/

# Test in another terminal:
curl http://127.0.0.1:5000/
# Expected: Hello, Flask API!

Routes and Methods

  • @app.route defines a URL path
  • GET is the default method
  • Create multiple routes

Practice: Add a /ping route that returns "pong".

from flask import Flask
app = Flask(__name__)

@app.route("/")
def home():
    return "Welcome!"

@app.route("/ping")
def ping():
    return "pong"  # GET request

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

Return JSON Responses

  • APIs usually return JSON
  • Use jsonify(...) for correct headers

Practice: Make a /status route that returns JSON.

from flask import Flask, jsonify
app = Flask(__name__)

@app.route("/status")
def status():
    data = {"service": "flask-demo", "status": "ok"}
    return jsonify(data)  # Content-Type: application/json

# Flask 1.1+ also allows: return {"service":"flask-demo","status":"ok"}

Path Parameters

  • Capture parts of the URL with placeholders
  • Flask converts types if you specify them

Practice: Visit /hello/StudentName

from flask import Flask, jsonify
app = Flask(__name__)

@app.route("/hello/<name>")
def hello(name):
    return jsonify({"message": f"Hello, {name}!"})

@app.route("/square/<int:n>")
def square(n):
    return jsonify({"n": n, "square": n * n})

Query Parameters

  • Access with request.args
  • Provide defaults to avoid None

Practice: Try /search?q=flask&page=2

from flask import Flask, request, jsonify
app = Flask(__name__)

@app.route("/search")
def search():
    q = request.args.get("q", "")
    page = int(request.args.get("page", 1))
    return jsonify({"query": q, "page": page})

POST JSON to the Server (request.get_json)

  • POST sends data in the request body (not in the URL).
  • Send header: Content-Type: application/json and a JSON body.
  • Use request.is_json to check the header; request.get_json(silent=True) to parse safely.
  • Validate inputs and return clear errors (400 Bad Request). Use 201 Created when creating resources.

Practice (in a new terminal):

# Echo back what you send
curl -X POST http://127.0.0.1:5000/echo \
  -H "Content-Type: application/json" \
  -d '{"hello":"world"}'

# Create a user (valid)
curl -X POST http://127.0.0.1:5000/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Ana","age":20}'

# Invalid JSON (shows an error)
curl -X POST http://127.0.0.1:5000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Ana", "age": not_a_number}'
from flask import Flask, request, jsonify
app = Flask(__name__)

def error(msg, status=400):
    return jsonify({"error": msg}), status

@app.route("/echo", methods=["POST"])
def echo():
    if not request.is_json:
        return error("Content-Type must be application/json")
    data = request.get_json(silent=True)
    if data is None:
        return error("Invalid JSON")
    return jsonify({"you_sent": data}), 200

@app.route("/users", methods=["POST"])
def create_user():
    if not request.is_json:
        return error("Content-Type must be application/json")
    body = request.get_json(silent=True)
    if body is None:
        return error("Invalid JSON")
    name = (body.get("name") or "").strip()
    age = body.get("age")
    if not name:
        return error("name is required")
    try:
        age = int(age)
    except (TypeError, ValueError):
        return error("age must be an integer")
    user = {"id": 1, "name": name, "age": age}
    return jsonify(user), 201  # Created

Build a Simple Items API

  • Use a list as temporary storage (resets on restart)
  • Implement GET and POST

Practice: Add an item with POST, list with GET.

from flask import Flask, request, jsonify
app = Flask(__name__)

items = []
next_id = 1

@app.route("/items", methods=["GET"])
def list_items():
    return jsonify(items)

@app.route("/items", methods=["POST"])
def add_item():
    global next_id
    body = request.get_json() or {}
    name = body.get("name")
    if not name:
        return jsonify({"error": "name is required"}), 400
    item = {"id": next_id, "name": name}
    items.append(item)
    next_id += 1
    return jsonify(item), 201

Update and Delete Items

  • PUT to update
  • DELETE to remove

Practice: Update an item name and then delete it.

from flask import Flask, request, jsonify
app = Flask(__name__)

# Assume items and next_id defined as in previous slide

def find_item(item_id):
    return next((i for i in items if i["id"] == item_id), None)

@app.route("/items/<int:item_id>", methods=["PUT"])
def update_item(item_id):
    item = find_item(item_id)
    if not item:
        return jsonify({"error": "not found"}), 404
    body = request.get_json() or {}
    name = body.get("name")
    if not name:
        return jsonify({"error": "name is required"}), 400
    item["name"] = name
    return jsonify(item)

@app.route("/items/<int:item_id>", methods=["DELETE"])
def delete_item(item_id):
    item = find_item(item_id)
    if not item:
        return jsonify({"error": "not found"}), 404
    items.remove(item)
    return "", 204

Practice: Mini Todo API

  • Create routes:
    • GET /todos — list all todos
    • POST /todos — create todo with {"title": "Read"}
    • PUT /todos/<int:id> — update title or mark done
    • DELETE /todos/<int:id> — remove a todo
  • Return proper status codes (201 for create, 404 if not found)
  • Bonus: Filter with query params, e.g., /todos?done=true

Common Pitfalls

  • Forgetting to activate the virtual environment
  • Using wrong URL (127.0.0.1 vs 0.0.0.0)
  • Not setting Content-Type: application/json for POST/PUT
  • Forgetting methods=["POST"] on POST routes
  • Relying on in-memory data for real apps (use a database later)
  • Leaving debug=True in production

Next Steps

  • Read: Flask Docs (flask.palletsprojects.com)
  • Use a database (SQLite + SQLAlchemy)
  • Testing with pytest and Flask test client
  • Deployment: gunicorn + a platform (Render, Railway, Fly.io, etc.)

Congrats! You can now build and test simple Flask APIs.