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

pisigo

package module
v0.0.0-...-772f697 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 22 Imported by: 0

README

Pisigo

Pisigo is a lightweight, production-oriented HTTP framework for Go.

Module: github.com/arnaudovproject/pisigo


Why Pisigo

Pisigo gives you a small core (router, context, middleware, errors) and optional adapters for SQL, MongoDB, Redis, NATS, metrics, tracing, auth, sessions, and more — without forcing a heavy application architecture.

  • Familiar handler style with explicit errors
  • First-class middleware and route groups
  • Built-in request binding and validation
  • Graceful shutdown, TLS, trusted proxies
  • Pluggable store / queue / pubsub interfaces
  • CLI for scaffolding, local development, migrations, and AI agent rules

Requirements

  • Go 1.26.1+

Install

Library
go get github.com/arnaudovproject/pisigo@latest
CLI
go install github.com/arnaudovproject/pisigo/cmd/pisigo@latest

Quick start

package main

import (
	"log"

	"github.com/arnaudovproject/pisigo"
	"github.com/arnaudovproject/pisigo/middleware"
)

func main() {
	app := pisigo.Boot()
	app.Use(
		middleware.Recover(),
		middleware.RequestID(),
		middleware.Logger(),
		middleware.Secure(),
	)

	app.GET("/", func(c *pisigo.Context) error {
		return c.JSON(200, map[string]string{"status": "ok"})
	})

	if err := app.Server("0.0.0.0", 8080); err != nil {
		log.Fatal(err)
	}
}
Scaffold a project
pisigo new myapp
cd myapp
# wire the module replace / go get as needed
pisigo install agents all    # optional: Cursor / Claude / Codex project rules
pisigo dev .

AI agent rules

Pisigo can install project-local instructions for popular coding agents so they understand the framework (routing, handlers, middleware, auth, adapters, recommended layout) with less prompting and fewer tokens.

Install into your app

Run from your application root (the project that already depends on Pisigo):

pisigo install agents all
pisigo install agents cursor
pisigo install agents claude
pisigo install agents codex
pisigo install agents cursor claude --force
pisigo install agents all -d ./myapp
Target What gets created Used by
cursor .cursor/rules/pisigo-*.mdc Cursor project rules
claude CLAUDE.md + .claude/rules/pisigo-*.md Claude Code
codex AGENTS.md OpenAI Codex / AGENTS.md-compatible tools
all Everything above —

Flags:

  • -d, --dir <path> — project root (default .)
  • -f, --force — overwrite existing files (default skips existing)
What the rules contain

Dense, English guidance covering:

  • How Pisigo boots, routes, groups, and handles errors
  • Recommended cmd/ + internal/{http,service,repository,domain} layout
  • Middleware, auth, sessions, binding/validation patterns
  • SQL / Mongo / store / queue / pubsub adapter usage
  • Migrations, health, metrics, and CLI commands

Commit the generated files in your application repo so the whole team gets the same agent context. Re-run with --force after upgrading Pisigo if templates improve.


Core concepts

App and routing
app := pisigo.Boot()
app.GET("/users/{id}", handler).SetName("user.show")
app.POST("/users", createUser)

api := app.Group("/api", authMiddleware)
api.GET("/me", me)

path, _ := app.Reverse("user.show", "id", "42") // /users/42

Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, CONNECT, TRACE, plus ANY.

Context

*pisigo.Context wraps request/response helpers:

Area Helpers
Request Param, Query, Bind, BindJSON, FormValue, BearerToken, Body
Response JSON, HTML, XML, String, Data, File, Download, Redirect, NoContent
Store Set, Get, MustGet
Misc IP, IsHTTPS, Log, Cookie, Header

Pooling: Context values are reused from a sync.Pool. Do not retain or access a *pisigo.Context after the handler returns (including from another goroutine) unless you call DetachFromPool() and later Release().

Errors

Return *pisigo.HTTPError (or sentinels like pisigo.ErrNotFound). The default error handler renders JSON:

return pisigo.NewHTTPError(400, "invalid input").WithDetails(map[string]any{
	"field": "email",
})
Validation

