Skip to content

🏗️ Building a Simple API

Creating REST APIs with Python's http.server


Introduction

Python's http.server module lets you build HTTP servers without external frameworks. While not suitable for production (use Flask, FastAPI, or Django for that), it's perfect for:

  • Learning how HTTP servers work
  • Testing and prototyping
  • Local development tools
  • Understanding what frameworks do for you

Basic HTTP Server

from http.server import HTTPServer, BaseHTTPRequestHandler

class SimpleHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # Send 200 OK status
        self.send_response(200)
        # Send headers
        self.send_header("Content-Type", "text/plain")
        self.end_headers()
        # Send body
        self.wfile.write(b"Hello, World!")

# Start server
server = HTTPServer(("localhost", 8000), SimpleHandler)
print("Server running on http://localhost:8000")
server.serve_forever()

Run this and visit http://localhost:8000 in your browser!


Handling Routes

Check self.path to route requests:

class RouterHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200)
            self.send_header("Content-Type", "text/plain")
            self.end_headers()
            self.wfile.write(b"Home page")

        elif self.path == "/about":
            self.send_response(200)
            self.send_header("Content-Type", "text/plain")
            self.end_headers()
            self.wfile.write(b"About page")

        else:
            self.send_response(404)
            self.send_header("Content-Type", "text/plain")
            self.end_headers()
            self.wfile.write(b"Page not found")

REST API Example

Let's build a simple Todo API:

import json
from http.server import HTTPServer, BaseHTTPRequestHandler

# In-memory storage
todos = [
    {"id": 1, "task": "Learn Python", "done": False},
    {"id": 2, "task": "Build API", "done": False},
]

class TodoAPI(BaseHTTPRequestHandler):
    def _send_json(self, data, status=200):
        """Helper to send JSON response."""
        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):
        """Handle GET requests."""
        if self.path == "/todos":
            # Get all todos
            self._send_json(todos)

        elif self.path.startswith("/todos/"):
            # Get single todo
            todo_id = int(self.path.split("/")[-1])
            todo = next((t for t in todos if t["id"] == todo_id), None)

            if todo:
                self._send_json(todo)
            else:
                self._send_json({"error": "Not found"}, 404)
        else:
            self._send_json({"error": "Not found"}, 404)

    def do_POST(self):
        """Handle POST requests."""
        if self.path == "/todos":
            # Read request body
            content_length = int(self.headers.get("Content-Length", 0))
            body = self.rfile.read(content_length)
            data = json.loads(body.decode())

            # Create new todo
            new_id = max(t["id"] for t in todos) + 1
            new_todo = {
                "id": new_id,
                "task": data.get("task", ""),
                "done": False
            }
            todos.append(new_todo)

            self._send_json(new_todo, 201)

    def do_DELETE(self):
        """Handle DELETE requests."""
        if self.path.startswith("/todos/"):
            todo_id = int(self.path.split("/")[-1])
            global todos
            todos = [t for t in todos if t["id"] != todo_id]
            self._send_json({"message": "Deleted"})

# Start server
server = HTTPServer(("localhost", 8000), TodoAPI)
print("API running on http://localhost:8000")
server.serve_forever()

Reading Request Body

def do_POST(self):
    # Get content length from headers
    content_length = int(self.headers.get("Content-Length", 0))

    # Read that many bytes
    body = self.rfile.read(content_length)

    # Decode and parse
    text = body.decode("utf-8")
    data = json.loads(text)

    # Process data...

API Design Patterns

RESTful Endpoints

Method Endpoint Action
GET /items List all items
GET /items/1 Get item with ID 1
POST /items Create new item
PUT /items/1 Update item 1
DELETE /items/1 Delete item 1

Response Format

# Success response
{
    "success": True,
    "data": { ... }
}

# Error response
{
    "success": False,
    "error": "Item not found",
    "code": 404
}

Testing Your API

# In another terminal, test with:
import json
from urllib.request import urlopen, Request

# GET all todos
with urlopen("http://localhost:8000/todos") as r:
    print(json.loads(r.read().decode()))

# POST new todo
req = Request(
    "http://localhost:8000/todos",
    data=json.dumps({"task": "New task"}).encode(),
    headers={"Content-Type": "application/json"},
    method="POST"
)
with urlopen(req) as r:
    print(json.loads(r.read().decode()))

Common Mistakes

Mistake Problem Solution
Forgetting end_headers() Client hangs waiting Always call before wfile.write()
Not decoding request body Binary data instead of text Use .decode("utf-8")
Missing Content-Length Can't read POST data Check headers before reading body
Not handling 404s All paths return 200 Check path and return appropriate status

When to Use What

Use Case Solution
Learning/testing http.server
Small production API Flask or FastAPI
Large application Django REST Framework
High performance FastAPI + async

Quick Reference

from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class APIHandler(BaseHTTPRequestHandler):
    def _json_response(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):
        self._json_response({"message": "GET received"})

    def do_POST(self):
        length = int(self.headers.get("Content-Length", 0))
        body = json.loads(self.rfile.read(length).decode())
        self._json_response({"received": body}, 201)

server = HTTPServer(("localhost", 8000), APIHandler)
server.serve_forever()

Next Steps

Now you can build APIs! Let's learn best practices for production-ready code.

→ Continue to 05: API Best Practices