← All projects

Python · Vercel Functions

Quote Generator

Fetch a random quote from the backend and save favorites server-side rather than in local storage.

Every quote comes from the backend's quote pool, and favorites are saved and removed through a server-side Python API rather than browser local storage — meaning favorites are a real piece of server state, not a client trick.

Live demo

embed
from http.server import BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import json

QUOTES = [
    {'id': '1', 'text': 'Simplicity is the soul of efficiency.', 'author': 'Austin Freeman'},
    {'id': '2', 'text': 'Make it work, make it right, make it fast.', 'author': 'Kent Beck'},
    {'id': '3', 'text': 'Programs must be written for people to read.', 'author': 'Harold Abelson'},
    {'id': '4', 'text': 'The best error message is the one that never shows up.', 'author': 'Thomas Fuchs'},
    {'id': '5', 'text': 'Code is like humor. When you have to explain it, it is bad.', 'author': 'Cory House'},
    {'id': '6', 'text': 'First, solve the problem. Then, write the code.', 'author': 'John Johnson'},
    {'id': '7', 'text': 'Any fool can write code that a computer can understand.', 'author': 'Martin Fowler'},
    {'id': '8', 'text': 'Fix the cause, not the symptom.', 'author': 'Steve Maguire'},
]
QUOTES_BY_ID = {q['id']: q for q in QUOTES}

# Module-level state persists across warm invocations of this function
# (resets on cold start) — fine for a demo, same tradeoff as the in-memory
# stores in the Node.js and Go examples in this repo.
favorite_ids = set()


class handler(BaseHTTPRequestHandler):
    def do_GET(self):
        favorites = [q for q in QUOTES if q['id'] in favorite_ids]
        self._json(200, favorites)

    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        raw = self.rfile.read(length) if length else b'{}'
        try:
            payload = json.loads(raw or b'{}')
        except json.JSONDecodeError:
            payload = {}

        quote_id = str(payload.get('id', ''))
        if quote_id not in QUOTES_BY_ID:
            self._json(404, {'error': 'quote not found'})
            return

        favorite_ids.add(quote_id)
        self._json(201, {'ok': True})

    def do_DELETE(self):
        query = parse_qs(urlparse(self.path).query)
        quote_id = (query.get('id', [''])[0] or '')
        favorite_ids.discard(quote_id)
        self._json(200, {'ok': True})

    def _json(self, status, payload):
        body = json.dumps(payload).encode('utf-8')
        self.send_response(status)
        self.send_header('Content-type', 'application/json')
        self.send_header('Content-length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)
let current = null;

async function nextQuote() {
  const res = await fetch('/api/quote');
  current = await res.json();
  document.getElementById('text').textContent = `“${current.text}”`;
  document.getElementById('author').textContent = `— ${current.author}`;
}

async function loadFavorites() {
  const res = await fetch('/api/favorites');
  const favs = await res.json();
  const list = document.getElementById('list');
  list.innerHTML = favs.map((q) => `
    <div class="item">
      <span class="t">“${q.text}”</span>
      <span class="a">${q.author}</span>
      <button class="icon" data-id="${q.id}" title="Remove">✕</button>
    </div>
  `).join('') || '<p class="empty">No favorites saved yet.</p>';

  list.querySelectorAll('button.icon').forEach((btn) => {
    btn.addEventListener('click', async () => {
      await fetch(`/api/favorites?id=${encodeURIComponent(btn.dataset.id)}`, { method: 'DELETE' });
      loadFavorites();
    });
  });
}

document.getElementById('next').addEventListener('click', nextQuote);
document.getElementById('fav').addEventListener('click', async () => {
  if (!current) return;
  await fetch('/api/favorites', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id: current.id }),
  });
  loadFavorites();
});

nextQuote();
loadFavorites();