This content originally appeared on DEV Community and was authored by nk sk
When developing modern applications, itβs common to hit a roadblock because the real backend service isnβt ready, or you need to test against multiple APIs at once. Thatβs where a mock server comes in: a lightweight server that returns fake data and files to mimic the behavior of your real backend.
Python is an excellent choice for building mock servers because:
- It ships with a built-in HTTP server.
- It has lightweight frameworks like Flask and FastAPI.
- It can scale from a quick one-liner to a production-like simulation.
In this guide, weβll cover all Python-based options β from serving static files, to full CRUD APIs, to enterprise-style mocking.
1. The Easiest Option: Pythonβs Built-in HTTP Server
Python ships with http.server
, a simple web server module. Itβs the fastest way to expose a directory of files over HTTP.
One-liner:
python3 -m http.server 8080 --directory ./mocks
- Serves static files (
./mocks/users.json
βhttp://localhost:8080/users.json
). - Supports file download (for
.pdf
,.zip
, etc.). - Useful for quick frontend testing.
Options you can use:
-
port
β change port (default 8000). -
--directory
β serve a different folder. -
--bind
β bind to specific IP (e.g.,127.0.0.1
). -
--cgi
β enable CGI scripts for basic dynamic responses.
Best for: serving static JSONs, files, or mock downloads.
Limitations: no dynamic routes, no CRUD.
2. Extending http.server
with Custom Handlers
If you need basic API-like behavior, subclass BaseHTTPRequestHandler
.
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
users = [{"id": 1, "name": "Alice"}]
class MockHandler(BaseHTTPRequestHandler):
def _send_json(self, data, status=200):
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def do_GET(self):
if self.path == "/api/users":
self._send_json(users)
else:
self._send_json({"error": "Not found"}, 404)
def do_POST(self):
if self.path == "/api/users":
length = int(self.headers["Content-Length"])
body = json.loads(self.rfile.read(length))
users.append(body)
self._send_json(body, 201)
httpd = HTTPServer(("localhost", 8080), MockHandler)
print("Mock server running at http://localhost:8080")
httpd.serve_forever()
Supports:
- GET β returns JSON
- POST β add data
- Extendable with PUT/DELETE
Limitations:
- In-memory only (data resets on restart).
- More coding effort than Flask/FastAPI.
3. Flask for Flexible Mock APIs
Flask is a minimal Python web framework that makes CRUD APIs + file serving simple.
from flask import Flask, jsonify, request, send_from_directory
import os
app = Flask(__name__)
users = [{"id": 1, "name": "Alice"}]
# --- CRUD Endpoints ---
@app.route("/api/users", methods=["GET"])
def get_users():
return jsonify(users)
@app.route("/api/users", methods=["POST"])
def add_user():
user = request.json
users.append(user)
return jsonify(user), 201
@app.route("/api/users/<int:user_id>", methods=["PUT"])
def update_user(user_id):
for u in users:
if u["id"] == user_id:
u.update(request.json)
return jsonify(u)
return jsonify({"error": "Not found"}), 404
@app.route("/api/users/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
global users
users = [u for u in users if u["id"] != user_id]
return jsonify({"message": "deleted"})
# --- File Download ---
@app.route("/files/<path:filename>")
def download_file(filename):
return send_from_directory("files", filename)
if __name__ == "__main__":
os.makedirs("files", exist_ok=True)
app.run(port=5000, debug=True)
Features:
- Full CRUD (
GET/POST/PUT/DELETE
). - File download support (
/files/sample.pdf
). - Easy to add new routes.
Limitation: not type-safe, no auto-generated docs.
4. FastAPI for Production-Like Mock Servers
FastAPI is a modern Python framework with async support, OpenAPI, and Swagger docs. Perfect for mocks that look like a real backend.
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
import os
app = FastAPI()
class User(BaseModel):
id: int
name: str
users = [User(id=1, name="Alice")]
@app.get("/api/users")
def get_users():
return users
@app.post("/api/users")
def add_user(user: User):
users.append(user)
return user
@app.put("/api/users/{user_id}")
def update_user(user_id: int, user: User):
for i, u in enumerate(users):
if u.id == user_id:
users[i] = user
return user
return {"error": "Not found"}
@app.delete("/api/users/{user_id}")
def delete_user(user_id: int):
global users
users = [u for u in users if u.id != user_id]
return {"message": "deleted"}
@app.get("/files/{filename}")
def download_file(filename: str):
return FileResponse(f"files/{filename}")
if __name__ == "__main__":
os.makedirs("files", exist_ok=True)
Run:
uvicorn mock_server:app --reload --port 8000
Auto-generated Swagger UI:
http://localhost:8000/docs
Features:
- Full CRUD with schema validation.
- File serving (
/files/<filename>
). - Auto-generated OpenAPI/Swagger docs.
- Type-safe with Pydantic models.
Limitation: requires
fastapi
+ uvicorn
install.
5. Test-Only Mocking (No Server Needed)
If youβre writing Python tests and donβt need an actual server:
-
responses
library (interceptsrequests
calls). -
requests-mock
(pytest-friendly).
Example with responses
:
import responses, requests
@responses.activate
def test_mock():
responses.add(
responses.GET, "http://api.test/users",
json=[{"id": 1, "name": "Alice"}], status=200
)
resp = requests.get("http://api.test/users")
assert resp.json()[0]["name"] == "Alice"
Best for: unit tests.
Not usable by frontends (since no actual server runs).
6. Running Multiple Mock Servers
If your app depends on multiple services:
Option A: Multiple http.server
instances
python3 -m http.server 8001 --directory ./service1
python3 -m http.server 8002 --directory ./service2
Option B: Multiple Flask/FastAPI apps
Run different servers on different ports.
Option C: Docker Compose
Define multiple mock services in docker-compose.yml
:
version: '3'
services:
users:
build: ./users-mock
ports: ["8001:5000"]
orders:
build: ./orders-mock
ports: ["8002:5000"]
Perfect for microservices testing.
7. Enhancing Mocks with Realism
A mock server shouldnβt just return βhappy pathβ responses. You can make it more production-like by:
-
Randomized fake data β use
faker
library. -
Error simulation β sometimes return
500
,404
, or slow responses. -
Versioning APIs β serve
/v1/users.json
vs/v2/users.json
. - Environment configs β switch between real and mock easily.
Summary: When to Use Which
Approach | Best For | Pros | Cons |
---|---|---|---|
http.server |
Static files, quick downloads | Zero setup | No dynamic logic |
Custom BaseHTTPRequestHandler
|
Lightweight custom JSON APIs | Built-in, simple | Harder to extend |
Flask | CRUD + file serving | Easy, readable | No schema validation |
FastAPI | Realistic mocks, Swagger docs | Modern, OpenAPI | Needs more setup |
responses / requests-mock
|
Unit tests (no server) | Simple, fast | Only inside Python tests |
Multi-instance/Docker mocks | Microservices | Scales well | Extra setup |
Final Thoughts
Mock servers in Python can be as simple as:
-
python3 -m http.server
for files and JSON. - Flask for CRUD APIs + downloads.
- FastAPI for realistic, contract-driven mocks.
- Or even
responses
for test-only mocks.
By combining these approaches, you can create a mock ecosystem that unblocks frontend developers, enables CI/CD testing, and simulates multiple backend services β all with Python.
This content originally appeared on DEV Community and was authored by nk sk