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

solanum

package module
v0.5.0-rc3 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 14 Imported by: 2

README

Solanum — A Modular Gin-Based Web Framework

Solanum is a lightweight framework built on top of Gin that helps Go developers build clean, maintainable HTTP services and micro-APIs with explicit structure and flow.

Instead of hiding control flow behind containers or implicit wiring, Solanum encourages clear module boundaries, predictable routing, and readable composition.


Why Solanum?

A typical Gin application often grows into:

  • A massive main.go that wires routes, middleware, and handlers inline
  • Implicit control flow that’s hard to follow once the project grows
  • Business logic spread across handlers with no clear module boundaries
  • Boilerplate duplicated across services and endpoints

Solanum addresses these problems by focusing on:

1. Explicit Flow Over Magic

All routes, handlers, and middleware are registered explicitly. There is no hidden resolution or automatic injection —
what you write is exactly what runs.

2. Modular Structure

Routes are grouped into self-contained Modules. Each module owns its routing prefix, controllers, and middleware.

3. Predictable Composition

Applications are composed step by step: modules → controllers → handlers → server. You can trace request flow by reading the code top to bottom.

4. Test-Friendly Design

Because handlers and services are constructed explicitly, you can test them in isolation without bootstrapping the entire server.


What Can You Build?

  • RESTful APIs
  • Microservices
  • GraphQL or gRPC gateways
  • Admin or internal tools
  • Web servers for applications
    • Health checks
    • Metrics
    • Admin dashboards

Solanum scales from a single /ping endpoint to multi-module services without changing how you structure your code.


Core Principles

  • Explicit is better than implicit
  • Composition over configuration
  • No hidden lifecycle or control flow
  • Readable code beats clever abstractions

Solanum does not try to replace Gin — it provides a clear structure around it.


Key Features

1. Modular Architecture
// Define a /users module
userModule := solanum.NewModule(
    solanum.WithUri("/users"),
)
userModule.SetControllers(myUserController)

Each module:

  • Owns its route prefix
  • Registers its controllers explicitly
  • Can be reasoned about independently
2. Controller-Centered Routing
type SolaService struct {
    Uri          string            // relative to the module prefix
    Method       string            // GET, POST, ...
    PreHandlers  []gin.HandlerFunc // route-only middleware before Handler
    Handler      gin.HandlerFunc
    PostHandlers []gin.HandlerFunc // route-only middleware after Handler
}

Controllers define:

  • HTTP method
  • Relative URI
  • Handler function (plus optional per-route pre/post handlers)

A service without Method or Handler panics at startup instead of silently registering a broken route.

No reflection-based routing, no annotations — just plain Go structures.

3. Module-Level Middleware

Each module supports pre-middleware (runs before the handler) and post-middleware (runs after the handler).

module := solanum.NewModule(
    solanum.WithUri("/users"),
)

// Pre-middleware: executed before the handler
module.SetPreMiddlewares(authMiddleware, loggingMiddleware)

// Post-middleware: executed after the handler
module.SetPostMiddlewares(responseLogger)

// You can also append individual middleware
module.AddPreMiddleware(rateLimiter)
module.AddPostMiddleware(metricsCollector)

The final handler chain for each route is:

module pre-middlewares → service PreHandlers → handler → service PostHandlers → module post-middlewares

Post handlers run even when an earlier handler calls c.Abort*() (use c.IsAborted() to tell), which makes them a good fit for logging and metrics. By then the response is usually already written, so they cannot change the status code or headers.

Module is a small interface (Uri() and SetRoutes()), so you can also plug in your own implementation:

type healthModule struct{}

func (healthModule) Uri() string { return "/health" }

func (healthModule) SetRoutes(r *gin.RouterGroup) {
    r.GET("", func(c *gin.Context) { c.Status(http.StatusNoContent) })
}

server.AddModules(healthModule{})
4. Application-Level CORS
import "github.com/annuums/solanum/middleware/cors"

server.Cors(
  cors.WithUrls([]string{"https://example.com"}),
  cors.WithMethods([]string{"GET", "POST"}),
  cors.WithAllowCredentials(true),
  cors.WithMaxAge(12 * time.Hour),
)

CORS middleware is applied at the application level using functional options. Sensible defaults are provided for headers and methods when not specified.

  • Without WithUrls / WithOriginFunc, all origins are allowed with Access-Control-Allow-Origin: * (the request origin is never echoed back).
  • WithAllowCredentials(true) requires an explicit origin list or origin func; combining it with all origins panics.
  • Call Cors before Run / Handler; calling it afterwards panics because existing routes would not be covered.
