An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          

[![CircleCI](https://circleci.com/gh/alexfalkowski/web.svg?style=svg)](https://circleci.com/gh/alexfalkowski/web)
[![codecov](https://codecov.io/gh/alexfalkowski/web/graph/badge.svg?token=S9SPVVYQAY)](https://codecov.io/gh/alexfalkowski/web)
[![Go Report Card](https://goreportcard.com/badge/github.com/alexfalkowski/web)](https://goreportcard.com/report/github.com/alexfalkowski/web)
[![Go Reference](https://pkg.go.dev/badge/github.com/alexfalkowski/web.svg)](https://pkg.go.dev/github.com/alexfalkowski/web)
[![Stability: Active](https://masterminds.github.io/stability/active.svg)](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`