c.Bind(&in) unmarshals JSON/XML and validates validate struct tags via go-playground/validator.


Middleware

Middleware Purpose
Recover Panic recovery (stack stays in logs, client gets generic 500)
Logger Structured request logging (no bodies/secrets by default)
RequestID X-Request-ID propagation (validated; max 128 chars [A-Za-z0-9_-])
CORS Cross-origin headers (credentials-safe)
Timeout Request timeout — returns 504 without blocking on slow handlers; cannot replace an already-committed response; handlers must respect c.Request().Context()
Secure Security headers; HSTS only for TLS or trusted-proxy HTTPS
BasicAuth HTTP Basic
RateLimit Token / window limiting
Compress Gzip responses
CSRF CSRF cookie + header/form check
Cache Response cache via store.Store
app.Use(
	middleware.Recover(),
	middleware.RequestID(),
	middleware.Logger(),
	middleware.CORS(),
	middleware.Timeout(15*time.Second),
)

Auth and sessions

cfg := auth.DefaultJWTConfig("your-secret")
token, _ := auth.Issue(cfg, "user-id", []string{"admin"}, nil)

app.Use(auth.JWT(cfg), auth.RequireRoles("admin"))
app.Use(auth.APIKey("X-API-Key", func(k string) bool { return k == "secret" }))

app.Use(session.Middleware(session.Config{
	Store: session.NewMemoryStore(),
	// Secure cookies are on by default. For local HTTP:
	// InsecureCookie: true,
}))

Production checklist

Use these defaults and settings for internet-facing services:

  1. CORS — allowlist exact origins; never combine AllowOrigins: ["*"] with AllowCredentials: true (Pisigo ignores the wildcard in that case).
  2. Trusted proxies — call app.SetTrustedProxies(...) only for your load balancer / reverse proxy CIDRs. Client IP and IsHTTPS() / HSTS honor forwarded headers only from trusted peers.
  3. Cookies — session and CSRF cookies are Secure by default; use InsecureCookie: true / CSRF(CSRFConfig{Secure: false}) only on plain HTTP.
  4. WebSocket — default CheckOrigin is same-origin; pass a custom allowlist via websocket.Config if needed.
  5. Postgres — DSN default sslmode=require; set SSLMode: "disable" only for local databases.
  6. Body size — default max body is 1 MiB (SetMaxBodyBytes); form/multipart parsing respects the same limit.
  7. Metrics — protect the endpoint: m.Register(app, "/metrics", middleware.BasicAuth(...)).
  8. Realtime (SSE/WS) — use pisigo.StreamingServerConfig() (WriteTimeout disabled) or set timeouts explicitly via ServerWithConfig.
  9. Rate limit — store errors fail closed (503); prefer Redis rate limit store in multi-instance deployments.
  10. Timeout — handlers must respect c.Request().Context(); Timeout returns 504 promptly and does not wait for ignored cancellation. If the handler already wrote headers/body, Timeout cannot replace that response with 504 (HTTP responses are not rewindable).
  11. Server errors — Server / ServerWithConfig return error (e.g. bind failure); always check it.
if err := app.ServerWithConfig("0.0.0.0", 8080, pisigo.StreamingServerConfig()); err != nil {
	log.Fatal(err)
}

Data layer

SQL (db + adapters)
import "github.com/arnaudovproject/pisigo/adapters/sqlite"

sqlDB, err := sqlite.OpenMemory()
// postgres, mysql, mssql adapters available

db.SQL provides Get, Select, Exec, Query, Transaction, pool options, and DSN builders.

MongoDB
import "github.com/arnaudovproject/pisigo/adapters/mongodb"

mdb, err := mongodb.Open(mongodb.Config{
	URI: "mongodb://localhost:27017",
	Database: "app",
})
Store / Queue / PubSub

Interfaces live in store, queue, and pubsub.

Backend Store Queue PubSub Rate limit
Memory yes yes yes via middleware
Redis yes yes — yes
NATS — JetStream yes —

Migrations

