restruct

package module
v0.0.0-...-cf49cd6 Latest Latest
Warning

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

Go to latest
Published: Jan 28, 2026 License: MIT Imports: 21 Imported by: 0

README

Run Tests Go Reference

restruct

restruct is a high-performance, service-oriented REST framework for Go. It allows you to build APIs by simply defining structs and methods, automating routing, parameter binding, validation, and view rendering.



Key Features

  • Struct-Based Routing: Exported methods are automatically mapped to routes.
  • Hierarchical Services: Nest structs to create path hierarchies (e.g., /api/v1/...).
  • Smart Binding: Auto-bind JSON, Form, and Query parameters to struct arguments.
  • Interface Driven: Customize behavior via Router, Viewer, Init, and Middlewares interfaces.
  • View Engine: Integrated minimal view engine with support for fs.FS and error page fallbacks.
  • Zero-Boilerplate: Focus on business logic, let the framework handle the plumbing.

Install

go get github.com/altlimit/restruct

Quick Start

package main

import (
	"net/http"
	"github.com/altlimit/restruct"
)

type Calculator struct{}

// POST /add
// Payload: {"a": 10, "b": 20}
func (c *Calculator) Add(req struct {
	A int `json:"a"`
	B int `json:"b"`
}) int {
	return req.A + req.B
}

func main() {
	restruct.Handle("/", &Calculator{})
	http.ListenAndServe(":8080", nil)
}

Core Concepts

Services & Methods

Any exported struct can be a service. Public methods become endpoints.

  • func (s *Svc) Index() -> /
  • func (s *Svc) Users() -> /users
  • func (s *Svc) Users_0() -> /users/{0}
  • func (s *Svc) UsersExport() -> /users-export
  • func (s *Svc) Any() -> /{any*} (Wildcard catch-all)
Routing & Parameters

You can define path parameters and wildcards using special method naming conventions or the Router interface.

Method Naming:

  • Find_0_User -> /find/{blobID}/user (where 0 maps to the first variable)
  • Files_Any -> /files/{any*} (Wildcard catch-all)

Explicit Routing (Router Interface): Recommended for cleaner method names.

func (s *Service) Routes() []restruct.Route {
    return []restruct.Route{
        {Handler: "Download", Path: "files/{id}", Methods: []string{"GET"}},
        {Handler: "Upload", Path: "files", Methods: []string{"POST"}},
    }
}

Path parameters can be accessed via restruct.Params(r)["id"].

Nested Services

Services can be nested to create API versions or groups.

type Server struct {
    ApiV1 V1 `route:"api/v1"` // Mounts V1 at /api/v1
    Admin AdminService `route:"admin"`
}

Request & Response

Binding & Validation

restruct binds request data (JSON body, Form data, Query params) to method arguments automatically. You can extend the default binder to add validation (e.g., using go-playground/validator).

type CreateRequest struct {
    Email string `json:"email" validate:"required,email"`
}

func (s *Service) Create(r *http.Request, req CreateRequest) error {
    // req is already bound and valid (if custom binder set up)
    return nil
}
Response Writers

Handlers can return:

  • struct / map / slice: Encoded as JSON by default.
  • string / []byte: Sent as raw response.
  • error: Converted to appropriate HTTP error status.
  • *restruct.Response: Complete control over Status, Headers, and Content.

Views & Asset Serving

Viewer Interface

Implement the Viewer interface to enable HTML rendering and asset serving for your service. This isolates views to specific services.

func (s *MyService) View() *restruct.View {
    return &restruct.View{
        FS:    publicFS, // fs.FS interface
        Error: "layout/error.html", // Validation/Error template
    }
}
  • If a method returns a struct/map, restruct first checks for a matching template (e.g., index.html for Index method).
  • If no template is found, it falls back to the configured Error template (if Any route matched) or JSON/DefaultWriter (if specific route matched).
Using embed.FS

You can easily serve embedded assets.

//go:embed public
var publicFS embed.FS

