https://github.com/alexfalkowski/web
A website lean-thoughts.com.
https://github.com/alexfalkowski/web
cucumber golang htmx make ruby
Last synced: 22 days ago
JSON representation
A website lean-thoughts.com.
- Host: GitHub
- URL: https://github.com/alexfalkowski/web
- Owner: alexfalkowski
- License: mit
- Created: 2024-07-09T10:42:51.000Z (about 2 years ago)
- Default Branch: master
- Last Pushed: 2026-06-08T11:33:13.000Z (about 2 months ago)
- Last Synced: 2026-06-08T13:22:14.029Z (about 2 months ago)
- Topics: cucumber, golang, htmx, make, ruby
- Language: Go
- Homepage: https://alexfalkowski.github.io/web
- Size: 1.97 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
[](https://circleci.com/gh/alexfalkowski/web)
[](https://codecov.io/gh/alexfalkowski/web)
[](https://goreportcard.com/report/github.com/alexfalkowski/web)
[](https://pkg.go.dev/github.com/alexfalkowski/web)
[](https://masterminds.github.io/stability/active.html)
# ๐ Web
A small Go service that serves the website at:
-
The service is built on top of the [`mvc`](https://github.com/alexfalkowski/go-service/tree/master/net/http/mvc) package from `go-service` and ships as a single binary with server-side templates, content, the favicon, and static site assets embedded.
## ๐งญ Background
This project is an implementation playground for the ideas outlined in:
-
## โจ What it does
At a high level the service:
- serves the home page (`/`)
- serves a books page (`/books`)
- serves `robots.txt` (`/robots.txt`)
- serves `sitemap.xml` (`/sitemap.xml`)
- serves the favicon (`/favicon.ico`)
- renders a custom not-found page for missing routes
- adds browser security headers to site responses
- exposes health, liveness/readiness, and metrics endpoints
The HTML templates, error templates, books YAML data, favicon image, robots file, and sitemap are embedded into the binary using `go:embed`. Full-page browser rendering also loads HTMX and Pico CSS from jsDelivr, with those origins allowed by the response CSP.
## ๐๏ธ Architecture overview
### ๐๏ธ Project layout
This repo follows the structure described in:
-
Key directories:
- `main.go`: entrypoint for the `web` binary
- `internal/`: application code (not importable from other modules)
- `test/`: acceptance/system tests and supporting Ruby test client
- `bin/` + `Makefile`: build/dev/test automation
### ๐งฉ Dependency injection and modules
The service is wired with dependency injection using `go-service/v2/di`. The top-level module that assembles the server is:
- `internal/cmd.Module`
It pulls in configuration, health, and site modules.
### ๐ฃ๏ธ MVC routing and rendering
Routing and rendering are handled using:
- `go-service/v2/net/http/mvc`
Feature modules (e.g. books/root/robots/sitemap) register their routes during DI wiring.
### ๐ฆ Embedded assets
The site package embeds:
- templates for layout, pages, and not-found errors
- the books YAML file used to render the books page
- the favicon PNG
- `robots.txt`
- `sitemap.xml`
See:
- `internal/site/site.go`
## ๐ Endpoints
### ๐ Pages
- `GET /` renders the home page
- `PUT /` renders a partial/fragment version of the home page (used for incremental updates)
- `GET /books` renders the books page
- `PUT /books` renders a partial/fragment version of the books page
- `GET /robots.txt` serves the robots file as a static asset
- `GET /sitemap.xml` serves the sitemap file as a static asset
- `GET /favicon.ico` serves the browser favicon
- missing routes render a `404` not-found page
> [!NOTE]
> The `PUT` endpoints exist to support partial rendering patterns, for example HTMX-style incremental updates. The exact response shape depends on the templates/layout configured in the MVC layer.
> [!TIP]
> Use `GET` when checking complete pages in a browser and `PUT` when checking fragment rendering.
### ๐ซ Health and observability
The HTTP transport registers service-prefixed health and observability routes.
With the local service name `web`, the endpoints are:
- `/web/healthz` (overall health / online)
- `/web/livez` (liveness)
- `/web/readyz` (readiness)
- `/web/metrics` (Prometheus metrics)
Health timings are configured via the service config under the `health` section.
`/web/healthz` uses the default `go-health/v2` online registration, so it can depend on public connectivity. `/web/livez` and `/web/readyz` use noop checks.
### ๐ก๏ธ Response headers
Site responses include browser security headers such as `Content-Security-Policy`, `X-Content-Type-Options`, `Referrer-Policy`, `X-Frame-Options`, `Permissions-Policy`, and `Strict-Transport-Security`.
The CSP intentionally permits jsDelivr for HTMX and Pico CSS, plus Cloudflare Insights script and beacon origins (`static.cloudflareinsights.com` and `cloudflareinsights.com`) for production browser analytics.
Embedded static assets also include `Cache-Control` and `ETag` headers, and matching `If-None-Match` requests return `304 Not Modified`.
> [!NOTE]
> The acceptance suite verifies these headers for the page, robots, sitemap, favicon, and not-found responses.
## ๐งฐ Development
### โ
Prerequisites
Install:
- [Go](https://go.dev/) (see `go.mod`; check locally with `go version`)
- [Ruby](https://www.ruby-lang.org/en/)
- Bundler for the Ruby test harness
If you are cloning the repo, initialize submodules before relying on Make targets:
```sh
git clone --recurse-submodules https://github.com/alexfalkowski/web.git
```
For an existing checkout where `bin/` may be absent or stale:
```sh
git submodule sync
git submodule update --init
```
Then install dependencies:
```sh
make dep
```
> [!IMPORTANT]
> The root `Makefile` includes shared build fragments from `bin/`, so a missing submodule can prevent `make` from parsing at all. Once `bin/` is present, `make submodule` can refresh it through the normal repo target.
> [!WARNING]
> Some targets require external tools in addition to Go and Ruby. For example, `make dev` uses `air`, Go checks may use `gotestsum`, `golangci-lint`, and `govulncheck`, and security checks may use Trivy.
### ๐งพ Useful Make targets
This repo relies on `make` for a consistent developer experience.
List all available commands:
```sh
make help
```
Common workflows:
```sh
# Install dependencies (Go + Ruby)
make dep
# Run linters
make lint
# Auto-fix lint where possible
make fix-lint
# Format code
make format
# Run the repo-defined Go specs wrapper
make specs
# Run Cucumber acceptance tests
make features
# Run Cucumber benchmark scenarios
make benchmarks
```
> [!TIP]
> `make help` is the best way to discover the current command surface because most project workflows come from the shared `bin/` Make fragments.
### ๐ Running locally
There are two common ways to run the service:
#### ๐ 1) Dev mode
Use the dev target (recommended while iterating):
```sh
make dev
```
This runs the service with `test/.config/server.yml`.
#### ๐งฑ 2) Build and run the binary
Build a local binary:
```sh
make build
```
Then run it:
```sh
./web server -config file:test/.config/server.yml
```
> [!IMPORTANT]
> The current config flag is `-config`; `-c` is the short form. The CLI command is `server`, registered in `internal/cmd`, and starts the HTTP server using the DI module graph.
### ๐ Example: verifying endpoints
Once the server is running, you can verify key endpoints.
If you started the service with `make dev` or with `-config file:test/.config/server.yml`,
the HTTP server listens on `localhost:11000`.
Pages:
```sh
curl -i http://localhost:11000/
curl -i http://localhost:11000/books
curl -i http://localhost:11000/robots.txt
curl -i http://localhost:11000/sitemap.xml
curl -i http://localhost:11000/favicon.ico
```
Partial renders (PUT):
```sh
curl -i -X PUT http://localhost:11000/
curl -i -X PUT http://localhost:11000/books
```
Health:
```sh
curl -i http://localhost:11000/web/healthz
curl -i http://localhost:11000/web/livez
curl -i http://localhost:11000/web/readyz
curl -i http://localhost:11000/web/metrics
```
Not found:
```sh
curl -i http://localhost:11000/not-a-real-page
```
> [!CAUTION]
> Ports, TLS, telemetry, and other server settings come from configuration. Do not treat `test/.config/server.yml` as a production configuration.
## โ๏ธ Configuration
The service config model lives in:
- `internal/config.Config`
It embeds the shared base config from `go-service` and adds a `health` section.
The canonical local example is:
- `test/.config/server.yml`
The local development config in `test/.config/server.yml` includes this
first-use excerpt:
```yaml
health:
duration: 1s
timeout: 1s
transport:
http:
address: tcp://:11000
```
The service-specific `health` section is required. `health.duration` must be a
positive Go duration and controls how often health registrations are evaluated;
`health.timeout` may be zero or greater and controls the online health check
timeout.
> [!NOTE]
> The full local config also sets the environment, UUID generation, tint logging, Prometheus metrics, an OTLP tracer endpoint, HTTP limiter tokens/interval, and HTTP timeout. The wider configuration shape comes from shared `go-service` sections such as environment, telemetry, transport, and version metadata.
## ๐งช Testing
The primary behavioral checks are the Ruby/Cucumber suites:
```sh
make features
make benchmarks
```
These targets run the acceptance harness from `test/`. Nonnative loads
`test/nonnative.yml`, starts `../web server -config file:.config/server.yml` on
`localhost:11000`, and writes Nonnative, server, and Cucumber output under
`test/reports/`. Stop any local service already using `11000` before running the
acceptance suites.
To run a narrower acceptance scope while iterating, pass feature paths relative
to `test/`:
```sh
make features feature=features/site/site.feature
make features feature=features/health/observability.feature
make benchmarks feature=features/site/benchmark.feature
```
The main CircleCI service build also runs:
```sh
make lint
make sec
make analyse
make coverage
make codecov-upload
```
`make codecov-upload` is CI upload behavior, not a read-only local validation
step.
The full CircleCI workflow additionally runs Docker image checks and the
submodule sync/push job on non-`master` branches:
```sh
make platform=amd64 test-docker
make platform=arm64 test-docker
make sync push
```
On `master`, CircleCI also runs versioning, Docker release/manifest, and deploy jobs.
Go support checks are still available:
```sh
make specs
go test ./...
```
> [!NOTE]
> Treat `make features` and `make benchmarks` as the authoritative product behavior checks. The Go tests support build/tooling confidence but are not the main product signal.
### ๐ Ruby acceptance test client
The Ruby test helper client is in:
- `test/lib/web.rb`
- `test/lib/web/v1/http.rb`
It provides a small wrapper around HTTP calls used by the acceptance tests.
## ๐จ Style
Go code generally follows:
-
## ๐ข Changes and releases
Releases are handled through CI and GoReleaser configuration:
- `.circleci/config.yml`
- `.goreleaser.yml`
Generated changelog text is part of the GoReleaser release flow.
> [!CAUTION]
> Release, Docker publishing, deployment, and GitHub PR targets can push to external systems. Use the read-only validation targets unless you intend to publish or update remote state.
## ๐ License
See:
- `LICENSE`