pisigo migrate create add_users
m := migrate.New(sqlDB, "migrations")
_ = m.Up(ctx)
_ = m.Down(ctx, 1)

SQL files use:

-- +migrate Up
CREATE TABLE users (...);

-- +migrate Down
DROP TABLE users;

Ops

Health
h := health.New()
h.Ready("db", func(ctx context.Context) error { return sqlDB.Ping(ctx) })
h.Register(app, "/health") // /health/live, /health/ready
Metrics (Prometheus)
m := metrics.New("myapp")
app.Use(m.Middleware())
m.Register(app, "/metrics", middleware.BasicAuth(middleware.BasicAuthConfig{
	Validator: func(u, p string) bool { return u == "prom" && p == "secret" },
}))
OpenTelemetry
shutdown, _ := otelx.Setup("myapp")
defer shutdown(context.Background())
app.Use(otelx.Middleware("myapp"))

Setup configures W3C TraceContext propagation. Swap the stdout exporter for OTLP in your app when shipping to a collector.

OpenAPI
openapi.Register(app, "/openapi.json", "My API", "1.0.0")
// also serves /docs (Swagger UI)

Realtime

  • WebSocket — websocket.Upgrade / websocket.Handler
  • SSE — sse.Open + Event / Comment / Retry

CLI

Command Description
pisigo new <name> Scaffold a new app
pisigo dev [path] Run with auto-reload
pisigo prod [path] [-o out] Production build
pisigo generate handler <name> Handler stub
pisigo migrate create <name> Migration file
pisigo install agents <target> Install AI agent rules (cursor, claude, codex, or all)
pisigo install agents all
pisigo install agents cursor claude --force

See AI agent rules for formats and details.


Testing

Use the built-in helpers:

import pisigotest "github.com/arnaudovproject/pisigo/testing"

res := pisigotest.GET(app, "/hello")
res = pisigotest.POST(app, "/in", map[string]string{"email": "a@b.com"})

Run the suite:

go test ./...

CI runs the same suite on every push and pull request.


Project layout

pisigo/
├── app.go, router.go, context.go, server.go   # HTTP core
├── middleware/                                # HTTP middleware
├── auth/, session/, config/, i18n/            # Cross-cutting
├── db/, migrate/, adapters/                   # Data adapters
├── store/, queue/, pubsub/                    # Messaging interfaces
├── health/, metrics/, otelx/, openapi/        # Observability / docs
├── websocket/, sse/, circuit/                 # Realtime & resilience
├── testing/                                   # Test helpers
└── cmd/pisigo/                                # CLI (+ embedded agent templates)

Configuration

cfg, _ := config.LoadEnvFile(".env")
port := cfg.Int("PORT", 8080)
debug := cfg.Bool("DEBUG", false)
timeout := cfg.Duration("TIMEOUT", 15*time.Second)

Also supports LoadJSONFile (nested keys flattened as db.host) and LoadEnv.


License

MIT — see LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBadRequest            = NewHTTPError(http.StatusBadRequest, "bad request")
	ErrUnauthorized          = NewHTTPError(http.StatusUnauthorized, "unauthorized")
	ErrForbidden             = NewHTTPError(http.StatusForbidden, "forbidden")
	ErrNotFound              = NewHTTPError(http.StatusNotFound, "not found")
	ErrMethodNotAllowed      = NewHTTPError(http.StatusMethodNotAllowed, "method not allowed")
	ErrConflict              = NewHTTPError(http.StatusConflict, "conflict")
	ErrTooManyRequests       = NewHTTPError(http.StatusTooManyRequests, "too many requests")
	ErrInternalServer        = NewHTTPError(http.StatusInternalServerError, "internal server error")
	ErrServiceUnavailable    = NewHTTPError(http.StatusServiceUnavailable, "service unavailable")
	ErrGatewayTimeout        = NewHTTPError(http.StatusGatewayTimeout, "gateway timeout")
	ErrRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge, "request entity too large")
)

Functions

func Adapt

func Adapt(handler HandlerFunc, logger Logger) http.Handler

