⚡ Zig Guide LiveUnofficialbut fully verified
✓ Zig 0.17.0-dev.2375+d8aab4878What's newOn an older Zig?

What We Are Building

Updated

A URL shortener: you hand it a long address, it hands you a short one, and anyone who follows the short one lands on the long one. It is a common first service. It is small enough to finish, and it still needs everything a bigger service needs: a database, a schema, validation, and an HTTP surface with all four verbs.

The finished service speaks five routes:

RouteDoesWhich is
POST /linksshorten a URL, answer with the slugCreate
GET /{slug}redirect, and count the hitRead
GET /links/{slug}report target and hit countRead
PUT /links/{slug}point the slug somewhere elseUpdate
DELETE /links/{slug}retire the slugDelete

Storage is PostgreSQL, one table:

CREATE TABLE links (
  id     bigserial PRIMARY KEY,
  slug   text UNIQUE NOT NULL,
  target text NOT NULL,
  hits   bigint NOT NULL DEFAULT 0
);

The shape of it

Every chapter in this project builds one box in this picture, bottom to top:

Browser / curl
      │
      ▼
 HTTP Server
      │
      ▼
   Routes ──────────► Slug logic
      │
      ▼
    Store
      │
      ▼
Postgres Client
      │
  TCP socket
      │
      ▼
  PostgreSQL

Read it as two halves. Everything above Store is about HTTP: what a request means and what to answer. Everything below it is about Postgres: how to say the query and how to read the answer. Store is the line between them, and it is the reason most of this code runs without a network.

One request, end to end

There is no JSON anywhere in this service. The body of a POST is the URL itself, and the answer is the slug. That keeps the parsing in the chapters that are about parsing.

curl -i -X POST http://127.0.0.1:8080/links \
  --data 'https://ziglang.org'
HTTP/1.1 201 Created
Content-Length: 2

1

The slug is 1 because it is the row id written in base62, not a random string. Following it gives the redirect, which is the service’s main job:

curl -i http://127.0.0.1:8080/1
HTTP/1.1 302 Found
Location: https://ziglang.org
Content-Length: 0

And the slug’s own record reports where it points and how often it has been followed:

curl -i http://127.0.0.1:8080/links/1
HTTP/1.1 200 OK
Content-Length: 34

1 -> https://ziglang.org (1 hits)

The routes chapter’s expected output contains these same exchanges, and CI replays them on every build.

Decision one: no driver

The service talks to Postgres directly, over its wire protocol. The cookbook recipe showed that protocol byte by byte: every message is a tag, a length, and a payload. This project turns that recipe into something a program can use: a client that connects, authenticates, sends SQL, iterates rows, and survives errors. That is what a driver is. Writing one small enough to read means no part of the path between your code and the database stays hidden.

Decision two: the seams

Almost nothing in this project needs a network to run. This follows the same rule as the rest of the guide: protocol code parses from a Reader, never from a socket. The Postgres client is tested against scripted bytes. The slug logic is arithmetic. The HTTP handlers take request bytes and return response bytes, against a store interface with an in-memory implementation, the same seam the ORM’s Repo chapter used. So those three chapters run in your browser, checked by CI like every other snippet on this site.

Only the last chapter opens sockets. It assembles the client, the slugs and the routes into one file, swaps the in-memory store for one that writes SQL, and serves the whole thing against a real database. CI compiles that file on every run; you run it on your machine, where a real Postgres can answer.

What is deliberately missing

This small project skips some things. Each gap below could be filled behind one of the seams in this design.

  • Connection pooling. One connection serves every request, which is fine at tutorial scale.
  • The extended query protocol. Real drivers send parameters separately from SQL. This project quotes values into SQL text, does it carefully, and the client chapter says exactly what the tradeoff is.
  • SCRAM authentication. The client speaks cleartext password auth, which is what a default local Postgres accepts from loopback. The client chapter names what production adds.
  • Users and rate limits. Nothing here knows who you are.

The web server track covers the HTTP side in full depth; this project reuses its ideas in miniature and keeps the focus on the storage path.