5. Production-Ready Server
server := solanum.NewSolanum(
    solanum.WithPort(8080),
    solanum.WithGinMode(gin.ReleaseMode),
    solanum.WithMiddlewares(gin.Logger(), requestID),   // application-wide middlewares
    solanum.WithRecovery(gin.CustomRecovery(onPanic)),   // gin.Recovery() by default
    solanum.WithLogger(slog.Default()),                  // framework logs (nil silences them)
    solanum.WithReadHeaderTimeout(5 * time.Second),
    solanum.WithShutdownTimeout(15 * time.Second),
)

// Blocks until SIGINT/SIGTERM, then shuts down gracefully.
if err := server.Run(); err != nil {
    log.Fatal(err)
}

// Or control the lifecycle yourself:
// err := server.RunContext(ctx)
Option Default
WithReadHeaderTimeout 10s
WithReadTimeout 30s
WithWriteTimeout 0 (unlimited, for streaming)
WithIdleTimeout 120s
WithShutdownTimeout 10s
WithRecovery gin.Recovery()
6. Testing Without a Network

Handler() registers the routes once and returns an http.Handler:

rec := httptest.NewRecorder()
server.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/ping", nil))

Getting Started

Get Solanum
go get github.com/annuums/solanum
Easy Start
package main

import (
  "log"
  "net/http"

  "github.com/annuums/solanum"
  "github.com/gin-gonic/gin"
)

func main() {

  pingModule := solanum.NewModule(
    solanum.WithUri("/ping"),
  )

  ctrl := solanum.NewController()
  ctrl.SetHandlers(
    &solanum.SolaService{
      Uri:    "",
      Method: http.MethodGet,
      Handler: func(c *gin.Context) {
        c.String(http.StatusOK, "pong")
      },
    },
  )
   
  pingModule.SetControllers(ctrl)

  server := solanum.NewSolanum(
    solanum.WithPort(5050),
  )

  server.AddModules(pingModule)
  if err := server.Run(); err != nil {
    log.Fatal(err)
  }
}

Every step is explicit:

  • Module creation
  • Controller registration
  • Handler definition
  • Server composition

No hidden wiring. No surprises.

Examples & More

See the Simple Example for a minimal health-check server.

Migrating from earlier versions

Before After
server.Run() panicked on failure Run() error returns the error; it also shuts down gracefully on SIGINT/SIGTERM
SetModules appended SetModules replaces, AddModules appends
cors.WithMaxAge(12) (hours) cors.WithMaxAge(12 * time.Hour)
cors.DefaultCredentials removed (it was unused)
CORS without urls echoed any origin answers with *; credentials require explicit origins
Post middlewares skipped on Abort always run after the chain
NewSolanum returned Runner returns *solanum.Server (still implements Runner)
runner.Engine field GinEngine()
Module / Controller interfaces exposed setters interfaces hold only Uri/SetRoutes and Handlers; use *SolaModule / *SolaController for setters
InitGlobalMiddlewares() use WithMiddlewares / WithRecovery

Who Is This For?

  • Go developers who value explicit control flow
  • Teams building REST APIs or microservices with Gin
  • Projects that want structure without heavy frameworks
  • Engineers who prefer readable composition over implicit behavior

If you believe that clarity beats convenience, Solanum is for you.

Documentation

Index

Constants

View Source
const (
	DefaultReadHeaderTimeout = 10 * time.Second
	DefaultReadTimeout       = 30 * time.Second
	DefaultWriteTimeout      = 0
	DefaultIdleTimeout       = 120 * time.Second
	DefaultShutdownTimeout   = 10 * time.Second
)

Default http.Server timeouts applied by NewSolanum. WriteTimeout is unlimited by default so streaming responses (SSE, downloads) keep working.

Variables

View Source
var ErrPortNotConfigured = errors.New("solanum: server port is not configured; use WithPort")

ErrPortNotConfigured is returned by Run when no valid port is set.

Functions

This section is empty.

Types

type Controller

type Controller interface {
	// Handlers returns the slice of service definitions this controller manages.
	Handlers() []*SolaService
}

Controller declares a set of service handlers for a logical grouping of routes. SolaController is the default implementation.

type Module

type Module interface {
	// Uri returns the base URI path for this module (e.g., "/users").
	Uri() string

	// SetRoutes binds the module's controllers, middleware, and routes onto a RouterGroup.
	SetRoutes(router *gin.RouterGroup)
}

Module represents a self-contained HTTP module mounted under its own URI prefix. SolaModule is the default implementation with controllers and pre/post middlewares.

type ModuleOption

type ModuleOption func(*SolaModule) error

ModuleOption configures a SolaModule. NewModule panics when an option returns an error.

func WithUri added in v0.4.4

