Skip to main content

Building Small Web Services

What You'll Learn

How to build a minimal web service using Flask (simple) or FastAPI (modern, typed), serve JSON endpoints, and run it on a server.

When to Build a Web Service

Build a web service when you need to:

  • Receive webhooks from Stripe, GitHub, Slack, etc.
  • Expose an internal data endpoint for dashboards
  • Build a health check endpoint for monitoring
  • Create a simple API for other tools to call

Flask — Simple and Minimal

pip install flask
# app.py
from flask import Flask, request, jsonify
from pathlib import Path
import json

app = Flask(__name__)


@app.route("/health")
def health():
"""Health check endpoint."""
return jsonify({"status": "ok", "version": "1.0.0"})


@app.route("/users", methods=["GET"])
def list_users():
"""Return all users as JSON."""
users = load_users() # your data loading logic
return jsonify({"users": users, "count": len(users)})


@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id: int):
"""Return a single user by ID."""
user = find_user(user_id)
if user is None:
return jsonify({"error": "User not found"}), 404
return jsonify(user)


@app.route("/users", methods=["POST"])
def create_user():
"""Create a new user from JSON body."""
data = request.get_json()
if not data or "name" not in data:
return jsonify({"error": "name is required"}), 400
user = save_user(data)
return jsonify(user), 201


if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)

Run:

python3 app.py
# * Running on http://127.0.0.1:5000

Test:

curl http://localhost:5000/health
curl http://localhost:5000/users
curl -X POST http://localhost:5000/users \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "[email protected]"}'

FastAPI — Modern, Typed, Fast

pip install fastapi uvicorn
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI(title="My API", version="1.0.0")


class User(BaseModel):
name: str
email: str
active: bool = True


class UserResponse(User):
id: int


@app.get("/health")
def health() -> dict:
return {"status": "ok"}


@app.get("/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int) -> UserResponse:
user = find_user(user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user


@app.post("/users", response_model=UserResponse, status_code=201)
def create_user(user: User) -> UserResponse:
"""Create a new user. FastAPI validates the request body automatically."""
return save_user(user)

Run:

uvicorn main:app --host 127.0.0.1 --port 8000 --reload

FastAPI automatically generates:

  • Interactive docs at http://localhost:8000/docs
  • OpenAPI schema at http://localhost:8000/openapi.json

Returning Error Responses

# Flask
@app.route("/items/<int:item_id>")
def get_item(item_id):
item = db.get(item_id)
if not item:
return jsonify({"error": "not found", "id": item_id}), 404
return jsonify(item)

# FastAPI
@app.get("/items/{item_id}")
def get_item(item_id: int):
item = db.get(item_id)
if not item:
raise HTTPException(status_code=404, detail=f"Item {item_id} not found")
return item

Simple Webhook Receiver

# Flask webhook receiver
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "your-secret-here"


def verify_signature(payload: bytes, signature: str) -> bool:
expected = hmac.new(
WEBHOOK_SECRET.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)


@app.route("/webhooks/github", methods=["POST"])
def github_webhook():
signature = request.headers.get("X-Hub-Signature-256", "")
if not verify_signature(request.data, signature):
return jsonify({"error": "invalid signature"}), 403

event = request.headers.get("X-GitHub-Event")
payload = request.get_json()

if event == "push":
branch = payload["ref"].split("/")[-1]
commits = len(payload["commits"])
print(f"Push to {branch}: {commits} commit(s)")
# trigger your CI/CD here

return jsonify({"ok": True})

Running in Production

For production (not development debug server):

# Flask with gunicorn
pip install gunicorn
gunicorn app:app --bind 0.0.0.0:8000 --workers 4

# FastAPI with uvicorn
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

With systemd:

[Service]
ExecStart=/opt/myapp/venv/bin/gunicorn app:app \
--bind 0.0.0.0:8000 \
--workers 4 \
--timeout 30 \
--log-file /var/log/myapp/gunicorn.log

Flask vs FastAPI

FeatureFlaskFastAPI
Learning curveLowerLow-Medium
PerformanceGoodExcellent
Type validationManualAutomatic (Pydantic)
Auto docs✅ (Swagger UI)
Async supportVia extensionsNative
Best forSimple APIs, webhooksFull APIs, with docs

Quick Reference

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

@app.route("/path", methods=["GET", "POST"])
def handler():
data = request.get_json()
return jsonify({"key": "value"}), 200

app.run(host="127.0.0.1", port=5000)

# FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()

class Item(BaseModel):
name: str

@app.get("/items/{id}")
def get(id: int): ...

@app.post("/items", status_code=201)
def create(item: Item): ...

# uvicorn main:app --reload

What's Next

Lesson 3: Webhooks and Integrations