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" \
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
| Feature | Flask | FastAPI |
|---|---|---|
| Learning curve | Lower | Low-Medium |
| Performance | Good | Excellent |
| Type validation | Manual | Automatic (Pydantic) |
| Auto docs | ❌ | ✅ (Swagger UI) |
| Async support | Via extensions | Native |
| Best for | Simple APIs, webhooks | Full 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