Gin, with the boring parts done.
Or push to GitHub and run it on AWS Lambda with Ginboot Cloud, free to startControllers, not boilerplate
A handler is a function that takes a request and returns a value and an error. Ginboot does the binding, status codes, tracing and JSON around it, and the same binary runs as an HTTP server or a Lambda function.
package main
import (
"log"
"github.com/klass-lk/ginboot"
"github.com/klass-lk/ginboot/example/internal/controller"
)
func main() {
// Loads ginboot.yml / application.yml and .env automatically
server := ginboot.New()
cfg := server.Config()
server.SetBasePath(cfg.Ginboot.Server.BasePath)
server.RegisterController("/posts", controller.NewPostController(svc))
// Runs as HTTP locally, as an API Gateway proxy on AWS Lambda
if err := server.Start(cfg.Ginboot.Server.Port); err != nil {
log.Fatal(err)
}
}package controller
import "github.com/klass-lk/ginboot"
func (c *PostController) Register(group *ginboot.ControllerGroup) {
group.GET("/:id", c.GetPost)
protected := group.Group("")
protected.POST("", c.CreatePost)
}
// Handlers return (DTO, error) — Ginboot handles binding,
// status codes and JSON serialisation for you.
func (c *PostController) GetPost(ctx *ginboot.Context) (model.Post, error) {
return c.postService.GetPostById(ctx.Param("id"))
}
func (c *PostController) CreatePost(ctx *ginboot.Context, req model.Post) (model.Post, error) {
post, err := c.postService.CreatePost(req)
if err != nil {
return model.Post{}, ginboot.NewApiError(400, "invalid post")
}
// Fire-and-forget call to another service, traced end to end
_ = ctx.CallServiceAsync("notification-service", "/notifications/send", post)
return post, nil
}Familiar from either side
Ginboot adds structure without hiding Go. Nothing is generated and nothing is injected behind your back, so a stack trace leads to code you wrote.
It's still Gin underneath
server.Engine() returns the real *gin.Engine. Your Gin middleware and func(c *gin.Context) handlers work as they are, so you can adopt Ginboot one route group at a time and stop wherever you like.
Migrate from Gin, Echo, Chi or net/httpThe structure you know, in plain Go
Controllers, repositories and a YAML config file sit where you'd expect them. The wiring is ordinary Go: you call constructors, return errors and can read the whole app top to bottom in main.go.
Port an API from another languageBatteries included
Everything a production Go service needs is already there, and each piece is optional. Use what helps and keep writing plain Gin for the rest.
Database agnostic
Generic repositories for MongoDB, SQL and DynamoDB behind one interface. CRUD and pagination with almost no code.
Serverless ready
Detects AWS Lambda at runtime and proxies API Gateway requests automatically. The same controllers run either way, and Ginboot Cloud deploys them for you.
Built-in telemetry
An optional OpenTelemetry plugin ships traces, metrics and logs to Grafana or any OTLP backend, with trace-correlated logging.
Declarative config
ginboot.yml plus automatic .env loading and four env-var injection syntaxes. Read it back with a typed server.Config().
Service to service
ctx.CallService resolves targets from config, env or DNS and propagates W3C trace and auth headers for you.
BDD testing
Godog and Cucumber integration with built-in step definitions, so API behaviour is specified in plain Gherkin.
Push to GitHub. Run on AWS Lambda.
Your Ginboot service already runs on Lambda. Ginboot Cloud takes care of the rest: builds, environments, API Gateway and dashboards. You don't write any Terraform or YAML pipelines.
- Every push builds once. Test it in preprod, then promote that exact build to production
- Request, error and latency metrics from API Gateway and Lambda, with no instrumentation
- OpenTelemetry logs and traces from your Ginboot app, per request
- Deploy, promote and roll back from Claude Code, Cursor or VS Code over MCP
Runs in our AWS account. A monthly allowance is included, then you pay for usage.
Runs in your own account and AWS bills you directly. No Ginboot charges on traffic.
No AWS account needed to start. Connect yours whenever you like.