Controller-based REST API server container for Golang with Postgres support
- Uses GORM v1.21.13 for DB interaction with Postgres
- Uses Gorilla Mux v1.8.0 for routing
- Uses logmatic v0.4.0 for nice server logs
- Optional OpenAPI 3.0 spec + Swagger UI generated from registered routes
- Optional MCP (Model Context Protocol) endpoint exposing routes as tools
- Default port is 8080
- Default base path is /api
Documentation here: https://godoc.org/github.com/mborders/requiem
s := requiem.NewServer(... controllers)
s.Start()s := requiem.NewServer(... controllers)
s.EnableDB = true
s.Start()s := requiem.NewServer(... controllers)
s.Port = 9090
s.BasePath = "/rest"
s.Start()type MyController struct {
DB *gorm.DB
}
type Response struct {
Message string
}
type CreateRequest struct {
SomeValue string
}
func (c MyController) getStuff(ctx requiem.HTTPContext) {
m := Response{Message: "Hello, world!"}
ctx.SendJSON(res)
}
func (c MyController) createStuff(ctx requiem.HTTPContext) {
m := ctx.Body.(*CreateRequest)
fmt.Println("Value: %s", m.SomeValue)
ctx.SendStatus(Http.StatusNoContent)
}
func AuthInterceptor(ctx HTTPContext) bool {
// Example:
// 1) Check if user is authenticated
// 2) Return false if not
// 3) Use ctx.SetAttribute() to pass user claim
return true
}
func (c MyController) Load(router *requiem.Router) {
c.DB = router.DB
r := router.NewRestRouter("/stuff")
r.Get("/", c.getStuff)
r.Post("/", c.createStuff)
// Use AuthInterceptor
r.Get("/interceptor", func(ctx HTTPContext) {
ctx.SendStatus(http.StatusOK)
}, AuthInterceptor)
}Enable an auto-generated OpenAPI 3.0 spec and Swagger UI page:
s := requiem.NewServer(controllers...)
s.UseOpenAPI(requiem.OpenAPIConfig{
Title: "My API",
Version: "1.0.0",
Servers: []string{"/api"},
})
s.Start()This mounts:
GET /api/openapi.json— the OpenAPI 3.0 document (JSON)GET /api/docs— Swagger UI rendered against the spec
Paths, methods, and request body schemas are derived automatically from your route registrations. Attach richer metadata by chaining off the returned *Route:
func (c MyController) Load(router *requiem.Router) {
r := router.NewRestRouter("/stuff")
r.Get("/", c.list).
Summary("List stuff").
Tags("stuff").
Query("limit", "integer", false, "Max items").
Returns(200, []Stuff{}, "List of stuff")
r.Post("/", c.create, CreateStuff{}).
Summary("Create stuff").
Tags("stuff").
Returns(201, Stuff{}, "Created").
Returns(400, ErrorResponse{}, "Invalid input")
r.Get("/{id}", c.getOne).
Summary("Get one").
Param("id", "string", "Stuff identifier").
Returns(200, Stuff{}, "OK").
Returns(404, ErrorResponse{}, "Not found")
r.Delete("/internal", c.purge, nil).
ExcludeFromSpec()
}Request body schemas come from the struct passed to Post/Put/Delete (honors json:"..." and validate:"required,min=,max=,oneof=" tags). Response schemas come from the value passed to Returns(...). Struct types are emitted under components.schemas and referenced via $ref.
OpenAPIConfig defaults:
SpecPathdefaults to/openapi.jsonDocsPathdefaults to/docs(set to"-"to disable the Swagger UI page)
Expose your existing REST routes as MCP tools so LLM clients can discover and call them. Like the OpenAPI support, it is a purely optional addon — nothing changes unless you enable it:
s := requiem.NewServer(controllers...)
s.UseMCP(requiem.MCPConfig{
Name: "My API",
Version: "1.0.0",
})
s.Start()This mounts a single JSON-RPC 2.0 endpoint (POST /api/mcp by default) implementing the MCP initialize, tools/list, and tools/call methods. Every registered route becomes a tool automatically:
- Tool names are derived from method + path (e.g.
GET /widgets/{id}→get_widgets_id). - Input schemas are built from the route's path params, query params, and request body struct (reusing the same schema generation as OpenAPI). The request body is nested under a
bodyproperty. - Descriptions come from
Summary/Descriptionmetadata.
Tool calls are re-dispatched in-process through the same router, so request body validation, interceptors (auth, etc.), and handlers all run exactly as they do for a real HTTP request. Headers from the MCP request are forwarded onto the call so auth interceptors keep working.
Opt individual routes out, or override a tool name:
r.Delete("/internal", c.purge, nil).ExcludeFromMCP()
r.Get("/{id}", c.getOne).
Summary("Get one").
MCPTool("fetch_widget")MCPConfig defaults:
Pathdefaults to the API's common route prefix plus/mcpInstructionsis optional guidance returned duringinitializeGuard, when set, is called with each exposed route's method and path as the endpoint is mounted; returning an error panics at startup. Use it to enforce a rule such as "only GET routes are tools":
s.UseMCP(requiem.MCPConfig{
OptIn: true,
Guard: func(method, path string) error {
if method != http.MethodGet {
return fmt.Errorf("only GET routes may be MCP tools")
}
return nil
},
})UseMCP returns a handle for code that runs its own agent loop inside the service. It lists and calls the same tools without an HTTP round trip, as the caller whose credentials are on the given request: Authenticate runs first, Tools applies each route's Authorize filter, and Call only runs tools that caller can see.
tools := s.UseMCP(requiem.MCPConfig{OptIn: true, Authenticate: auth})
// later, inside a handler
list, err := tools.Tools(ctx.Request)
res, err := tools.Call(ctx.Request, "fetch_widget", map[string]interface{}{"id": "42"})
// res.Text is the route's response body; res.IsError is true for a 4xx/5xx statusDB_HOST
DB_PORT
DB_NAME
DB_USERNAME
DB_PASSWORD
