A concurrent, in-memory URL shortener service written in Go. This is a practical project demonstrating Go's concurrency primitives (sync.RWMutex, sync/atomic), REST API design, middleware composition, and client-server architecture.
The project consists of:
linkd(Daemon/Server): The REST API backend that handles shortening URLs, resolving short keys to original URLs, and atomically tracking click metrics.linkc(Client): A command-line client to interact with the shortener service.
internal/httpio: HTTP I/O utilities. Defines a chainableHandlertype (afunc(w, r) Handlerwhere each handler returns its next step), aResponderwith pre-built handlers for JSON, text, redirect, and error responses, aJsonDecoderthat reads, unmarshals, and optionally validates request bodies via aValidate() errorinterface, and aMaxBytesReaderthat unwraps wrappedResponseWriters before applying a size limit.internal/hlog: Request logging middleware. Captures responseStatusCodeandDurationvia composableMiddlewareFuncwrappers and logs each request with structured attributes usinglog/slog.internal/traceid: Distributed tracing utilities. Generates UUID-based trace IDs, propagates them throughcontext.Context, and enriches everysloglog record with the trace ID via a customslog.Handler.
- Concurrency: Utilizes
sync.RWMutexfor safe concurrent access to the in-memory store andsync/atomicfor race-free click tracking. - Chainable Handlers: The
httpio.Handlertype enables a clean handler-chaining pattern — each handler returns its next step, andServeHTTPdrives the chain. This avoids third-party router dependencies while keeping handler logic composable. - JSON & Form Support: The
/shortenendpoint accepts bothapplication/jsonandapplication/x-www-form-urlencodedrequest bodies, with a 4 KB request size limit to guard against oversized payloads. - REST Endpoints:
GET /: Health check.POST /shorten: Shorten a URL (accepts JSON or form data; returns{"key": "<key>"}on success).GET /resolve/{key}: Resolve a shortened key to its original URL (HTTP 302 redirect) and increment its click counter.PUT /update/{key}: Update the destination URL for an existing key.
- Observability: Every request is logged with method, URL, status code, and duration. A UUID trace ID is injected into each request's context and automatically attached to all related log records via a custom
slog.Handler.
Start the daemon with optional flags:
go run ./cmd/linkd/main.go
go run ./cmd/linkd/main.go -addr localhost:9090 -timeout.read 2s -timeout.idle 10s| Flag | Default | Description |
|---|---|---|
-addr |
localhost:8080 |
Address to listen on |
-timeout.read |
1s |
HTTP server read timeout |
-timeout.idle |
5s |
Idle connection timeout |
Use the client to interact with the API:
go run ./cmd/linkc/main.goThe linkc CLI supports the following commands:
health: Check the server status.shorten <link> [key]: Shorten a given link with an optional custom key.resolve <key>: Resolve a shortened key back to its original link.update <key> <link>: Update the destination link for an existing key.
To run the tests, data race tests, and benchmarks for this specific project:
go test ./...
go test -race ./...
go test -run=' ' -bench=. ./...