func AdaptApp

func AdaptApp(app *App, handler HandlerFunc) http.Handler

func Adapter

func Adapter(handler HandlerFunc) http.Handler

func ContextWithLogger

func ContextWithLogger(ctx context.Context, logger Logger) context.Context

func DefaultErrorHandler

func DefaultErrorHandler(c *Context, err error)

func Validate

func Validate(v any) error

func Validator

func Validator() *validator.Validate

Types

type App

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

func Boot

func Boot() *App

func (*App) ANY

func (a *App) ANY(path string, handler HandlerFunc) []*Route

func (*App) CONNECT

func (a *App) CONNECT(path string, handler HandlerFunc) *Route

func (*App) DELETE

func (a *App) DELETE(path string, handler HandlerFunc) *Route

func (*App) File

func (a *App) File(urlPath, filePath string)

func (*App) GET

func (a *App) GET(path string, handler HandlerFunc) *Route

func (*App) Group

func (a *App) Group(prefix string, middlewares ...Middleware) *Group

func (*App) HEAD

func (a *App) HEAD(path string, handler HandlerFunc) *Route

func (*App) HTTPServer

func (a *App) HTTPServer() *http.Server

func (*App) Handler

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

func (*App) ListenAndServe

func (a *App) ListenAndServe(addr string, cfg ServerConfig) error

func (*App) Logger

func (a *App) Logger() Logger

func (*App) OPTIONS

func (a *App) OPTIONS(path string, handler HandlerFunc) *Route

func (*App) PATCH

func (a *App) PATCH(path string, handler HandlerFunc) *Route

func (*App) POST

func (a *App) POST(path string, handler HandlerFunc) *Route

func (*App) PUT

func (a *App) PUT(path string, handler HandlerFunc) *Route

func (*App) Reverse

func (a *App) Reverse(name string, params ...string) (string, error)

func (*App) Routes

func (a *App) Routes() []Route

func (*App) Server

func (a *App) Server(host string, port int) error

func (*App) ServerWithConfig

func (a *App) ServerWithConfig(host string, port int, cfg ServerConfig) error

func (*App) SetErrorHandler

func (a *App) SetErrorHandler(handler ErrorHandler)

func (*App) SetLogger

func (a *App) SetLogger(logger Logger)

func (*App) SetMaxBodyBytes

func (a *App) SetMaxBodyBytes(n int64)

func (*App) SetMethodNotAllowed

func (a *App) SetMethodNotAllowed(handler HandlerFunc)

func (*App) SetNotFound

func (a *App) SetNotFound(handler HandlerFunc)

func (*App) SetProxyHeaders

func (a *App) SetProxyHeaders(headers ...string)

func (*App) SetTrustedProxies

func (a *App) SetTrustedProxies(cidrs ...string) error

func (*App) Shutdown

func (a *App) Shutdown(ctx context.Context) error

func (*App) Static

func (a *App) Static(prefix, root string)

func (*App) TRACE

func (a *App) TRACE(path string, handler HandlerFunc) *Route

func (*App) Use

func (a *App) Use(middlewares ...Middleware)

type Context

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

Context is request-scoped and may be reused from a pool after the handler returns. Do not retain or access a Context from another goroutine after the handler returns unless you call DetachFromPool and later Release.

Timeout middleware enables shared mode so the request goroutine and the handler goroutine may safely use the same Context until the handler finishes.

func NewContext

func NewContext(w http.ResponseWriter, r *http.Request) *Context

func (*Context) App

func (c *Context) App() *App

func (*Context) BearerToken

func (c *Context) BearerToken() string

func (*Context) Bind

func (c *Context) Bind(v any) error

func (*Context) BindJSON

func (c *Context) BindJSON(v any) error

func (*Context) BindXML

func (c *Context) BindXML(v any) error

func (*Context) Body

func (c *Context) Body() ([]byte, error)

func (*Context) Cookie

func (c *Context) Cookie(cookie *http.Cookie)

func (*Context) CookieGet

func (c *Context) CookieGet(name string) (*http.Cookie, error)

