sim

package module
v0.1.0 Latest Latest
Warning

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

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

README

Sim Web Framework

Sim is a minimal HTTP web framework for Go, built on top of net/http and http.ServeMux — no third-party dependencies.

Features

  • Zero dependencies: only the Go standard library
  • Method-based routing: Get, Post, Put, Delete, Patch, Options, Head, Connect, Trace, and Any
  • Routing follows the net/http.ServeMux patterns
  • Standard net/http handlers and wrappers
  • Wrapper composition with Chain and ChainFunc
  • Route groups
  • Graceful shutdown with Run

Installation

Requires Go 1.26+.

go get github.com/qm012/sim

Example

A complete runnable REST API:

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/qm012/sim"
)

func main() {
	app := sim.NewApp()

	// Use registers wrappers that run on every handler below.
	app.Use(logging)

	app.Get("/", func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("welcome"))
	})
	app.Any("/ping", func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("pong"))
	})

	// Group routes under a common prefix.
	app.Group("/api", func(r sim.Router) {
		r.Get("/users", listUsers)
		r.Get("/users/{id}", getUser)
		r.Post("/users", createUser)
		r.Put("/users/{id}", updateUser)
		r.Delete("/users/{id}", deleteUser)
	})

	// Compose wrappers with Chain / ChainFunc.
	app.Get("/admin", sim.ChainFunc(auth)(adminPanel))

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

func logging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s", r.Method, r.URL.Path)
		next.ServeHTTP(w, r)
	})
}

func auth(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") == "" {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func listUsers(w http.ResponseWriter, r *http.Request) {
	_, _ = fmt.Fprintln(w, "list users")
}

func getUser(w http.ResponseWriter, r *http.Request) {
	_, _ = fmt.Fprintf(w, "user %s", r.PathValue("id"))
}

func createUser(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusCreated)
	_, _ = fmt.Fprintln(w, "user created")
}

func updateUser(w http.ResponseWriter, r *http.Request) {
	_, _ = fmt.Fprintf(w, "user %s updated", r.PathValue("id"))
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNoContent)
}

func adminPanel(w http.ResponseWriter, r *http.Request) {
	_, _ = fmt.Fprintln(w, "admin")
}

Save it as main.go and run it:

go run main.go

Open http://localhost:8080/ to see "welcome", and http://localhost:8080/api/users for the user list.

Contributing

See CONTRIBUTING.md for how to report bugs, suggest features, improve docs, write tests, and submit changes.

Acknowledgements

Sim's design was inspired by:

License

MIT, see LICENSE.

Documentation

Overview

Package sim provides a small, idiomatic HTTP router built on top of net/http.ServeMux, extending it with method-based routing helpers such as App.Get, App.Post, and App.Any.

Example:

package main

import (
	"context"
	"log/slog"
	"net/http"

	"github.com/qm012/sim"
)

func main() {
	app := sim.NewApp()

	app.Get("/", func(w http.ResponseWriter, _ *http.Request) {
		w.Write([]byte("root."))
	})
	app.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("user " + r.PathValue("id")))
	})
	app.Group("/api", func(r sim.Router) {
		r.Post("/users", func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusCreated)
		})
	})

	if err := app.Run(context.Background(), ":3333"); err != nil {
		slog.Error("server failed", "err", err)
	}
}

Routes are registered on an App, which implements http.Handler and can be passed directly to http.ListenAndServe or served with App.Run, which shuts the server down gracefully when its context is canceled.

Patterns

Pattern matching uses the same syntax and precedence rules as http.ServeMux since Go 1.22. A pattern may carry an optional method and host prefix, and a path may contain wildcard segments such as {name} and {name...}. Wildcard values are read from the request with http.Request.PathValue. For example:

  • "GET /users/{id}" matches only GET requests, capturing the id.
  • "/static/" matches every method and any path under "/static/".
  • "/files/{path...}" matches the remainder of the URL, including slashes.

The method helpers register the same pattern for a single method: App.Get registers "GET /path", App.Post registers "POST /path", and App.Any registers "/path" for every method. The pattern given to a method helper must be a plain path; method prefixes belong to the helper itself.

See the http.ServeMux documentation for the complete pattern syntax, precedence rules, and trailing-slash redirection behavior.

Wrappers registered with App.Use are applied to every handler registered after the call, with the first wrapper outermost. Chain composes wrappers into one; ChainFunc is its counterpart over http.HandlerFunc, the type accepted by the method helpers such as App.Get.

See the documentation of App for the full routing API.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Chain

func Chain(ss ...func(http.Handler) http.Handler) func(http.Handler) http.Handler

Chain returns a function that composes the given wrappers into a single wrapper. Applying the returned function to a handler h returns a new handler that runs each wrapper in order: ss[0] is outermost, receives the request first, and its response is what the caller ultimately sees.

Chain(Logging, Auth)(h) is equivalent to Logging(Auth(h)). With no wrappers, Chain returns a function that leaves its argument unchanged.

func ChainFunc

func ChainFunc(ss ...func(http.Handler) http.Handler) func(http.HandlerFunc) http.HandlerFunc

ChainFunc returns a function that composes the given wrappers into a single wrapper over http.HandlerFunc handlers, the counterpart of Chain for func-typed registration methods such as App.Get and App.Put.