func WithUri(uri string) ModuleOption

WithUri sets the module's base URI prefix. It must be empty or start with '/'.

type Option

type Option func(*Server)

Option configures a Server.

func WithGinMode

func WithGinMode(mode string) Option

WithGinMode sets the global gin mode (gin.DebugMode, gin.ReleaseMode, gin.TestMode) before the engine is created. Note that gin mode is process-wide.

func WithIdleTimeout

func WithIdleTimeout(d time.Duration) Option

WithIdleTimeout sets http.Server.IdleTimeout (zero falls back to the read timeout).

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets the logger used by the framework itself (defaults to slog.Default()). Passing nil silences framework logs.

func WithMiddlewares

func WithMiddlewares(middlewares ...gin.HandlerFunc) Option

WithMiddlewares appends application-wide middlewares (logger, authentication, ...) that run before every module's middlewares.

func WithPort added in v0.4.4

func WithPort(port int) Option

WithPort sets the TCP port the server listens on.

func WithReadHeaderTimeout

func WithReadHeaderTimeout(d time.Duration) Option

WithReadHeaderTimeout sets http.Server.ReadHeaderTimeout (zero means no timeout).

func WithReadTimeout

func WithReadTimeout(d time.Duration) Option

WithReadTimeout sets http.Server.ReadTimeout (zero means no timeout).

func WithRecovery

func WithRecovery(recovery gin.HandlerFunc) Option

WithRecovery replaces the default gin.Recovery() middleware (e.g. with gin.CustomRecovery). Passing nil disables panic recovery.

func WithShutdownTimeout

func WithShutdownTimeout(d time.Duration) Option

WithShutdownTimeout sets how long graceful shutdown waits for in-flight requests.

func WithWriteTimeout

func WithWriteTimeout(d time.Duration) Option

WithWriteTimeout sets http.Server.WriteTimeout (zero means no timeout).

type Runner

type Runner interface {
	// InitModules sets up all registered modules and their routes (idempotent).
	InitModules()

	// Handler initializes module routes (once) and returns an http.Handler for tests or custom servers.
	Handler() http.Handler

	// Modules returns a slice of pointers to registered Modules.
	Modules() []Module

	// SetModules replaces all registered Modules with the provided list.
	SetModules(m ...Module)

	// AddModules appends one or more Modules to the Runner.
	AddModules(m ...Module)

	// GinEngine exposes the underlying *gin.Engine for custom setup.
	GinEngine() *gin.Engine

	// Port exposes the configured port for the HTTP server.
	Port() int

	// Cors applies CORS configuration to the Gin engine using functional options.
	// It panics when the options are insecure (e.g. credentials with all origins)
	// or when called after routes are initialized.
	Cors(opts ...func(*cors.Option))

	// Run boots the HTTP server, initializing modules and listening on the configured port.
	// It shuts down gracefully on SIGINT/SIGTERM and returns an error when serving fails.
	Run() error

	// RunContext is like Run but shuts down gracefully when ctx is done.
	RunContext(ctx context.Context) error
}

Runner is the application entrypoint interface for Solanum. It manages module initialization, CORS, and server start. Global middlewares are configured with NewSolanum options (WithMiddlewares, WithRecovery).

type Server

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

Server implements the Runner interface and drives the application startup. It holds the Gin engine, listening port, and registered modules.

func NewSolanum

func NewSolanum(opts ...Option) *Server

NewSolanum creates and returns a new Server configured with the given options. gin.Recovery() is registered by default; see WithRecovery.

func (*Server) AddModules

func (server *Server) AddModules(m ...Module)

AddModules appends one or more Module implementations to the Runner.

func (*Server) Cors

func (server *Server) Cors(opts ...func(*cors2.Option))

Cors applies configured CORS settings to the Gin engine using the cors middleware. Accepts functional options for customizing allowed origins, methods, headers, etc. It panics when the options are insecure (e.g. credentials with all origins) or when called after routes are initialized.

func (*Server) GinEngine

func (server *Server) GinEngine() *gin.Engine

GinEngine returns the underlying *gin.Engine for direct access and customization.

func (*Server) Handler

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

Handler initializes module routes (once) and returns the server as an http.Handler. Useful for tests with httptest or for embedding into a custom http.Server.

func (*Server) InitModules

func (server *Server) InitModules()

InitModules sets up routing groups for each Module and applies their routes. It is idempotent: routes are registered only on the first call.

func (*Server) Modules

func (server *Server) Modules() []Module

Modules returns the slice of all registered Module pointers.

func (*Server) Port

func (server *Server) Port() int

Port returns the configured port for the HTTP server.

func (*Server) Run

