2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00
2026-02-14 14:02:21 +00:00

Tower

Tower is a minimal, composable HTTP web framework for Go built on top of net/http and httprouter.
It provides a small, explicit API for routing, middleware composition, grouping, and structured request/response handling via a custom context.

The framework favors:

  • Small surface area
  • Explicit middleware chaining
  • Typed handler signatures
  • Zero magic abstractions
  • Compatibility with the standard library

Installation

go get git.trcreatives.at/TR_Creatives/go-tower

Quick Start

package main

import (
	"log"

	"git.trcreatives.at/TR_Creatives/go-tower"
)

func main() {
	app := tower.New()

	app.GET("/hello/:name", func(c *tower.Context) error {
		name := c.Param("name")
		return c.WriteJSON(200, tower.Map{
			"message": "Hello " + name,
		})
	})

	log.Fatal(app.Start(":8080"))
}

Core Concepts

Handler

All route handlers use a unified signature:

type Handler func(c *Context) error

Returning an error forwards it to the configured error handler.

app.GET("/ping", func(c *tower.Context) error {
	return c.WriteString(200, "pong")
})

Middleware

Middleware wraps handlers and composes into a chain:

type Middleware func(h Handler) Handler

Example:

func Logger() tower.Middleware {
	return func(next tower.Handler) tower.Handler {
		return func(c *tower.Context) error {
			err := next(c)
			log.Printf("%s %s -> %d",
				c.Request().Method,
				c.Request().URL.Path,
				c.Status(),
			)
			return err
		}
	}
}

Register globally:

app.Use(Logger())

Or per route:

app.GET("/secure", handler, AuthMiddleware())

Middleware execution order (outer → inner):

  1. Global middleware
  2. Group middleware
  3. Route middleware
  4. Handler

Routing

Tower uses httprouter, so parameters are declared with :name.

app.GET("/users/:id", func(c *tower.Context) error {
	id := c.Param("id")
	return c.WriteString(200, "User: "+id)
})

Available helpers:

app.GET(path, handler, mws...)
app.POST(path, handler, mws...)
app.PUT(path, handler, mws...)
app.PATCH(path, handler, mws...)
app.DELETE(path, handler, mws...)

Or manually:

app.Handle("GET", "/custom", handler)

Groups

Groups allow route prefixing and shared middleware.

api := app.Group("/api")

api.GET("/health", func(c *tower.Context) error {
	return c.WriteString(200, "ok")
})

Nested groups:

v1 := api.Group("/v1")
v1.GET("/users", listUsers)

Group-level middleware:

auth := app.Group("/auth", AuthMiddleware())
auth.GET("/profile", profileHandler)

Static Files

Serve a directory under a URL prefix:

app.Static("/assets", "./public")

Group variant:

api.Static("/docs", "./docs")

Context API

Context wraps http.Request and http.ResponseWriter and provides helpers.

Request Access

c.Request()         // *http.Request
c.Param("id")       // path parameter
c.Query("q")        // query string value
c.Header("X-Token") // request header
c.Cookie("session") // cookie
c.ClientIP()        // best-effort client IP

Response Helpers

c.SetStatus(201)
c.SetHeader("X-App", "tower")

c.WriteString(200, "ok")
c.WriteJSON(200, data)
c.WriteXML(200, data)
c.WriteBytes(200, []byte("raw"), "application/octet-stream")
c.Redirect(302, "/login")

Binding Request Bodies

var payload CreateUserRequest
if err := c.BindJSON(&payload); err != nil {
	return err
}

Also supported:

c.BindXML(&payload)

Context Values

type userKey tower.ContextKey // define your own alias if desired

c.SetValue("userID", "123")
id := c.Value("userID")

(Values are stored in the underlying request.Context().)


Error Handling

Handlers return errors which are passed to the global error handler.

Set a custom handler:

app.SetErrorHandler(func(c *tower.Context, err error) error {
	return c.WriteJSON(500, tower.Map{
		"error": err.Error(),
	})
})

If the error handler itself returns an error, it is logged.


Server Lifecycle

Start HTTP

if err := app.Start(":8080"); err != nil {
	log.Fatal(err)
}

Start HTTPS

log.Fatal(app.StartTLS(":8443", "cert.pem", "key.pem"))

Graceful Shutdown

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

_ = app.Shutdown(ctx)

Execution Flow

For each incoming request:

  1. Route matched via httprouter
  2. Context constructed
  3. Route middleware applied
  4. Global middleware applied
  5. Handler executed
  6. Returned error passed to ErrorHandler

Map Helper

Tower provides a small convenience type:

type Map map[string]any

Useful for JSON responses:

return c.WriteJSON(200, tower.Map{
	"status": "ok",
})

Design Goals

  • Minimal abstraction over net/http
  • Deterministic middleware composition
  • No reflection or hidden state
  • Explicit error propagation
  • Fully standard-library compatible
Description
No description provided
Readme 40 KiB
Languages
Go 100%