A web based chat application. The server is written in Go. The pages are rendered on the server with templ, styled with shadcn-templ components, and updated in the browser with htmx.
This is step one. The application shows one page with a button. A click on the button sends a request, and the server answers with an HTML fragment.
| Part | Choice | Version |
|---|---|---|
| Language | Go | 1.27 |
| Templates | templ | 0.3 |
| Components | shadcn-templ | 2.0 |
| Styling | Tailwind CSS | 4.3.3 |
| Browser updates | htmx | 4.0.0 |
| Linting | golangci-lint | 2.13.2 |
| Markup check | html-validate | 10 |
| Database | PostgreSQL | 18 |
| Queries | sqlc and pgx | 1.31.1 |
| Migrations | goose | 3 |
The file assets/js/htmx.min.js is htmx 4.0.0. The name carries no version, so
read the version here. htmx 4 is not the default version on npm, so every
reference must name 4.0.0.
All static files are compiled into the binary with go:embed. The program runs
with no other files next to it.
Install Go 1.27 or later. Then install the two command line tools:
go install github.com/go-task/task/v3/cmd/task@latest
go install github.com/axadrn/shadcn-templ/v2/cmd/shadcn-templ@latest
The templ command is a Go tool of this module, so go tool templ always uses
the version in go.mod. The Tailwind command is downloaded to ./bin by the
tools task, and git ignores that directory.
The repository holds a devcontainer. Open the folder in an editor that supports devcontainers, or start it from the command line:
devcontainer up --workspace-folder .
The container is the Go 1.27 image. It installs task and shadcn-templ,
downloads the module dependencies and the Tailwind binary, and forwards the
ports 8080 and 7331.
With the devcontainer you can skip the next section.
Start the database and write the development users:
task db:up
task seed
Start the server and the file watchers:
task dev
Open http://localhost:8080. The templ watcher reloads the browser after each
change.
Build the binary:
task build
Check the code before you commit:
task fix
task lint
task fmt
task html
The page follows the light or dark theme of the operating system. There is no switch.
The project follows the Chrome modern web guidance. The Baseline target is Baseline Widely available. The server sends a strict Content Security Policy, packs text answers with gzip, and serves every static file under an address that carries a hash of the content.
Run task alone to see all tasks.
A visitor types an email address on /login.
- A person with a passkey signs in with the passkey.
- A person without one receives a six digit code by SMS, and the page then offers to create a passkey.
There is no SMS provider yet. The server prints the message to standard
output, so the code stands in the terminal that runs task dev.
The seed writes two users, and both carry the same phone number. Sign in as
arne@ejbygruppe.dk or test-jorgensen@ejbygruppe.dk.
A passkey belongs to one address. ORIGIN, or the flag -origin, must be the
address that the browser shows. The default is http://localhost:8080.
A conversation carries a subject and a fixed set of people. A signed in person sees their conversations in the sidebar, starts a new one, reads it and writes in it. The list counts the messages that this person has not read, and the sidebar also holds the name and the way out.
The page asks the server for new messages every three seconds, so an answer from somebody else appears without a reload.
task image
That builds the binaries and the image for amd64 and arm64 and publishes
nothing. A push to main that passes the CI workflow writes the next patch tag
and pushes the image to ghcr.io/spejder/chat.
The image starts from scratch and carries one file, the binary, which holds
every static file and the root certificates. Give it DATABASE_URL and
ORIGIN:
docker run --rm -p 8080:8080 \
-e DATABASE_URL=postgres://chat:chat@postgres:5432/chat?sslmode=disable \
-e ORIGIN=https://chat.example.com \
ghcr.io/spejder/chat:latest
GitHub Actions builds and tests the project, and runs the linter, on every push to main and on every pull request. The workflow calls the same tasks that you call, so a green build on your machine means a green build there.
shadcn-templ add <name>
task generate
go mod tidy
The command copies the source into internal/components, so the project owns
the code and you can edit it.
| Path | Content |
|---|---|
cmd/chat |
The program that starts the server |
internal/server |
Routes and HTTP handlers |
internal/web |
Page templates and fragments |
internal/components |
shadcn-templ components |
internal/utils |
Helpers that the components need |
assets |
CSS, JavaScript and the embed declaration |