Help us shape the future of Go! Take the 2026 Go Developer Survey

web

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 15 Imported by: 0

README

go-web

go-web is a lightweight, general-purpose web server wrapper for Go, specifically designed for applications serving HTML pages using html/template. It provides a structured way to organize routes via controllers, handles template management, and ensures a robust server lifecycle with graceful shutdown.

Key Features

  • Global Middleware Support: Easily apply middleware (logging, CORS, auth, etc.) to all requests using srv.Use().
  • Custom Not Found Handler: Set a custom handler for 404 responses using srv.NotFoundHandler().
  • Controller-Based Routing: Organize related routes into controllers for better maintainability.
  • Automatic Template Loading: Recursively loads all .html files from a specified directory.
  • Safe Rendering: Templates are rendered to a buffer before being sent to the client, preventing "half-sent" pages with a 200 OK status on error.
  • Graceful Shutdown: Built-in support for shutting down the server cleanly when a signal is received.
  • Flexible Configuration: Supports YAML configuration and environment variables via the go-cfg package.
  • Static File Serving: Easy setup for serving CSS, JS, and images.
  • Modern Go Standards: Leverages net/http (Go 1.22+ routing) and log/slog.

Installation

go get github.com/meesooqa/go-web

Quick Start

1. Define a Controller

Create a struct that implements the web.Controller interface.

type HomeController struct {
    tmpl *web.Templates
}

func (c *HomeController) Routes() []web.Route {
    return []web.Route{
        {Pattern: "GET /{$}", Handler: c.Index},
        {Pattern: "GET /about", Handler: c.About},
    }
}

func (c *HomeController) Index(w http.ResponseWriter, r *http.Request) {
    c.tmpl.Render(w, http.StatusOK, "index.html", map[string]any{"Title": "Home Page"})
}

func (c *HomeController) About(w http.ResponseWriter, r *http.Request) {
    c.tmpl.Render(w, http.StatusOK, "about.html", nil)
}
2. Initialize and Run the Server
package main

import (
    "context"
    "log/slog"
    "os"
    "os/signal"
    "syscall"

    "github.com/meesooqa/go-middleware"
    "github.com/meesooqa/go-web"
)

func main() {
    cfg := web.Config{
        Host:         "0.0.0.0",
        Port:         8080,
        TemplatesDir: "./templates",
        StaticDir:    "./static",
    }

    srv, err := web.New(cfg, slog.Default())
    if err != nil {
        panic(err)
    }

    // Add global middleware
    srv.Use(
        middleware.Logging(slog.Default()),
        middleware.CORS(middleware.CORSConfig{
            AllowedOrigins: []string{"*"},
        }),
    )

    // Register controllers
    homeCtrl := &HomeController{tmpl: srv.Templates()}
    srv.Register(homeCtrl)

    // Set custom 404 handler
    srv.NotFoundHandler(func(w http.ResponseWriter, r *http.Request) {
        homeCtrl.tmpl.Render(w, http.StatusNotFound, \"404.html\", map[string]any{
            \"Path\": r.URL.Path,
        })
    })

    // Handle graceful shutdown
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    if err := srv.Run(ctx); err != nil {
        slog.Error("server error", "err", err)
    }
}

Configuration

The server can be configured using the web.Config struct. You can pass this struct to web.New() or use web.Load(path) to load configuration from a YAML file.

Field Description Default
Host Address to listen on 0.0.0.0
Port TCP port 8080
ReadTimeout Max duration for reading request 15s
ReadHeaderTimeout Max duration for reading headers 5s
WriteTimeout Max duration for writing response 15s
IdleTimeout Keep-alive connection timeout 60s
ShutdownTimeout Graceful shutdown timeout 10s
MaxHeaderBytes Max total size of request headers 1MB
TemplatesDir Directory containing HTML templates -
StaticDir Directory for static files -
StaticURLPath URL prefix for static files /static/
CORS CORS configuration (middleware.CORSConfig) -

Examples

Check the examples/ directory for more detailed usage scenarios.

License

This project is licensed under the LICENSE file.

Documentation

Overview

Package web provides a general-purpose web server for serving HTML pages (html/template)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Host is the address the server listens on:
	// "0.0.0.0" for all interfaces,
	// "127.0.0.1" for local connections only.
	Host string `yaml:"host" default:"0.0.0.0"`

	// Port is the server's TCP port.
	Port int `yaml:"port" default:"8080" env:"SERVER_PORT"`

	// ReadTimeout is the maximum duration for reading the entire request,
	// including the body, from the time the connection is established.
	ReadTimeout time.Duration `yaml:"read_timeout" default:"15s"`

	// ReadHeaderTimeout is the maximum duration for reading request
	// headers. Provides separate protection against slow clients (Slowloris).
	ReadHeaderTimeout time.Duration `yaml:"read_header_timeout" default:"5s"`

	// WriteTimeout is the maximum duration for writing the response.
	WriteTimeout time.Duration `yaml:"write_timeout" default:"15s"`

	// IdleTimeout is how long to keep a keep-alive connection open
	// between requests from the same client.
	IdleTimeout time.Duration `yaml:"idle_timeout" default:"60s"`

	// ShutdownTimeout is how long to wait for active requests to complete
	// during server shutdown (graceful shutdown) before forcibly
	// terminating them.
	ShutdownTimeout time.Duration `yaml:"shutdown_timeout" default:"10s"`

	// MaxHeaderBytes is the maximum total size of request headers
	// in bytes. Protects against excessively large headers.
	MaxHeaderBytes int `yaml:"max_header_bytes" default:"1048576"` // 1 MB

	// TemplatesDir is the directory with HTML templates (html/template),
	// scanned recursively. If empty, templates are not loaded
	// automatically; pass them via WithTemplates if needed.
	TemplatesDir string `yaml:"templates_dir"`

	// StaticDir is the directory with static files (CSS, JS, images).
	// If empty, static file serving is not enabled.
	StaticDir string `yaml:"static_dir"`

	// StaticURLPath is the URL prefix under which static files are served.
	StaticURLPath string `yaml:"static_url_path" default:"/static/"`

	// CORS configures the CORS headers for the CORS middleware
	CORS middleware.CORSConfig `yaml:"cors"`
}

