Success
It worked!
If you can read this page, the web server is installed, the certificate is valid, and requests are reaching the application. This is a test page for a small web stack.
This page exists to confirm that a new site is up. It also doubles as a style sampler: below you will find headings, paragraphs, lists, a table, a quotation, and some code, the kinds of building blocks nearly every site needs. If you are learning a web stack, treat it as a cheat sheet.
What is running here
The stack is deliberately small. Each piece does one job, and there is very little between a request and the response.
- Go handles requests with the standard library's
net/httpserver and ships as a single static binary. - Caddy terminates HTTPS, obtains certificates automatically, and proxies requests to the application.
- HTMX lets plain HTML make requests and swap fragments of the page without a JavaScript framework.
- Tailwind CSS provides utility classes for styling, compiled ahead of time into one small stylesheet.
- PostgreSQL stores the data, reached through hand-written SQL.
- systemd starts the application at boot and restarts it if it stops.
How a request flows
- The browser looks up the hostname and connects over HTTPS on port 443.
- Caddy presents a certificate and decrypts the request. Plain HTTP on port 80 is redirected to HTTPS.
- Caddy forwards the request to the application on a local-only port.
- The application renders an HTML page, or just a fragment of one, and sends it back.
- The browser displays the result. HTMX can then fetch further fragments on demand.
Make it work, make it right, make it fast, in that order.
Sample code
A Go handler that returns a greeting looks like this:
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.PathValue("name"))
}
mux := http.NewServeMux()
mux.HandleFunc("GET /hello/{name}", hello)
http.ListenAndServe("127.0.0.1:8080", mux)
An HTMX button that loads a fragment into the page needs no JavaScript at all:
<button hx-get="/clock" hx-target="#time" hx-swap="innerHTML">
What time is it?
</button>
<span id="time"></span>
A Caddy site block that proxies to the application is only a few lines:
example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
}
Typography samples
Body text can contain bold, italic, inline code, and links. Long paragraphs should stay readable: lines of roughly sixty to eighty characters, generous line height, and enough contrast between text and background.
A small table
| Layer | Tool | Purpose |
|---|---|---|
| Edge | Caddy | HTTPS and reverse proxy |
| Application | Go | Routing and HTML rendering |
| Interactivity | HTMX | Partial page updates |
| Styling | Tailwind CSS | Utility-first CSS |
| Storage | PostgreSQL | Relational data |
A card
Next steps
If you are exploring this stack yourself, a good order is: serve a static page, add a dynamic route, connect a database, then sprinkle in HTMX where a page should update in place. Each step builds on the last, and every step is small enough to understand completely.
- Read the
net/httppackage documentation. - Try the Caddy
file_serverdirective for static content. - Experiment with HTMX attributes such as
hx-post,hx-trigger, andhx-swap. - Build a tiny to-do list: it touches routing, forms, storage, and partial updates.
That's all there is to it. The server is working, and this page will be replaced once there is something more interesting to show.