Echo middleware for serving OpenAPI/Swagger specifications and UI. Type-safe, zero-dependency (except for Echo and kin-openapi).
| Feature | Description |
|---|---|
| 🚀 Type-Safe | Uses *openapi3.T — no runtime parsing errors |
| 📄 YAML Auto-Serialization | Specs serialized to YAML automatically |
| 🎨 Swagger UI | Built-in UI with CDN (works offline with local assets) |
| ⚙️ Fully Configurable | Custom paths for UI and spec endpoints |
| 🛡️ Non-Mutating | Original spec object is never modified |
| 📝 HEAD Support | Correct Content-Length for HEAD requests |
| ✅ Well Tested | 100% test coverage with table-driven tests |
- Installation
- Quick Start
- API Reference
- Configuration
- Advanced Usage
- Important Notes
- Performance
- Requirements
- License
go get github.com/adlandh/echo-oapi-middleware/v2package main
import (
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v5"
echooapimiddleware "github.com/adlandh/echo-oapi-middleware/v2"
)
func main() {
e := echo.New()
spec := &openapi3.T{
OpenAPI: "3.0.3",
Info: &openapi3.Info{Title: "My API", Version: "1.0.0"},
Paths: &openapi3.Paths{},
}
// Serves YAML at GET /swagger.yaml
e.Use(echooapimiddleware.SwaggerYaml(spec))
e.GET("/api/users", func(c *echo.Context) error {
return c.JSON(200, []string{"user1", "user2"})
})
e.Start(":8080")
}func main() {
e := echo.New()
spec := &openapi3.T{
OpenAPI: "3.0.3",
Info: &openapi3.Info{Title: "Pet Store", Version: "1.0.0"},
Paths: &openapi3.Paths{},
}
// Serves UI at GET /swagger, /swagger/, /swagger/index.html
// Serves YAML at GET /swagger.yaml
e.Use(echooapimiddleware.SwaggerUI(spec))
e.Start(":8080")
}If you use oapi-codegen to generate your API:
import (
"github.com/labstack/echo/v5"
echooapimiddleware "github.com/adlandh/echo-oapi-middleware/v2"
"your-generated-api/pkg/api"
)
func main() {
e := echo.New()
spec, _ := api.GetSwagger() // From generated code
e.Use(echooapimiddleware.SwaggerUI(spec))
// ...
}Serves OpenAPI spec as YAML.
func SwaggerYaml(spec *openapi3.T) echo.MiddlewareFunc| Endpoint | Method | Content-Type |
|---|---|---|
/swagger.yaml |
GET, HEAD | text/yaml; charset=utf-8 |
Serves OpenAPI spec with custom configuration.
func SwaggerYamlWithConfig(spec *openapi3.T, cfg SwaggerYamlConfig) echo.MiddlewareFuncServes Swagger UI + YAML spec.
func SwaggerUI(spec *openapi3.T) echo.MiddlewareFunc| Endpoint | Method | Content-Type |
|---|---|---|
/swagger |
GET, HEAD | text/html; charset=utf-8 |
/swagger/ |
GET, HEAD | text/html; charset=utf-8 |
/swagger/index.html |
GET, HEAD | text/html; charset=utf-8 |
/swagger.yaml |
GET, HEAD | text/yaml; charset=utf-8 |
Serves Swagger UI with custom configuration.
func SwaggerUIWithConfig(spec *openapi3.T, cfg SwaggerUIConfig) echo.MiddlewareFunctype SwaggerYamlConfig struct {
// Path for YAML endpoint.
// Default: "/swagger.yaml"
Path string
// KeepServers preserves the servers field in output.
// Default: false (servers are stripped for CORS compatibility)
KeepServers bool
}type SwaggerUIConfig struct {
// Path for Swagger UI.
// Default: "/swagger"
Path string
// SpecPath for YAML endpoint.
// Default: "/swagger.yaml"
SpecPath string
// KeepServers preserves the servers field in output.
// Default: false
KeepServers bool
}e.Use(echooapimiddleware.SwaggerUIWithConfig(spec, echooapimiddleware.SwaggerUIConfig{
Path: "/api/docs", // UI at /api/docs
SpecPath: "/api/openapi.yaml", // YAML at /api/openapi.yaml
}))func main() {
e := echo.New()
// v1 API
e.Use(echooapimiddleware.SwaggerUIWithConfig(&openapi3.T{
OpenAPI: "3.0.3",
Info: &openapi3.Info{Title: "Pet Store", Version: "1.0.0"},
Paths: &openapi3.Paths{},
}, echooapimiddleware.SwaggerUIConfig{
Path: "/v1/docs",
SpecPath: "/v1/openapi.yaml",
}))
// v2 API
e.Use(echooapimiddleware.SwaggerUIWithConfig(&openapi3.T{
OpenAPI: "3.0.3",
Info: &openapi3.Info{Title: "Pet Store", Version: "2.0.0"},
Paths: &openapi3.Paths{},
}, echooapimiddleware.SwaggerUIConfig{
Path: "/v2/docs",
SpecPath: "/v2/openapi.yaml",
}))
e.Start(":8080")
}By default, servers are stripped from output (better CORS). To preserve:
e.Use(echooapimiddleware.SwaggerYamlWithConfig(spec, echooapimiddleware.SwaggerYamlConfig{
KeepServers: true,
}))import (
"io"
"os"
"gopkg.in/yaml.v3"
)
func main() {
f, _ := os.Open("openapi.yaml")
defer f.Close()
data, _ := io.ReadAll(f)
var spec openapi3.T
yaml.Unmarshal(data, &spec)
e.Use(echooapimiddleware.SwaggerUI(&spec))
}The middleware never modifies your spec object:
spec := &openapi3.T{
OpenAPI: "3.0.3",
Info: &openapi3.Info{Title: "API"},
Servers: openapi3.Servers{{URL: "https://api.example.com"}},
}
e.Use(echooapimiddleware.SwaggerYaml(spec))
// spec.Servers is STILL here - not mutated!
_ = spec.Servers // safe- GET — returns full response
- HEAD — returns headers only (Content-Length set correctly)
- Other methods pass through to next handler
Swagger UI loads from unpkg CDN:
https://unpkg.com/swagger-ui-dist@5/swagger-ui.csshttps://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js
For offline use, you'll need to serve these assets separately or use a local copy.
| Metric | Value |
|---|---|
| Serialization | Once at middleware creation |
| Request overhead | ~350ns |
| Memory per request | O(1) |
- Go 1.25+
- Echo v5 —
github.com/labstack/echo/v5 - kin-openapi —
github.com/getkin/kin-openapi
MIT — see LICENSE file.