Fix review issues: front matter, headings, consistency, typos
check / check (push) Successful in 9s

- Move title and last_modified to YAML front matter (all policy docs)
- Make all document sections H1, subsections H2
- Update version rule to reference front matter format
- Fix "our" → "your" typo in Go styleguide
- Fix Python styleguide numbering (2. → 1.)
- Fix README: "flat collection" → accurate description, remove stale TODO
- Remove Makefile items from code styleguides (repo stuff, not code),
  add note linking to Repository Policies
- Change zerolog → slog in Go styleguide
- Fix JS styleguide npm reference: both work, but use make targets
- Drop .json from healthcheck path, add JSON content-type requirement
- Add Author/License to Go HTTP Server Conventions
- Convert hyperlinks to backtick URLs in checklists for consistency
- Add version/front matter to both checklists
This commit is contained in:
2026-02-22 17:15:06 +01:00
parent 3768b8ca02
commit e97b48eea4
9 changed files with 184 additions and 173 deletions
+76 -67
View File
@@ -1,12 +1,13 @@
Version: 2026-02-22
# Go HTTP Server Conventions
---
title: Go HTTP Server Conventions
last_modified: 2026-02-22
---
This document defines the architectural patterns, design decisions, and
conventions for building Go HTTP servers. All new projects must follow these
standards.
## Table of Contents
# Table of Contents
1. [Required Libraries](#1-required-libraries)
2. [Project Structure](#2-project-structure)
@@ -25,7 +26,7 @@ standards.
---
## 1. Required Libraries
# 1. Required Libraries
These libraries are **mandatory** for all new projects:
@@ -44,7 +45,7 @@ These libraries are **mandatory** for all new projects:
---
## 2. Project Structure
# 2. Project Structure
```
project-root/
@@ -86,7 +87,7 @@ project-root/
└── Dockerfile
```
### Key Principles
## Key Principles
- **`cmd/{appname}/`**: Only the entry point. Minimal logic, just bootstrapping.
- **`internal/`**: All application packages. Not importable by external
@@ -96,9 +97,9 @@ project-root/
---
## 3. Dependency Injection (Uber fx)
# 3. Dependency Injection (Uber fx)
### Entry Point Pattern
## Entry Point Pattern
```go
// cmd/httpd/main.go
@@ -143,7 +144,7 @@ func main() {
}
```
### Params Struct Pattern
## Params Struct Pattern
Every component that receives dependencies uses a params struct with `fx.In`:
@@ -163,7 +164,7 @@ type Handlers struct {
}
```
### Factory Function Pattern
## Factory Function Pattern
All components expose a `New` function with this signature:
@@ -187,7 +188,7 @@ func New(lc fx.Lifecycle, params SomeParams) (*Something, error) {
}
```
### Dependency Order
## Dependency Order
Providers are resolved automatically by fx, but conceptually follow this order:
@@ -203,9 +204,9 @@ Providers are resolved automatically by fx, but conceptually follow this order:
---
## 4. Server Architecture
# 4. Server Architecture
### Server Struct
## Server Struct
The Server struct is the central orchestrator:
@@ -236,7 +237,7 @@ type Server struct {
}
```
### Server Factory
## Server Factory
```go
func New(lc fx.Lifecycle, params ServerParams) (*Server, error) {
@@ -261,7 +262,7 @@ func New(lc fx.Lifecycle, params ServerParams) (*Server, error) {
}
```
### HTTP Server Setup
## HTTP Server Setup
```go
// internal/server/http.go
@@ -291,7 +292,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
```
### Signal Handling and Graceful Shutdown
## Signal Handling and Graceful Shutdown
```go
func (s *Server) serve() int {
@@ -335,9 +336,9 @@ func (s *Server) cleanShutdown() {
---
## 5. Routing (go-chi)
# 5. Routing (go-chi)
### Route Setup Pattern
## Route Setup Pattern
```go
// internal/server/routes.go
@@ -380,7 +381,7 @@ func (s *Server) SetupRoutes() {
s.router.Get("/login", auth(s.h.HandleLoginGET()).ServeHTTP)
// Health check (standard path)
s.router.Get("/.well-known/healthcheck.json", s.h.HandleHealthCheck())
s.router.Get("/.well-known/healthcheck", s.h.HandleHealthCheck())
// Protected route groups
if viper.GetString("METRICS_USERNAME") != "" {
@@ -392,7 +393,7 @@ func (s *Server) SetupRoutes() {
}
```
### Middleware Ordering (Critical)
## Middleware Ordering (Critical)
1. `middleware.Recoverer` - Panic recovery (must be first)
2. `middleware.RequestID` - Generate request IDs
@@ -402,7 +403,7 @@ func (s *Server) SetupRoutes() {
6. `middleware.Timeout(60s)` - Request timeout
7. `sentryhttp.Handler` - Sentry error reporting (if enabled)
### API Versioning
## API Versioning
Use route groups for API versioning:
@@ -412,7 +413,7 @@ s.router.Route("/api/v1", func(r chi.Router) {
})
```
### Static File Serving
## Static File Serving
Static files are served at `/s/` prefix:
@@ -422,9 +423,9 @@ s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Stati
---
## 6. Handler Conventions
# 6. Handler Conventions
### Handler Base Struct
## Handler Base Struct
```go
// internal/handlers/handlers.go
@@ -457,7 +458,7 @@ func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
}
```
### Closure-Based Handler Pattern
## Closure-Based Handler Pattern
All handlers return `http.HandlerFunc` using the closure pattern. This allows
initialization logic to run once when the handler is created:
@@ -479,7 +480,7 @@ func (s *Handlers) HandleIndex() http.HandlerFunc {
}
```
### JSON Handler Pattern
## JSON Handler Pattern
```go
// internal/handlers/now.go
@@ -494,7 +495,7 @@ func (s *Handlers) HandleNow() http.HandlerFunc {
}
```
### Response Helpers
## Response Helpers
```go
// internal/handlers/handlers.go
@@ -514,7 +515,7 @@ func (s *Handlers) decodeJSON(w http.ResponseWriter, r *http.Request, v interfac
}
```
### Handler Naming Convention
## Handler Naming Convention
- `HandleIndex()` - Main page
- `HandleLoginGET()` / `HandleLoginPOST()` - Form handlers with HTTP method
@@ -524,9 +525,9 @@ func (s *Handlers) decodeJSON(w http.ResponseWriter, r *http.Request, v interfac
---
## 7. Middleware Conventions
# 7. Middleware Conventions
### Middleware Struct
## Middleware Struct
```go
// internal/middleware/middleware.go
@@ -550,7 +551,7 @@ func New(lc fx.Lifecycle, params MiddlewareParams) (*Middleware, error) {
}
```
### Middleware Signature
## Middleware Signature
All custom middleware methods return `func(http.Handler) http.Handler`:
@@ -569,7 +570,7 @@ func (s *Middleware) Auth() func(http.Handler) http.Handler {
}
```
### Logging Middleware with Status Capture
## Logging Middleware with Status Capture
```go
type loggingResponseWriter struct {
@@ -613,7 +614,7 @@ func (s *Middleware) Logging() func(http.Handler) http.Handler {
}
```
### CORS Middleware
## CORS Middleware
```go
func (s *Middleware) CORS() func(http.Handler) http.Handler {
@@ -628,7 +629,7 @@ func (s *Middleware) CORS() func(http.Handler) http.Handler {
}
```
### Metrics Middleware
## Metrics Middleware
```go
func (s *Middleware) Metrics() func(http.Handler) http.Handler {
@@ -654,9 +655,9 @@ func (s *Middleware) MetricsAuth() func(http.Handler) http.Handler {
---
## 8. Configuration (Viper)
# 8. Configuration (Viper)
### Config Struct
## Config Struct
```go
// internal/config/config.go
@@ -679,7 +680,7 @@ type Config struct {
}
```
### Configuration Loading
## Configuration Loading
```go
func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
@@ -737,7 +738,7 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
}
```
### Configuration Precedence
## Configuration Precedence
1. **Environment variables** (highest priority via `AutomaticEnv()`)
2. **`.env` file** (loaded via `godotenv/autoload` import)
@@ -745,7 +746,7 @@ func New(lc fx.Lifecycle, params ConfigParams) (*Config, error) {
`~/.config/{appname}/{appname}.yaml`
4. **Defaults** (lowest priority)
### Environment Loading
## Environment Loading
Import godotenv with autoload to automatically load `.env` files:
@@ -757,9 +758,9 @@ import (
---
## 9. Logging (slog)
# 9. Logging (slog)
### Logger Struct
## Logger Struct
```go
// internal/logger/logger.go
@@ -775,7 +776,7 @@ type Logger struct {
}
```
### Logger Setup with TTY Detection
## Logger Setup with TTY Detection
```go
func New(lc fx.Lifecycle, params LoggerParams) (*Logger, error) {
@@ -809,7 +810,7 @@ func New(lc fx.Lifecycle, params LoggerParams) (*Logger, error) {
}
```
### Logger Methods
## Logger Methods
```go
func (l *Logger) EnableDebugLogging() {
@@ -830,7 +831,7 @@ func (l *Logger) Identify() {
}
```
### Logging Patterns
## Logging Patterns
```go
// Info with fields
@@ -866,9 +867,9 @@ s.log.Info("request",
---
## 10. Database Wrapper
# 10. Database Wrapper
### Database Struct
## Database Struct
```go
// internal/database/database.go
@@ -885,7 +886,7 @@ type Database struct {
}
```
### Database Factory with Lifecycle
## Database Factory with Lifecycle
```go
func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) {
@@ -912,7 +913,7 @@ func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) {
}
```
### Usage Pattern
## Usage Pattern
The Database struct is injected into handlers and other services:
@@ -933,9 +934,9 @@ func (s *Handlers) HandleSomething() http.HandlerFunc {
---
## 11. Globals Package
# 11. Globals Package
### Package Variables and Struct
## Package Variables and Struct
```go
// internal/globals/globals.go
@@ -967,7 +968,7 @@ func New(lc fx.Lifecycle) (*Globals, error) {
}
```
### Setting Globals in Main
## Setting Globals in Main
```go
// cmd/httpd/main.go
@@ -985,7 +986,7 @@ func main() {
}
```
### Build-Time Variable Injection
## Build-Time Variable Injection
Use ldflags to inject version information at build time:
@@ -999,9 +1000,9 @@ build:
---
## 12. Static Assets & Templates
# 12. Static Assets & Templates
### Static File Embedding
## Static File Embedding
```go
// static/static.go
@@ -1026,7 +1027,7 @@ static/
└── jquery-3.5.1.slim.min.js
```
### Template Embedding and Lazy Parsing
## Template Embedding and Lazy Parsing
```go
// templates/templates.go
@@ -1049,7 +1050,7 @@ func GetParsed() *template.Template {
}
```
### Template Composition
## Template Composition
Templates use Go's template composition:
@@ -1064,7 +1065,7 @@ Templates use Go's template composition:
{{ template "pagefooter.html" . }} {{ template "htmlfooter.html" . }}
```
### Static Asset References
## Static Asset References
Reference static files with `/s/` prefix:
@@ -1076,9 +1077,9 @@ Reference static files with `/s/` prefix:
---
## 13. Health Check
# 13. Health Check
### Health Check Service
## Health Check Service
```go
// internal/healthcheck/healthcheck.go
@@ -1114,7 +1115,7 @@ func New(lc fx.Lifecycle, params HealthcheckParams) (*Healthcheck, error) {
}
```
### Health Check Response
## Health Check Response
```go
type HealthcheckResponse struct {
@@ -1144,19 +1145,19 @@ func (s *Healthcheck) Healthcheck() *HealthcheckResponse {
}
```
### Standard Endpoint
## Standard Endpoint
Health check is served at the standard `.well-known` path:
```go
s.router.Get("/.well-known/healthcheck.json", s.h.HandleHealthCheck())
s.router.Get("/.well-known/healthcheck", s.h.HandleHealthCheck())
```
---
## 14. External Integrations
# 14. External Integrations
### Sentry Error Reporting
## Sentry Error Reporting
Sentry is conditionally enabled based on `SENTRY_DSN` environment variable:
@@ -1201,7 +1202,7 @@ if s.sentryEnabled {
}
```
### Prometheus Metrics
## Prometheus Metrics
Metrics are conditionally enabled and protected by basic auth:
@@ -1220,7 +1221,7 @@ if viper.GetString("METRICS_USERNAME") != "" {
}
```
### Environment Variables Summary
## Environment Variables Summary
| Variable | Description | Default |
| ------------------ | -------------------------------- | ------- |
@@ -1231,3 +1232,11 @@ if viper.GetString("METRICS_USERNAME") != "" {
| `MAINTENANCE_MODE` | Enable maintenance mode | false |
| `METRICS_USERNAME` | Basic auth username for /metrics | "" |
| `METRICS_PASSWORD` | Basic auth password for /metrics | "" |
# Author
[@sneak](https://sneak.berlin) <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)>
# License
MIT. See [LICENSE](../LICENSE).