The wrappers are the same func(http.Handler) http.Handler type as Chain's, so wrappers written for Chain work unchanged.

With no wrappers, ChainFunc returns a function that leaves its argument unchanged.

Types

type App

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

App is an HTTP router built on top of http.ServeMux, extending it with method-based routing helpers such as App.Get, App.Post and App.Any. Requests are matched against registered patterns using the same syntax and precedence rules as http.ServeMux. App implements http.Handler; create one with NewApp.

func NewApp

func NewApp() *App

NewApp returns a new App value.

func (*App) Any

func (a *App) Any(path string, handlerFunc http.HandlerFunc)

Any registers handlerFunc for the given path, matching all HTTP methods.

func (*App) Connect

func (a *App) Connect(path string, handlerFunc http.HandlerFunc)

Connect registers handlerFunc for CONNECT requests to the given path.

func (*App) Delete

func (a *App) Delete(path string, handlerFunc http.HandlerFunc)

Delete registers handlerFunc for DELETE requests to the given path.

func (*App) Get

func (a *App) Get(path string, handlerFunc http.HandlerFunc)

Get registers handlerFunc for GET requests to the given path.

func (*App) Group

func (a *App) Group(relativePath string, fn func(r Router))

Group creates a new router group with the given relative path and invokes fn with it. Routes registered by fn are resolved relative to the group's path (see Router.Group). If fn is nil, Group does nothing.

func (*App) Handle

func (a *App) Handle(pattern string, handler http.Handler)

Handle registers the handler for the given pattern, with the same behavior as http.ServeMux.Handle and http.Handle.

func (*App) HandleFunc

func (a *App) HandleFunc(pattern string, handlerFunc http.HandlerFunc)

HandleFunc registers the handler function for the given pattern, with the same behavior as http.ServeMux.HandleFunc and http.HandleFunc.

func (*App) Handler

func (a *App) Handler(r *http.Request) (http.Handler, string)

Handler returns the handler and the matching pattern for the given request.

func (*App) Head

func (a *App) Head(path string, handlerFunc http.HandlerFunc)

Head registers handlerFunc for HEAD requests to the given path.

func (*App) Options

func (a *App) Options(path string, handlerFunc http.HandlerFunc)

Options registers handlerFunc for OPTIONS requests to the given path.

func (*App) Patch

func (a *App) Patch(path string, handlerFunc http.HandlerFunc)

Patch registers handlerFunc for PATCH requests to the given path.

func (*App) Post

func (a *App) Post(path string, handlerFunc http.HandlerFunc)

Post registers handlerFunc for POST requests to the given path.

func (*App) Put

func (a *App) Put(path string, handlerFunc http.HandlerFunc)

Put registers handlerFunc for PUT requests to the given path.

func (*App) Run

func (a *App) Run(ctx context.Context, addr string) error

Run listens on the given TCP address and serves HTTP requests until ctx is canceled or the server fails. If ctx is canceled, Run shuts the server down gracefully and returns nil.

func (*App) ServeHTTP

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*App) Trace

func (a *App) Trace(path string, handlerFunc http.HandlerFunc)

Trace registers handlerFunc for TRACE requests to the given path.

func (*App) Use

func (a *App) Use(ss ...func(http.Handler) http.Handler)

Use registers the given wrappers and applies them to every handler registered after this call. Wrappers run in registration order: the first is outermost and receives the request first, the same composition as Chain.

type Router

type Router interface {
	// Use registers the given wrappers and applies them to every handler
	// registered after this call. Wrappers run in registration order:
	// the first is outermost and receives the request first, the same
	// composition as [Chain].
	Use(ss ...func(http.Handler) http.Handler)

	// Handle registers the handler for the given pattern, with the same
	// behavior as [http.ServeMux.Handle] and [http.Handle].
	Handle(pattern string, handler http.Handler)
	// HandleFunc registers the handler function for the given pattern,
	// with the same behavior as [http.ServeMux.HandleFunc] and [http.HandleFunc].
	HandleFunc(pattern string, handler http.HandlerFunc)

	// Any Get Post Delete Patch Put Options Head Connect and Trace
	// register handlerFunc on the given pattern for their respective HTTP
	// methods; Any matches all methods.
	Any(path string, handlerFunc http.HandlerFunc)
	Get(path string, handlerFunc http.HandlerFunc)
	Post(path string, handlerFunc http.HandlerFunc)
	Delete(path string, handlerFunc http.HandlerFunc)
	Patch(path string, handlerFunc http.HandlerFunc)
	Put(path string, handlerFunc http.HandlerFunc)
	Options(path string, handlerFunc http.HandlerFunc)
	Head(path string, handlerFunc http.HandlerFunc)
	Connect(path string, handlerFunc http.HandlerFunc)
	Trace(path string, handlerFunc http.HandlerFunc)

	// Group creates a new router group with the given relative path.
	// The fn function registers routes within the group, each of which
	// is resolved relative to the group's path.
	// For example, a group registered at "/api" with a route registered
	// at "/users" handles requests for "/api/users".
	Group(relativePath string, fn func(r Router))
}

Router is the set of core routing methods implemented by App, using only the standard net/http.

Jump to

Keyboard shortcuts

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