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
- Install
- Quick Start
- Core Concepts
- Request & Response
- Views & Asset Serving
- Middleware
- Benchmarks
- License
- 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, andMiddlewaresinterfaces. - View Engine: Integrated minimal view engine with support for
fs.FSand error page fallbacks. - Zero-Boilerplate: Focus on business logic, let the framework handle the plumbing.
go get github.com/altlimit/restructpackage 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)
}Any exported struct can be a service. Public methods become endpoints.
func (s *Svc) Index()->/func (s *Svc) Users()->/usersfunc (s *Svc) Users_0()->/users/{0}func (s *Svc) UsersExport()->/users-exportfunc (s *Svc) Any()->/{any*}(Wildcard catch-all)
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"].
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"`
}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
}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.*restruct.Render: Force rendering a specific template path (see Explicit Template Rendering).
Implement the Writer interface to enable HTML rendering and asset serving for your service. This isolates views to specific services.
Use the included View to automatically handle template rendering and asset serving using the file system structure.
func (s *MyService) Writer() *restruct.Writer {
return &restruct.View{
FS: publicFS, // fs.FS interface
Error: "error.html", // Validation/Error template
}
}- If a method returns a struct/map,
restructfirst checks for a matching template (e.g.,index.htmlforIndexmethod). - If no template is found, it falls back to the configured
Errortemplate (ifAnyroute matched) or JSON/DefaultWriter (if specific route matched).
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,
// ...
}
}Use *restruct.Render to force rendering a specific template, regardless of the request URL:
func (s *Service) Dashboard(ctx context.Context) (*restruct.Render, error) {
userID := restruct.Vars(ctx)["id"]
if userID == "" {
return nil, errors.New("user not found")
}
return &restruct.Render{
Path: "dashboard/main.html",
Data: map[string]interface{}{
"UserID": userID,
"Title": "Dashboard",
},
}, nil
}Path: Template path relative to the View's FS root.Data: Data passed to the template (accessible as.Dataor merged if map).
Middleware can be applied globally, per-service, or per-route.
func (s *Service) Init(h *restruct.Handler) {
h.Use(loggingMiddleware)
h.Use(authMiddleware)
}High performance with minimal overhead. (See bench_test.go for latest results).
MIT