Server and Routes

Use server.Server to configure the public Go server and register routes.

Create the Server

Create one server during process setup:

runtime := server.New(server.Config{
    Addr:   ":8080",
    Logger: logger,
})

The zero values use these defaults:

Setting Default
Address :8080
Read timeout 15 seconds
Write timeout 30 seconds
Idle timeout 60 seconds
Shutdown timeout 15 seconds
Logger slog.Default()

Run starts the server. It waits for SIGINT, SIGTERM, or context cancellation. It then drains active HTTP connections.

if err := runtime.Run(context.Background()); err != nil {
    return err
}

Register Routes

Wahoo uses http.ServeMux patterns. Go 1.22 method patterns work without an extra router.

runtime.HandleFunc("GET /healthz", health)
runtime.HandleFunc("POST /api/projects", createProject)
runtime.Handle("GET /events", events)

Use HandleFunc for a function. Use Handle for an http.Handler.

Register a page route after you set a renderer:

runtime.SetRenderer(ssr.NewRenderer())
if err := runtime.Page("GET /"); err != nil {
    return err
}

The GET / pattern handles page paths that do not match a more specific route.

Return JSON

Use server.JSON to set the content type and encode a value:

func health(w http.ResponseWriter, _ *http.Request) {
    _ = server.JSON(w, http.StatusOK, map[string]string{
        "status": "ok",
    })
}

Pass 0 as the status to use 200 OK.

Define a stable error format in the application:

{
  "error": {
    "code": "validation_failed",
    "message": "Check the highlighted fields.",
    "request_id": "..."
  }
}

Do not return internal database or provider errors to the browser.

Return HTML

Use server.HTML for a small HTML response:

_ = server.HTML(w, http.StatusOK, "<h1>Ready</h1>")

Use the SSR renderer for React pages. TemplateRenderer is useful for a small non-React page or a test fixture. It does not replace the React worker.

Request Behavior

For each request, Server:

  1. Uses X-Request-ID when the client sends one.
  2. Creates an ID when the header is absent.
  3. Adds X-Request-ID to the response.
  4. Recovers a panic at the HTTP boundary.
  5. Writes a structured completion log entry.

Add application middleware for authentication, authorization, request size limits, rate limits, CORS, and security headers. Wahoo does not add these policies by default.

Read JSON Safely

The generated application includes decodeJSON. It rejects unknown fields:

var input CreateProjectInput
if err := decodeJSON(r, &input); err != nil {
    _ = server.JSON(w, http.StatusBadRequest, errorResponse("invalid JSON"))
    return
}

Before you decode a public request, set an HTTP body limit. Validate every field after decoding.

Test Routes

Use httptest for route tests:

request := httptest.NewRequest(http.MethodGet, "/healthz", nil)
response := httptest.NewRecorder()
runtime.ServeHTTP(response, request)

if response.Code != http.StatusOK {
    t.Fatalf("status = %d", response.Code)
}

Test status codes, response JSON, cookies, authorization, and error cases.