A Small, Complete API
Time to assemble the pieces: a users API with the five classic endpoints —
POST /users create · GET /users list · GET /users/{id} read one · PUT /users/{id} replace · DELETE /users/{id} remove.
The store is an in-memory dict, so the example stays self-contained. (A real server handles requests concurrently, so a production version would guard the dict with a lock and probably swap it for a database — the handlers would not change shape.)
Create and List
POST parses the incoming JSON, gives the user an id, stores it, and answers 201 with the created record — id included, so the client learns it. GET returns everyone as a JSON array.
POST /users and GET /users
Two creations, then the list: each POST answers 201 with the stored user, and the final GET shows both — the API's state grew with each request.
Output: complete Python program
import json
import threading
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
users = {}
next_id = 1
class UsersHandler(BaseHTTPRequestHandler):
def do_POST(self):
global next_id
length = int(self.headers.get("Content-Length", 0))
try:
data = json.loads(self.rfile.read(length))
except json.JSONDecodeError:
self.reply(400, {"error": "invalid JSON"})
return
user = {"id": next_id, "name": data["name"]}
users[next_id] = user
next_id += 1
self.reply(201, user)
def do_GET(self):
self.reply(200, [users[k] for k in sorted(users)])
def reply(self, status, data):
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write((json.dumps(data) + "\n").encode())
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), UsersHandler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
for name in ["Ana", "Ben"]:
payload = json.dumps({"name": name}).encode()
req = urllib.request.Request(base + "/users", data=payload,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
print("POST /users ->", resp.status, resp.read().decode().strip())
with urllib.request.urlopen(base + "/users") as resp:
print("GET /users ->", resp.status, resp.read().decode().strip())
server.shutdown()
Read One, Replace, Delete
The /users/{id} routes complete the set: read answers 200 or 404, replace parses the new data into the existing id, delete answers 204. After the delete, the same GET that worked before honestly returns 404 — the resource is gone.
GET, PUT and DELETE on /users/{id}
The full life of one resource: read it, replace it (the response carries the new name), delete it, and fail to read it — every step with the correct status code.
Output: complete Python program
import json
import threading
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
users = {1: {"id": 1, "name": "Ana"}}
class UserHandler(BaseHTTPRequestHandler):
def user_id(self):
return int(self.path.rsplit("/", 1)[-1]) # "/users/1" -> 1
def do_GET(self):
user = users.get(self.user_id())
if user is None:
self.reply(404, {"error": "user not found"})
else:
self.reply(200, user)
def do_PUT(self):
length = int(self.headers.get("Content-Length", 0))
data = json.loads(self.rfile.read(length))
user = {"id": self.user_id(), "name": data["name"]} # keep the id
users[self.user_id()] = user
self.reply(200, user)
def do_DELETE(self):
users.pop(self.user_id(), None)
self.send_response(204)
self.end_headers()
def reply(self, status, data):
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write((json.dumps(data) + "\n").encode())
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), UserHandler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
def call(method, path, payload=None):
data = payload.encode() if payload else None
req = urllib.request.Request(base + path, data=data, method=method,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req) as resp:
status, body = resp.status, resp.read().decode()
except urllib.error.HTTPError as err:
status, body = err.code, err.read().decode()
print(f"{method:<6} {path} -> {status} {body.strip()}".rstrip())
call("GET", "/users/1")
call("PUT", "/users/1", '{"name": "Ana Maria"}')
call("DELETE", "/users/1")
call("GET", "/users/1")
server.shutdown()