func (server *Server) Run() error

Run initializes all modules and serves HTTP on the configured port until SIGINT or SIGTERM is received, then shuts down gracefully.

func (*Server) RunContext

func (server *Server) RunContext(ctx context.Context) error

RunContext initializes all modules and serves HTTP on the configured port until ctx is done, then shuts down gracefully within the shutdown timeout. It returns nil after a graceful shutdown and an error when the server fails to start or stop.

func (*Server) SetModules

func (server *Server) SetModules(m ...Module)

SetModules replaces all registered Module implementations with the provided list.

type SolaController

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

SolaController groups one or more SolaService handlers under a logical controller. It implements the Controller interface.

func NewController

func NewController() *SolaController

NewController constructs an empty SolaController ready to receive handlers.

func (*SolaController) Handlers

func (ctr *SolaController) Handlers() []*SolaService

Handlers returns the slice of SolaService entries managed by this controller.

func (*SolaController) SetHandlers

func (ctr *SolaController) SetHandlers(handlers ...*SolaService)

SetHandlers replaces the controller's handler list with the provided entries.

type SolaModule

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

SolaModule encapsulates a self-contained HTTP module with its own URI prefix, controllers, and middleware stacks.

func NewModule

func NewModule(opts ...ModuleOption) *SolaModule

NewModule creates a new SolaModule with the given URI prefix. The module starts with empty controller and middleware lists.

func (*SolaModule) AddControllers

func (m *SolaModule) AddControllers(c ...Controller)

AddControllers appends one or more Controller implementations to this module.

func (*SolaModule) AddPostMiddleware added in v0.3.2

func (m *SolaModule) AddPostMiddleware(middleware gin.HandlerFunc)

AddPostMiddleware appends a single middleware to the post-handler chain.

func (*SolaModule) AddPreMiddleware added in v0.3.2

func (m *SolaModule) AddPreMiddleware(middleware gin.HandlerFunc)

AddPreMiddleware appends a single middleware to the pre-handler chain.

func (*SolaModule) Controllers

func (m *SolaModule) Controllers() []Controller

Controllers returns all controllers registered in this module.

func (*SolaModule) PostMiddlewares added in v0.3.0

func (m *SolaModule) PostMiddlewares() []gin.HandlerFunc

PostMiddlewares returns the list of middleware to execute after handlers.

func (*SolaModule) PreMiddlewares added in v0.3.0

func (m *SolaModule) PreMiddlewares() []gin.HandlerFunc

PreMiddlewares returns the list of middleware to execute before handlers.

func (*SolaModule) SetControllers

func (m *SolaModule) SetControllers(c ...Controller)

SetControllers registers one or more Controller implementations for this module.

func (*SolaModule) SetPostMiddlewares added in v0.4.0

func (m *SolaModule) SetPostMiddlewares(middlewares ...gin.HandlerFunc)

SetPostMiddlewares replaces the post-handler middleware chain with the provided list.

func (*SolaModule) SetPreMiddlewares added in v0.4.0

func (m *SolaModule) SetPreMiddlewares(middlewares ...gin.HandlerFunc)

SetPreMiddlewares replaces the pre-handler middleware chain with the provided list.

func (*SolaModule) SetRoutes

func (m *SolaModule) SetRoutes(router *gin.RouterGroup)

SetRoutes registers the module's routes and middleware on the given RouterGroup. The final handler chain per route is: module pre-middlewares → service PreHandlers → handler → service PostHandlers → module post-middlewares. Post handlers run after the rest of the chain completes, even when it was aborted (check c.IsAborted() to tell); they are skipped only when the chain panics. It panics when a service is nil, has no Handler, or has no Method.

func (*SolaModule) Uri

func (m *SolaModule) Uri() string

Uri returns the base URI prefix for this module.

type SolaService

type SolaService struct {
	// Uri is the route path relative to the module prefix (e.g., "/:id").
	Uri string

	// Method is the HTTP method to bind (GET, POST, etc.).
	Method string

	// PreHandlers are middleware functions to run before Handler for this route only.
	PreHandlers []gin.HandlerFunc

	// Handler is the Gin handler function to execute.
	Handler gin.HandlerFunc

	// PostHandlers are middleware functions to run after Handler for this route only.
	// They run even when a pre-handler or Handler calls ctx.Abort*() (check ctx.IsAborted()),
	// so they suit logging and metrics. The response may already be written by then,
	// so changing the status code or headers here has no effect.
	PostHandlers []gin.HandlerFunc
}

SolaService represents a single HTTP route handler configuration.

Directories

Path Synopsis
docs
examples/simple command
middleware

Jump to

Keyboard shortcuts

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