func (s *Server) View() *restruct.View {
    sub, _ := fs.Sub(publicFS, "public")
    return &restruct.View{
        FS: sub,
        // ...
    }
}

Middleware

Middleware can be applied globally, per-service, or per-route.

func (s *Service) Init(h *restruct.Handler) {
    h.Use(loggingMiddleware)
    h.Use(authMiddleware)
}

Benchmarks

High performance with minimal overhead. (See bench_test.go for latest results).

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrReaderReturnLen = errors.New("reader args len does not match")
)
View Source
var (
	MaxBodySize int64 = 10485760 // 10MB default limit
)

Functions

func Bind

func Bind(r *http.Request, out interface{}, methods ...string) error

Bind checks for valid methods and tries to bind query strings and body into struct

func BindForm

func BindForm(r *http.Request, out interface{}) error

BindForm puts all struct fields with tag:"form" from a form request

func BindJson

func BindJson(r *http.Request, out interface{}) error

BindJson puts all json tagged values into struct fields

func BindQuery

func BindQuery(r *http.Request, out interface{}) error

BindQuery puts all query string values into struct fields with tag:"query"

func GetVal

func GetVal(ctx context.Context, key string) interface{}

func GetVals

func GetVals(ctx context.Context) map[string]interface{}

func GetValue

func GetValue(r *http.Request, key string) interface{}

GetValue returns the stored value from context

func GetValues

func GetValues(r *http.Request) map[string]interface{}

GetValues returns a map of all values from context

func Handle

func Handle(pattern string, svc interface{})

Handle registers a struct or a *Handler for the given pattern in the http.DefaultServeMux.

func Params

func Params(r *http.Request) map[string]string

Params returns map of params from url path like /{param1} will be map[param1] = value

func Query

func Query(r *http.Request, name string) string

Query returns a query string value

func Recovery

func Recovery(next http.Handler) http.Handler

Recovery middleware handles panics and returns 500 error

func SetVal

func SetVal(ctx context.Context, key string, val interface{}) context.Context

func SetValue

func SetValue(r *http.Request, key string, val interface{}) *http.Request

SetValue stores a key value pair in context

func Vars

func Vars(ctx context.Context) map[string]string

Vars returns map of params from url from request context

Types

type DefaultReader

type DefaultReader struct {
	Bind func(*http.Request, interface{}, ...string) error
}

DefaultReader processes request with json.Encoder, urlencoded form and multipart for structs if it's just basic types it will be read from body as array such as [1, "hello", false] you can overwrite bind to apply validation library, etc

func (*DefaultReader) Read

func (dr *DefaultReader) Read(r *http.Request, types []reflect.Type) (vals []reflect.Value, err error)

type DefaultWriter

type DefaultWriter struct {
	// Optional ErrorHandler, called whenever unhandled errors occurs, defaults to logging errors
	ErrorHandler   func(error)
	Errors         map[error]Error
	EscapeJsonHtml bool
}

DefaultWriter uses json.Encoder for output and manages error handling. Adding Errors mapping can help with your existing error to a proper Error{}

func (*DefaultWriter) Write

func (dw *DefaultWriter) Write(w http.ResponseWriter, r *http.Request, types []reflect.Type, vals []reflect.Value)

Write implements the DefaultWriter ResponseWriter returning (int, any, error) will write status int, any response if error is nil returning (any, error) will write any response if error is nil with status 200 or 400, 500 depdening on your error returning (int, any, any, error) will write status int slice of [any, any] response if error is nil

func (*DefaultWriter) WriteJSON

func (dw *DefaultWriter) WriteJSON(w http.ResponseWriter, out interface{})

This writes application/json content type uses status code 200 on valid ones and 500 on uncaught, 400 on malformed json, etc. use Json{Status, Content} to specify a code

func (*DefaultWriter) WriteResponse

func (dw *DefaultWriter) WriteResponse(w http.ResponseWriter, resp *Response)

type Error

type Error struct {
	Status  int
	Message string
	Data    interface{}
	Err     error
}