Config describes the web server configuration

type Controller

type Controller interface {
	Routes() []Route
}

Controller represents a unit of web pages: a group of related routes (e.g., all pages in the "user profile" section). Registered via Server.Register.

A typical implementation stores dependencies (templates, data access) as struct fields and returns its methods as handlers:

type HomeController struct {
	tmpl *web.Templates
}

func (c *HomeController) Routes() []web.Route {
	return []web.Route{
		{Pattern: "GET /{$}", Handler: c.Index},
		{Pattern: "GET /about", Handler: c.About},
	}
}

type Option

type Option func(*Server)

Option configures a Server during creation via New

func WithTemplates

func WithTemplates(t *Templates) Option

WithTemplates passes pre-loaded templates instead of automatically loading them from Config.TemplatesDir

type Route

type Route struct {
	Pattern string
	Handler http.HandlerFunc
}

Route describes a single controller route. Pattern is a standard net/http.ServeMux pattern (Go 1.22+), e.g., "GET /{$}", "POST /login", "GET /users/{id}". If no method is specified at the beginning, the route responds to any HTTP method.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is a web server: a router, a set of global middleware, (optionally) loaded templates, and a wrapper around http.Server with graceful shutdown support

func Load

func Load(path string, logger *slog.Logger, envFiles ...string) (*Server, error)

Load reads a YAML file (and optional .env files) via the cfg package and immediately builds a Server. Use this function if the application does not have a single unified config.yml and the server settings are the only thing that needs to be loaded

func New

func New(c Config, logger *slog.Logger, opts ...Option) (*Server, error)

New creates a Server from the provided Config. Use this function if Config is part of the application's overall configuration, already loaded separately (via cfg.Load[AppConfig] with a Server web.Config field). If logger is nil, slog.Default() is used

func (*Server) Addr

func (s *Server) Addr() string

Addr returns the address the server is actually listening on, after Run has started accepting connections (useful when Config.Port is 0 — the OS selects a free port). Until that point, it returns an empty string

func (*Server) Handle

func (s *Server) Handle(pattern string, h http.Handler)

Handle registers an http.Handler directly

func (*Server) HandleFunc

func (s *Server) HandleFunc(pattern string, h http.HandlerFunc)

HandleFunc registers a single handler directly, bypassing controllers — for cases where a full Controller is overkill

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the final http.Handler — the router wrapped in the global middleware chain. It does not require starting a network listener, making it convenient for testing (httptest.NewServer(srv.Handler())) or if you want to manage the net.Listener and TLS yourself instead of calling Run

func (*Server) NotFoundHandler added in v0.3.0

func (s *Server) NotFoundHandler(h http.HandlerFunc)

NotFoundHandler sets a custom handler for when no route matches. If h is nil, the default http.NotFound handler is restored.

func (*Server) Register

func (s *Server) Register(controllers ...Controller)

Register registers the routes of one or more controllers

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run starts the server and blocks until ctx is canceled, after which it performs a graceful shutdown with the Config.ShutdownTimeout. Typical call from main:

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := srv.Run(ctx); err != nil {
	log.Fatal(err)
}

Run can only be called once per Server instance

func (*Server) Static

func (s *Server) Static(urlPath, dir string)

Static enables serving static files from the dir directory under the urlPath URL prefix. It is called automatically from New if Config.StaticDir is set — use it directly only if you need to serve more than one static directory

func (*Server) Templates

func (s *Server) Templates() *Templates

Templates returns the loaded templates (or nil if they were not loaded — TemplatesDir is empty and WithTemplates was not used)

func (*Server) Use added in v0.2.0

func (s *Server) Use(mw ...middleware.Middleware)

Use adds global middleware applied to all requests. Middleware are executed in the order they are added (the first added is the outermost, see middleware.Chain). Call before Run

type Templates

type Templates struct {
	// contains filtered or unexported fields
}

Templates is a wrapper around a set of HTML templates loaded from a directory. It uses the html/template package (instead of text/template) — values are automatically escaped when inserted into HTML, protecting against XSS vulnerabilities when rendering user data.

func LoadTemplates

func LoadTemplates(dir string, opts ...TemplatesOption) (*Templates, error)

LoadTemplates recursively scans dir and parses all found template files using html/template. The name of each template within the set is its filename (e.g., "index.html"), so layouts and partials can be organized into subdirectories arbitrarily — the only requirement is that filenames must be unique within dir.

func (*Templates) Render

func (t *Templates) Render(w http.ResponseWriter, status int, name string, data any) error

Render renders the template name with the given data and writes the result to w with the specified HTTP status. The template is first rendered into a temporary buffer — if rendering fails (e.g., due to a missing field in data), the client will not receive a half-sent page mixed with a 200 status code: headers and body are written to the response only when rendering is guaranteed to succeed.

type TemplatesOption

type TemplatesOption func(*templatesOptions)

TemplatesOption configures LoadTemplates.

func WithExtensions

func WithExtensions(exts ...string) TemplatesOption

WithExtensions specifies the file extensions that are considered templates (by default, only ".html").

func WithFuncs

func WithFuncs(fm template.FuncMap) TemplatesOption

WithFuncs adds custom functions available within templates (similar to template.FuncMap).

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL