thecodex.expert · The Codex Family of Knowledge
Tier 3 · Upper-Intermediate · Python Project

REST API

Build a JSON REST API with proper routes and methods — create, read, update, delete records over HTTP. The backbone of modern apps.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a REST API: a web service that lets clients create, read, update, and delete records (a to-do list, say) using standard HTTP methods (GET, POST, PUT, DELETE) and JSON. It teaches the conventions of REST — how the entire web of apps and services talks to itself.

Where this shows up: every mobile app backend, every single-page web app, every microservice, every public API (Stripe, Twitter, GitHub). REST is the lingua franca of the internet. Understanding it is essential to backend work.

2 How to Think About It

Think about how REST maps actions to HTTP, before any code:

The plan — in plain English
1. Each record lives at a URL like /todos/3. → 2. The HTTP method says what to do: GET reads, POST creates, PUT updates, DELETE removes. → 3. The body carries JSON data. → 4. The server routes each request to the right handler and returns JSON. This method-plus-URL convention is REST.

GET

POST

PUT

DELETE

Request arrives

Which method?

Return records as JSON

Create a record

Update a record

Remove a record

Send JSON response

3 The Build — explained part by part

Here is a complete REST API using only Python’s built-in http.server — no framework needed. Each part is explained below.

Pythonapi.py
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

todos = {}      # id -> todo dict
next_id = 1

class Handler(BaseHTTPRequestHandler):
    def _send(self, code, data):
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def do_GET(self):
        # GET /todos -> list every todo.
        self._send(200, list(todos.values()))

    def do_POST(self):
        # POST /todos -> create a todo from the JSON body.
        global next_id
        length = int(self.headers["Content-Length"])
        body = json.loads(self.rfile.read(length))
        todo = {"id": next_id, "task": body["task"], "done": False}
        todos[next_id] = todo
        next_id += 1
        self._send(201, todo)

    def do_DELETE(self):
        # DELETE /todos/3 -> remove that todo.
        todo_id = int(self.path.split("/")[-1])
        if todo_id in todos:
            del todos[todo_id]
            self._send(200, {"deleted": todo_id})
        else:
            self._send(404, {"error": "not found"})

if __name__ == "__main__":
    HTTPServer(("localhost", 8000), Handler).serve_forever()
What each part does — in plain words
do_GET, do_POST, do_DELETE — the magic of http.server: name a method do_GET and it runs for every GET request. The HTTP method picks the handler automatically — that is REST’s method convention in action.

_send(code, data) — a helper that sends a status code (200 OK, 201 Created, 404 Not Found) and a JSON body. Reusing it keeps every response consistent.

do_POST — read the Content-Length to know how many bytes the body is, parse the JSON, build a new todo with the next ID, and return it with 201 Created.

self.path.split("/")[-1] — pull the ID out of a URL like /todos/3 by taking the last path piece.

status codes matter201 for created, 404 for not found. Clients rely on these codes to know what happened.
Common mistakes — and how to avoid them
✗ Returning 200 for a newly created record — REST convention is 201 Created.
✓ Use the right status code: 201 for create, 200 for read, 404 for missing.
✗ Forgetting to read Content-Length — you read too few or too many bytes of the body.
✓ Read exactly Content-Length bytes before parsing JSON.
✗ Mixing data logic into the handlers — then nothing is testable.
✓ Keep create/delete as plain functions; let handlers call them.

4 Test & Prove Each Part

We test the data operations — create and delete — directly, separate from the HTTP layer, so tests are fast and need no running server.

Creating a todo assigns an ID and stores it
Deleting a todo removes it
Deleting a missing todo reports not-found
Pythontest_api.py
def create_todo(store, next_id, task):
    todo = {"id": next_id, "task": task, "done": False}
    store[next_id] = todo
    return todo


def delete_todo(store, todo_id):
    if todo_id in store:
        del store[todo_id]
        return True
    return False


def test_create():
    """Creating stores a todo with its ID."""
    store = {}
    todo = create_todo(store, 1, "Buy milk")
    assert todo["id"] == 1
    assert store[1]["task"] == "Buy milk"


def test_delete():
    """Deleting removes the todo."""
    store = {1: {"id": 1}}
    assert delete_todo(store, 1) is True
    assert 1 not in store


def test_delete_missing():
    """Deleting a missing todo returns False."""
    assert delete_todo({}, 99) is False

Run with pytest -v. We test the data logic apart from the HTTP plumbing — a key technique. The handlers just connect HTTP to these tested operations.

5 API Documentation

The API’s endpoints, documented like any professional REST service.

GET/todoslist all todos
POST/todoscreate a todo (JSON body)
Request body
{"task": "Buy milk"}
Response (201)
{"id": 1, "task": "Buy milk", "done": false}
DELETE/todos/{id}remove a todo

6 Run It & Automate It

Run it locally
python3 api.py
Then in another terminal: curl -X POST localhost:8000/todos -d '{"task":"Test"}' to create, and curl localhost:8000/todos to list.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
# POST /todos {"task":"Buy milk"}
{"id": 1, "task": "Buy milk", "done": false}

# GET /todos
[{"id": 1, "task": "Buy milk", "done": false}]
If it breaks — how to fix it
🚨 KeyError: 'Content-Length'.
The request had no body. Guard for it, or ensure the client sends one on POST.
🚨 The client gets no JSON back.
Make sure you set the Content-Type header and encode the body with .encode().
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Add PUT. Implement updating a todo’s done status. (Teaches: the update half of CRUD.)
  2. Persist to disk. Save todos to JSON so they survive restarts. (Teaches: combining with persistence.)
  3. Use a framework. Rebuild it with Flask or FastAPI. (Teaches: real-world tools.)
What you learned
You built a real REST API: routing by HTTP method, JSON request/response bodies, and meaningful status codes — the conventions every web service shares. You also learned to test the data logic apart from the HTTP layer. Related: http.server, json.