func (*Context) Cookies

func (c *Context) Cookies() []*http.Cookie

func (*Context) Data

func (c *Context) Data(status int, contentType string, data []byte) error

func (*Context) DeleteCookie

func (c *Context) DeleteCookie(name string, path string)

func (*Context) DetachFromPool

func (c *Context) DetachFromPool()

DetachFromPool prevents AdaptApp from returning this Context to the pool. The caller must invoke Release when the Context is no longer used.

func (*Context) DisableShared

func (c *Context) DisableShared()

DisableShared ends concurrent Context access. Only call after the handler goroutine has returned.

func (*Context) Download

func (c *Context) Download(filePath string, filename string) error

func (*Context) EnableShared

func (c *Context) EnableShared()

EnableShared marks this Context safe for concurrent use by Timeout's handler goroutine and the request goroutine. Call DisableShared only after the handler goroutine has finished.

func (*Context) Error

func (c *Context) Error(status int, message string) error

func (*Context) File

func (c *Context) File(path string) error

func (*Context) FormFile

func (c *Context) FormFile(key string) ([]byte, string, error)

func (*Context) FormValue

func (c *Context) FormValue(key string) string

func (*Context) Get

func (c *Context) Get(key string) (any, bool)

func (*Context) HTML

func (c *Context) HTML(status int, value string) error

func (*Context) Header

func (c *Context) Header(key string, value string)

func (*Context) HeaderGet

func (c *Context) HeaderGet(key string) string

func (*Context) HoldForHandler

func (c *Context) HoldForHandler(done <-chan struct{})

HoldForHandler keeps the Context alive until done is signaled, after the middleware chain finishes. Used by Timeout so the request can return 504 without waiting for a slow handler, while still protecting the pool.

func (*Context) Host

func (c *Context) Host() string

func (*Context) IP

func (c *Context) IP() string

func (*Context) IsHTTPS

func (c *Context) IsHTTPS() bool

IsHTTPS reports whether the request arrived over TLS, or was forwarded as HTTPS by a trusted proxy (X-Forwarded-Proto). Untrusted clients cannot spoof HTTPS via X-Forwarded-Proto alone.

func (*Context) IsJSON

func (c *Context) IsJSON() bool

func (*Context) IsXML

func (c *Context) IsXML() bool

func (*Context) JSON

func (c *Context) JSON(status int, data any) error

func (*Context) Log

func (c *Context) Log() Logger

func (*Context) MarkWritten

func (c *Context) MarkWritten(status int)

MarkWritten records that a response was already committed (status + body), so the error handler will not write again.

func (*Context) Method

func (c *Context) Method() string

func (*Context) MustGet

func (c *Context) MustGet(key string) any

func (*Context) NoContent

func (c *Context) NoContent() error

func (*Context) Param

func (c *Context) Param(key string) string

func (*Context) Path

func (c *Context) Path() string

func (*Context) Query

func (c *Context) Query(key string) string

func (*Context) QueryDefault

func (c *Context) QueryDefault(key string, fallback string) string

func (*Context) QueryInt

func (c *Context) QueryInt(key string, fallback int) int

func (*Context) Redirect

func (c *Context) Redirect(status int, location string) error

func (*Context) Referer

func (c *Context) Referer() string

func (*Context) Release

func (c *Context) Release()

Release returns a previously detached Context to the pool.

func (*Context) Request

func (c *Context) Request() *http.Request

func (*Context) Response

func (c *Context) Response() http.ResponseWriter

func (*Context) Set

func (c *Context) Set(key string, value any)

func (*Context) SetCookie

func (c *Context) SetCookie(
	name string,
	value string,
	maxAge int,
	path string,
	secure bool,
	httpOnly bool,
)

func (*Context) SetLogger

func (c *Context) SetLogger(logger Logger)

func (*Context) SetRequest

func (c *Context) SetRequest(r *http.Request)

func (*Context) SetWriter

func (c *Context) SetWriter(w http.ResponseWriter)

func (*Context) Status

func (c *Context) Status(status int)

func (*Context) StatusCode