func (Error) Error

func (e Error) Error() string

type Handler

type Handler struct {
	// Writer controls the output of your service, defaults to DefaultWriter
	Writer ResponseWriter
	// Reader controls the input of your service, defaults to DefaultReader
	Reader RequestReader
	// contains filtered or unexported fields
}

func NewHandler

func NewHandler(svc interface{}) *Handler

NewHandler creates a handler for a given struct.

func (*Handler) AddService

func (h *Handler) AddService(path string, svc interface{})

AddService adds a new service to specified route. You can put {param} in this route.

func (*Handler) Routes

func (h *Handler) Routes() (routes []string)

Routes returns a list of routes registered and it's definition

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP calls the method with the matched route.

func (*Handler) Use

func (h *Handler) Use(fns ...Middleware)

Use adds a middleware to your services.

func (*Handler) WithPrefix

func (h *Handler) WithPrefix(prefix string) *Handler

WithPrefix prefixes your service with given path. You can't use parameters here. This is useful if you want to register this handler with another third party router.

type Init

type Init interface {
	Init(*Handler)
}

Init interface to access and override handler configs

type Json

type Json struct {
	Status  int
	Content interface{}
}

Json response to specify a status code for default writer

type Middleware

type Middleware func(http.Handler) http.Handler

type Middlewares

type Middlewares interface {
	Middlewares() []Middleware
}

Middlewares interface for common middleware for a struct

type RequestReader

type RequestReader interface {
	Read(*http.Request, []reflect.Type) ([]reflect.Value, error)
}

RequestReader is called for input for your method if your parameter contains a things other than *http.Request, http.ResponseWriter, context.Context you'll get a slice of types and you must return values corresponding to those types

type Response

type Response struct {
	Status      int
	Headers     map[string]string
	ContentType string
	Content     []byte
}

Response is used by DefaultWriter for custom response

type ResponseWriter

type ResponseWriter interface {
	Write(http.ResponseWriter, *http.Request, []reflect.Type, []reflect.Value)
}

ResponseWriter is called on outputs of your methods. slice of reflect.Type & Value is the types and returned values

type Route

type Route struct {
	// Handler is the method name you want to use for this route
	Handler string
	// optional path, will use default behaviour if not present
	Path string
	// optional methods, will allow all if not present
	Methods []string
	// optional middlewares, run specific middleware for this route
	Middlewares []Middleware
}

Route for doing overrides with router interface and method restrictions.

type Router

type Router interface {
	Routes() []Route
}

Router can be used to override method name to specific path, implement Router interface in your service and return a slice of Route: [Route{Handler:"ProductEdit", Path: "product/{pid}"}]

type View

type View struct {
	// Source file system
	FS fs.FS
	// Funcs for templates
	Funcs template.FuncMap
	// Skips files matching this regex
	Skips *regexp.Regexp
	// Default layout to use if not specified in frontmatter
	Layout string
	// Error template to use if view/file not found
	Error string

	// Writer to fallback for non-view responses or errors.
	// If nil, it will use the Handler's writer if available, or default to DefaultWriter.
	Writer *ResponseWriter
	// contains filtered or unexported fields
}

View handles rendering of templates and static files from a file system.

func (*View) Routes

func (v *View) Routes() []string

Routes scans the FS and returns a Map of path -> method name (which is "Serve") It returns a list of paths that should be registered.

func (*View) Serve

func (v *View) Serve() interface{}

Serve is a placeholder method used to register routes for static files/views that don't have a backing struct method. It returns nil so that the ResponseWriter (View.Write) is triggered.

func (*View) Write

func (v *View) Write(w http.ResponseWriter, r *http.Request, types []reflect.Type, vals []reflect.Value)

Write implements ResponseWriter. It checks if the request path matches a view file. If so, it renders the view. Otherwise, it delegates to DefaultWriter (if set) or handles it as static file if appropriate.

type Viewer

type Viewer interface {
	View() *View
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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