func (c *Context) StatusCode() int

func (*Context) Stream

func (c *Context) Stream(status int, contentType string, reader io.Reader) error

func (*Context) String

func (c *Context) String(status int, value string) error

func (*Context) UserAgent

func (c *Context) UserAgent() string

func (*Context) Validate

func (c *Context) Validate(v any) error

func (*Context) Written

func (c *Context) Written() bool

func (*Context) XML

func (c *Context) XML(status int, data any) error

type ErrorHandler

type ErrorHandler func(c *Context, err error)

type Group

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

func (*Group) ANY

func (g *Group) ANY(path string, handler HandlerFunc) []*Route

func (*Group) CONNECT

func (g *Group) CONNECT(path string, handler HandlerFunc) *Route

func (*Group) DELETE

func (g *Group) DELETE(path string, handler HandlerFunc) *Route

func (*Group) GET

func (g *Group) GET(path string, handler HandlerFunc) *Route

func (*Group) Group

func (g *Group) Group(prefix string, middlewares ...Middleware) *Group

func (*Group) HEAD

func (g *Group) HEAD(path string, handler HandlerFunc) *Route

func (*Group) OPTIONS

func (g *Group) OPTIONS(path string, handler HandlerFunc) *Route

func (*Group) PATCH

func (g *Group) PATCH(path string, handler HandlerFunc) *Route

func (*Group) POST

func (g *Group) POST(path string, handler HandlerFunc) *Route

func (*Group) PUT

func (g *Group) PUT(path string, handler HandlerFunc) *Route

func (*Group) TRACE

func (g *Group) TRACE(path string, handler HandlerFunc) *Route

func (*Group) Use

func (g *Group) Use(middlewares ...Middleware)

type HTTPError

type HTTPError struct {
	Code     int            `json:"-"`
	Message  string         `json:"message"`
	Details  map[string]any `json:"details,omitempty"`
	Internal error          `json:"-"`
}

func AsHTTPError

func AsHTTPError(err error) (*HTTPError, bool)

func NewHTTPError

func NewHTTPError(code int, message string) *HTTPError

func NewHTTPErrorWithInternal

func NewHTTPErrorWithInternal(code int, message string, internal error) *HTTPError

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Status

func (e *HTTPError) Status() int

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

func (*HTTPError) WithDetails

func (e *HTTPError) WithDetails(details map[string]any) *HTTPError

type HandlerFunc

type HandlerFunc func(*Context) error

func Chain

func Chain(handler HandlerFunc, middlewares ...Middleware) HandlerFunc

type Logger

type Logger interface {
	Debug(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
	With(args ...any) Logger
}

func DefaultLogger

func DefaultLogger() Logger

func FromSlog

func FromSlog(l *slog.Logger) Logger

func LoggerFromContext

func LoggerFromContext(ctx context.Context) (Logger, bool)

func NewLogger

func NewLogger(level slog.Level) Logger

func NewTextLogger

func NewTextLogger(level slog.Level) Logger

type Middleware

type Middleware func(HandlerFunc) HandlerFunc

type ProxyConfig

type ProxyConfig struct {
	TrustedCIDRs []string
	Headers      []string
}

func DefaultProxyConfig

func DefaultProxyConfig() ProxyConfig

type Route

type Route struct {
	Method  string
	Path    string
	Handler HandlerFunc
	Name    string
	// contains filtered or unexported fields
}

func (*Route) SetName

func (r *Route) SetName(name string) *Route

type ServerConfig

type ServerConfig struct {
	ReadTimeout       time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration
	ReadHeaderTimeout time.Duration
	ShutdownTimeout   time.Duration
	MaxHeaderBytes    int
	CertFile          string
	KeyFile           string
}

func DefaultServerConfig

func DefaultServerConfig() ServerConfig

func StreamingServerConfig

func StreamingServerConfig() ServerConfig

StreamingServerConfig disables WriteTimeout so SSE and WebSocket handlers can hold the connection open. Prefer this (or ServerWithConfig) for realtime routes.

Jump to

Keyboard shortcuts

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