goas

package module
v0.1.9 Latest Latest
Warning

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

Go to latest
Published: May 22, 2026 License: MIT Imports: 14 Imported by: 0

README

Goas

CI

Auto-generate OpenAPI 3.x from your Go route registrations.

The goal is to keep your routing code clean (plain GET/POST/PUT/PATCH/DELETE) while still producing a good OpenAPI spec + Swagger UI.


Background and motivation

Creating OpenAPI (OpenAPI 3.x) documentation for Go projects is often tedious and error-prone. Most common workflows require hand-maintaining large YAML or JSON files that declare the entire API surface — types, request/response schemas, parameters, security schemes, and more. For medium to large APIs this quickly becomes unmanageable: teams may end up with thousands of lines of YAML (10k+ lines is not unusual) that must be edited and kept in sync with code changes.

Every change to a handler, request/response type, or parameter often means manually editing the documentation files. This duplication increases the risk of inconsistencies, stale docs, and significant maintenance overhead. Compared to frameworks like Spring Boot or FastAPI — which offer more integrated or declarative approaches for keeping API docs close to code — the Go ecosystem has historically lacked a lightweight, ergonomic solution for automatic OpenAPI generation.

Goas was created to bridge that gap. Instead of writing a YAML entry for every endpoint, Goas captures route registrations and a small, config-first specification to generate a complete OpenAPI document and Swagger UI automatically. The goals are:

  • Eliminate the need to maintain huge, hand-written OpenAPI YAML files.
  • Keep handlers idiomatic and minimal while centralizing schema metadata in a compact config.
  • Reduce duplication and human error by generating docs from the same source of truth as your routes.
  • Provide practical features teams need (multipart uploads, security schemes, grouped tags) so large APIs remain maintainable and well-documented.

This README continues with the features you get and examples on how to use the library.


What you get

  • GET /openapi.json (generated OpenAPI document)
  • Swagger UI mounted at:
    • http://localhost:8080/swagger-ui/index.html#/
    • /swagger is kept as a legacy redirect

Key concepts

1) Base router (net/http)

Use the built-in router:

  • goas.New(...) → returns an http.Handler (lightweight net/http-backed router)
  • register routes with GET/POST/PUT/PATCH/DELETE
  • call Docs() once after registering your routes to mount /openapi.json and Swagger UI

Note: the default router implementation used to be chi-backed; it now uses a small net/http-based mux compatible with the project's needs. Adapters for Gin, Echo and Fiber remain available.

2) Config-first spec (SpringBoot-like)

Go handlers don’t expose schema information automatically. So Goas uses a config-first approach:

  • put route schemas/tags/security/query/header params in one place using spec
  • keep your handlers clean and readable
3) Multipart upload support

Use MultipartUpload(...) to get multipart/form-data request bodies and a file upload field in Swagger UI.


Installation

go get github.com/yzidev/goas@latest

Minimal example (net/http)

package main

import (
	"net/http"

	"github.com/yzidev/goas"
)

type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	r := goas.New(goas.Config{Title: "User API", Version: "1.0.0"})

	r.GET("/users", func(w http.ResponseWriter, _ *http.Request) {
		goas.JSON(w, http.StatusOK, []User{{ID: "1", Name: "Alice"}})
	}, goas.Res([]User{}), goas.Tags("Users"))

	r.Docs()
	_ = http.ListenAndServe(":8080", r)
}

Prefer grouped config instead of per-route options? spec is still available as an advanced config-first layer:

b := spec.New()
b.GroupTags("", []string{"Users"}, func(s *spec.SpecBuilder) {
	s.GET("/users").Res([]User{}).OK()
})

base := goas.New(goas.Config{Title: "User API", Version: "1.0.0"})
r := spec.HTTP(base, b.Spec())
r.GET("/users", listUsers)
base.Docs()

Springdoc-like mode

For Gin, Echo, and Fiber, you can register routes with the framework directly and add Goas with one docs call. Goas discovers method/path information from the framework router and mounts Swagger UI.

engine := gin.Default()

engine.GET("/users/:id", getUser)
engine.POST("/users", createUser)

ginadapter.Docs(engine, goas.Config{
	Title:   "User API",
	Version: "1.0.0",
})

_ = engine.Run(":8080")

This zero-config mode documents paths, methods, path params, and default responses. Go handlers do not expose request/response body types at runtime, so body schemas are intentionally opt-in:

r := ginadapter.Wrap(engine)
r.POST("/users", createUser,
	ginadapter.Req(CreateUser{}),
	ginadapter.Res(User{}),
	ginadapter.Created(),
)
r.Docs(goas.Config{Title: "User API", Version: "1.0.0"})

For the built-in net/http router or muxadapter, use goas.New(...) or muxadapter.Mount(...):

mux := http.NewServeMux()
r := muxadapter.Mount(mux, goas.Config{
	Title:   "User API",
	Version: "1.0.0",
})

r.GET("/users/{id}", getUser, goas.Res(User{}), goas.Tags("Users"))

_ = http.ListenAndServe(":8080", mux)

Note: the standard http.ServeMux does not expose registered route metadata, so Goas can auto-document routes registered through goas.Router or muxadapter.Router, but not arbitrary handlers registered directly on http.ServeMux.


Multipart upload example

On a route:

r.POST("/users/upload", uploadUserFile,
	goas.MultipartUpload(
		"file",
		goas.MultipartField{Name: "note", Type: goas.ParamString},
	),
	goas.Res(map[string]string{}),
)

In spec, the equivalent is:

s.POST("/users/upload").MultipartUpload(
	"file",
	goas.MultipartField{Name: "note", Type: goas.ParamString},
).Res(map[string]string{}).OK()

In Swagger UI this will show:

  • file as file chooser
  • note as text input
  • requestBody content type: multipart/form-data

Security

You can provide security schemes via goas.Config.SecuritySchemes. For Springdoc-like auto-docs, set goas.Config.Security as a global requirement. For route-specific security, attach requirements per route with goas.Security(...) or adapter aliases like ginadapter.Security(...). Examples include two schemes:

  • Bearer JWT (Authorization: Bearer <token>)
  • API key (X-API-Key: <key>)

Run examples and open Swagger UI:

Default (net/http)
  • Docs: HTTPROUTER.md (See the doc above for run commands, endpoints, security, and upload sample.)
Gin
  • Docs: GIN.md (See the doc above for run commands, endpoints, security, and upload sample.)
Echo
  • Docs: ECHO.md (See the doc above for run commands, endpoints, security, and upload sample.)
Fiber
  • Docs: FIBER.md (See the doc above for run commands, endpoints, security, and upload sample.)

Current support (today)

Goas is currently focused on 4 frameworks/router setups:

  1. net/http (built-in goas.Router)
  2. Gin
  3. Echo
  4. Fiber

Notes:

  • Other frameworks may be added later, but the repo intentionally stays small and dependency-light.
  • Adapters are provided as packages under adapters/* so you can use them when needed. They are compiled by default and no special build tags are required to use them. If you prefer to keep adapter dependencies optional for your project, consider shipping adapters as separate modules (e.g. github.com/yzidev/goas-adapter-gin) so downstream projects opt-in.

Roadmap / future updates

The direction going forward:

  • Keep the public API simple:

    • common HTTP methods only: GET/POST/PUT/PATCH/DELETE
    • grouping via Group(...)
    • OpenAPI metadata via route options or config-first spec (spec)
  • Improve schema inference gradually:

    • better tag support (omitempty, pointer handling)
    • better nested struct handling
    • better multipart documentation
  • Better DX in Swagger UI:

    • theming improvements
    • cleaner auth UX
    • consistent error schemas
  • Adapter expansion (optional):

    • If more frameworks are added, they will follow the same pattern:
      • keep handlers/framework usage idiomatic
      • keep Goas integration minimal
      • keep core library independent of adapter dependencies
Update policy / compatibility
  • The project is evolving quickly.
  • We aim to keep the core API stable (goas.New, goas.Router, goas.Register, and spec).
  • Adapter APIs may change as we simplify integration and keep parity across frameworks.
Framework support timeline

For now Goas only ships examples + adapters for:

  • net/http (built-in router)
  • Gin
  • Echo
  • Fiber

Additional frameworks are considered future work (optional adapters behind build tags).

How to add another framework (adapter concept)

If you want to support another framework, the recommended approach is:

  • Create a new adapter package under adapters/<framework>.
  • Guard it with a build tag (so the dependency stays optional).
  • The adapter should expose a router wrapper similar to the existing ones:
    • register GET/POST/PUT/PATCH/DELETE
    • keep grouping if the framework supports groups
    • call goas.Router.Handle(...) / attach HandlerOptions in the same way.

For a starting point, check:

  • adapters/gin
  • adapters/echo
  • adapters/fiber

Adapters (how to use with frameworks)

Goas provides lightweight adapters for multiple frameworks so you can keep your handler code clean while still generating OpenAPI and mounting Swagger UI.

Pattern (recommended):

  1. Create your framework engine/app (e.g., gin, echo, fiber).
  2. For zero-config route discovery, call ginadapter.Docs(engine, cfg), echoadapter.Docs(e, cfg), or fiberadapter.Docs(app, cfg). For net/http, use goas.New(...) or muxadapter.Mount(...).
  3. If you want body schemas/tags/security per route, wrap with Wrap/New and register routes with short options like Res, Req, Tags, and Created.
  4. Run the engine/app.

Examples:

  • Gin
import (
    ginlib "github.com/gin-gonic/gin"
    "github.com/yzidev/goas"
    "github.com/yzidev/goas/adapters/ginadapter"
)

engine := ginlib.New()
engine.GET("/users", listUsers)
ginadapter.Docs(engine, goas.Config{Title: "My API", Version: "0.1.0"})
engine.Run(":8080")
  • Echo
import (
    echolib "github.com/labstack/echo/v4"
    "github.com/yzidev/goas"
    "github.com/yzidev/goas/adapters/echoadapter"
)

base := echolib.New()
base.GET("/users", listUsers)
echoadapter.Docs(base, goas.Config{Title: "My API", Version: "0.1.0"})
base.Start(":8080")
  • Fiber
import (
    fiberlib "github.com/gofiber/fiber/v2"
    "github.com/yzidev/goas"
    "github.com/yzidev/goas/adapters/fiberadapter"
)

app := fiberlib.New()
app.Get("/users", listUsers)
fiberadapter.Docs(app, goas.Config{Title: "My API", Version: "0.1.0"})
app.Listen(":8080")

Notes:

  • The Docs helpers discover native framework routes. The Wrap helpers add richer per-route metadata when you need schemas, tags, security, or custom responses.
  • If you previously built with -tags, adapters are now compiled by default — no need to use build tags to get adapter implementations.

License

MIT. See LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bind

func Bind(r *http.Request, v interface{}) error

func BuildSpec

func BuildSpec(routes []RouteMeta, cfg Config) *openapi3.T

BuildSpec builds an OpenAPI document from captured routes and config.

func JSON

func JSON(w http.ResponseWriter, code int, v interface{})

func MultipartSchema

func MultipartSchema(fileField string, fields ...MultipartField) *openapi3.SchemaRef

MultipartSchema returns a schema sample that produces a multipart/form-data request body.

This returns an *openapi3.SchemaRef directly so we can accurately represent: - file as type=string, format=binary - primitive fields as string/integer/number/boolean - required fields

It is designed for use with the oas builder:

s.POST("/upload").Req(goas.MultipartSchema("file", goas.MultipartField{Name: "note"})).Res(...)

func PathValue

func PathValue(r *http.Request, key string) string

func QueryValue

func QueryValue[T any](r *http.Request, name string) (T, bool, error)

QueryValue reads a query parameter from URL and parses it into the requested type.

func QueryValues

func QueryValues[T any](r *http.Request, name string) ([]T, bool, error)

QueryValues reads repeated query params (?id=1&id=2) and parses into []T.

func Register

func Register(r *Router, cfg Config)

Types

type Config

type Config struct {
	Title           string
	Version         string
	Description     string
	SecuritySchemes map[string]*SecuritySchemeRef
	Security        SecurityRequirements
	Tags            DocumentTags
	SpecPath        string
	SwaggerPath     string

	// Schemas registers component schemas by name without attaching them to a route.
	// Useful when you want config-only schema registration.
	Schemas SchemaRegistry

	// DefaultErrorResponses controls which standard error responses are automatically
	// added to every operation (if not already declared).
	//
	// If nil, a sensible default set is used.
	// If empty (len==0), automatic error responses are disabled.
	DefaultErrorResponses []int

	// DefaultErrorSchema is the schema used for DefaultErrorResponses.
	// If nil, goas.ErrorResponse{} is used.
	DefaultErrorSchema any
}

type DocumentTags added in v0.1.9

type DocumentTags = openapi3.Tags

Re-export common OpenAPI config types so applications can configure Goas without importing the underlying kin-openapi package directly.

type ErrorResponse

type ErrorResponse struct {
	Error string `json:"error"`
}

ErrorResponse is the default error schema used when you don't provide explicit response specs via WithResponses.

You can override/replace this by declaring your own responses with WithResponses(...) on a route.

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group allows applying shared options (e.g., WithTags) and a common path prefix to multiple routes.

Example:

api := r.Group("", WithTags("Users"))
api.GET("/users", ...)

Options provided to the group are applied to every route in that group, and can be overridden/extended by per-route options.

Path joining is kept oas and consistent with common router behavior. We avoid cleaning ":" or "{}" segments; NormalizePath handles OpenAPI normalization.

func NewGroup

func NewGroup(prefix string, opts []HandlerOption, route func(method, path string, h http.HandlerFunc, opts ...HandlerOption)) *Group

NewGroup constructs a Group with the provided prefix, options and a custom route function. Useful for adapters or wrappers that need the group's routing to call a specific handler registration function (for examples, oas.Router wants to ensure route registrations go through its Handle method so spec injection happens correctly).

func (*Group) DELETE

func (g *Group) DELETE(p string, h http.HandlerFunc, opts ...HandlerOption)

func (*Group) GET

func (g *Group) GET(p string, h http.HandlerFunc, opts ...HandlerOption)

func (*Group) Handle

func (g *Group) Handle(method, p string, h http.HandlerFunc, opts ...HandlerOption)

func (*Group) PATCH

func (g *Group) PATCH(p string, h http.HandlerFunc, opts ...HandlerOption)

func (*Group) POST

func (g *Group) POST(p string, h http.HandlerFunc, opts ...HandlerOption)

func (*Group) PUT

func (g *Group) PUT(p string, h http.HandlerFunc, opts ...HandlerOption)

type HandlerOption

type HandlerOption func(*RouteMeta)

HandlerOption configures RouteMeta.

func Created

func Created(schema ...any) HandlerOption

Created declares a 201 Created response for a route.

func Headers

func Headers(params ...HeaderParam) HandlerOption

Headers declares header parameters for a route.

func JSONRoute

func JSONRoute(reqSchema any, resSchema any, successStatus int) []HandlerOption

JSONRoute JSONRouteSpec is a convenience for common JSON APIs. It wires request/response schemas + a primary success status code.

You still can override everything by passing explicit options.

Typical usage from adapters:

r.POST("/users", h, goas.JSONRoute(CreateUser{}, struct{}{}, http.StatusCreated)...)

func MergeOptionSlices

func MergeOptionSlices(slices ...[]HandlerOption) []HandlerOption

MergeOptionSlices merges multiple slices, returning a new slice.

func MergeOptions

func MergeOptions(base []HandlerOption, add ...HandlerOption) []HandlerOption

MergeOptions merges a base option slice with additional options, returning a new slice (doesn't mutate base).

func MultipartUpload

func MultipartUpload(fileField string, fields ...MultipartField) HandlerOption

MultipartUpload declares a multipart/form-data request body with a file field.

func NoContent

func NoContent() HandlerOption

NoContent declares a 204 No Content response for a route.

func PathParam

func PathParam(name string, typ ParamType, required bool, description string) HandlerOption

PathParam declares a typed path parameter for a route.

func Query

func Query(params ...QueryParam) HandlerOption

Query declares query parameters for a route.

func Req

func Req(schema any) HandlerOption

Req declares a JSON request schema for a route.

func Res

func Res(schema any) HandlerOption

Res declares the default success response schema for a route.

func Responses

func Responses(responses ...ResponseSpec) HandlerOption

Responses declares one or more OpenAPI responses for a route.

func Security

func Security(security *SecurityRequirement) HandlerOption

Security declares route-level OpenAPI security requirements.

func Status

func Status(status int, schema ...any) HandlerOption

Status declares a primary response status for a route. When schema is omitted, the route's Res schema is reused if it was set earlier.

func Tags

func Tags(tags ...string) HandlerOption

Tags assigns OpenAPI tags to a route.

func WithHeaderParams

func WithHeaderParams(params ...HeaderParam) HandlerOption

WithHeaderParams declares header parameters for a route for OpenAPI generation.

func WithPathParam

func WithPathParam(name string, typ ParamType, required bool, description string) HandlerOption

WithPathParam declares a typed path parameter (name + primitive type) for OpenAPI generation.

func WithQueryParams

func WithQueryParams(params ...QueryParam) HandlerOption

WithQueryParams declares query parameters for a route for OpenAPI generation.

func WithRequestSchema

func WithRequestSchema(schema interface{}) HandlerOption

func WithResponseSchema

func WithResponseSchema(schema interface{}) HandlerOption

func WithResponses

func WithResponses(responses ...ResponseSpec) HandlerOption

WithResponses adds/overrides response entries for a route.

func WithSecurity

func WithSecurity(security *openapi3.SecurityRequirement) HandlerOption

func WithTags

func WithTags(tags ...string) HandlerOption

type HeaderParam

type HeaderParam struct {
	Name        string
	Type        ParamType
	Required    bool
	Description string
}

HeaderParam describes a header parameter for OpenAPI generation.

type MultipartField

type MultipartField struct {
	Name        string
	Type        ParamType
	Required    bool
	Description string
}

MultipartField defines an extra multipart/form-data field (besides the file).

Type controls the primitive type for that field in the OpenAPI schema. If omitted/unknown, it defaults to string.

NOTE: multipart/form-data fields are represented as properties on an object schema.

type MultipartFile

type MultipartFile struct{}

MultipartFile is a marker type for OpenAPI generation.

Use it inside a request schema struct to document multipart/form-data uploads. Example:

type UploadReq struct {
	File MultipartFile `json:"file"`
	Note string       `json:"note"`
}

When used with WithRequestSchema(UploadReq{}), the builder will render the request body as multipart/form-data with a binary file part.

At runtime you still parse files using your framework (r.FormFile, c.FormFile, etc). This type is only for spec generation.

type ParamType

type ParamType string

ParamType represents a primitive type used for path/query parameters.

const (
	ParamString  ParamType = "string"
	ParamInteger ParamType = "integer"
	ParamNumber  ParamType = "number"
	ParamBoolean ParamType = "boolean"
)

type PathParamSpec

type PathParamSpec struct {
	Name        string
	Type        ParamType
	Required    bool
	Description string
}

type QueryParam

type QueryParam struct {
	Name        string
	Type        ParamType
	Required    bool
	Description string
}

QueryParam describes a query parameter for OpenAPI generation.

type Registry

type Registry struct{}

Registry is reserved for future: merge routes across multiple routers/adapters. Today, Router stores routes in-memory and BuildSpec consumes them.

type ResponseSpec

type ResponseSpec struct {
	Status      int
	Description string
	Schema      interface{}
}

ResponseSpec declares an OpenAPI response for a specific status code.

Schema can be nil to indicate an empty body. Description is optional; if empty we'll use http.StatusText(code) where possible.

Example:

WithResponses(
  ResponseSpec{Status: 200, Schema: User{}, Description: "OK"},
  ResponseSpec{Status: 401, Schema: ErrorResponse{}, Description: "Unauthorized"},
)

type RouteMeta

type RouteMeta struct {
	Method         string
	Path           string
	Handler        http.HandlerFunc
	Summary        string
	Description    string
	Tags           []string
	RequestSchema  interface{}
	ResponseSchema interface{}
	Responses      []ResponseSpec
	Security       *openapi3.SecurityRequirement
	QueryParams    []QueryParam
	HeaderParams   []HeaderParam
	PathParams     []PathParamSpec
}

type Router

type Router struct {
	Mux *httpMux
	// contains filtered or unexported fields
}

Router uses a small net/http-backed mux that understands oas path params of the form /users/{id}. This avoids depending on chi while preserving behavior needed by the OpenAPI builder (path param extraction via context).

func New

func New(cfg ...Config) *Router

New creates an OpenAPI-aware net/http router.

Typical usage:

r := goas.New(goas.Config{Title: "User API", Version: "1.0.0"})
r.GET("/users", listUsers, goas.Res([]User{}), goas.Tags("Users"))
r.Docs()

func NewRouter

func NewRouter() *Router

func (*Router) DELETE

func (r *Router) DELETE(path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) Docs

func (r *Router) Docs(cfg ...Config)

Docs registers the OpenAPI JSON endpoint and Swagger UI using the router config. Pass a Config here to override or set the config after New().

func (*Router) GET

func (r *Router) GET(path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) Get

func (r *Router) Get(path string, h http.HandlerFunc)

Get registers a plain GET handler (used by Swagger UI mount helpers).

func (*Router) Group

func (r *Router) Group(prefix string, opts ...HandlerOption) *Group

func (*Router) HEAD

func (r *Router) HEAD(path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) Handle

func (r *Router) Handle(method, path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) OPTIONS

func (r *Router) OPTIONS(path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) PATCH

func (r *Router) PATCH(path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) POST

func (r *Router) POST(path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) PUT

func (r *Router) PUT(path string, h http.HandlerFunc, opts ...HandlerOption)

func (*Router) Routes

func (r *Router) Routes() []RouteMeta

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

type SchemaRegistry

type SchemaRegistry map[string]any

SchemaRegistry allows registering types that should appear in OpenAPI components/schemas without having to attach them to a specific route.

This is the closest Go equivalent of “Spring Boot config-only” schema registration. It does NOT automatically infer which route uses which schema; it only ensures schemas exist in components for references and tooling.

If you want per-route request/response schemas, keep using JSONRoute(...) or WithRequestSchema/WithResponseSchema on that route.

type SecurityRequirement

type SecurityRequirement = openapi3.SecurityRequirement

Re-export common OpenAPI config types so applications can configure Goas without importing the underlying kin-openapi package directly.

func NewSecurityRequirement added in v0.1.9

func NewSecurityRequirement() SecurityRequirement

type SecurityRequirements added in v0.1.9

type SecurityRequirements = openapi3.SecurityRequirements

Re-export common OpenAPI config types so applications can configure Goas without importing the underlying kin-openapi package directly.

type SecurityScheme added in v0.1.9

type SecurityScheme = openapi3.SecurityScheme

Re-export common OpenAPI config types so applications can configure Goas without importing the underlying kin-openapi package directly.

type SecuritySchemeRef added in v0.1.9

type SecuritySchemeRef = openapi3.SecuritySchemeRef

Re-export common OpenAPI config types so applications can configure Goas without importing the underlying kin-openapi package directly.

type Tag added in v0.1.9

type Tag = openapi3.Tag

Re-export common OpenAPI config types so applications can configure Goas without importing the underlying kin-openapi package directly.

Directories

Path Synopsis
adapters
examples
httprouter command
Package oas is the legacy import path for the config-first OpenAPI builder.
Package oas is the legacy import path for the config-first OpenAPI builder.
Package spec provides the config-first OpenAPI route specification builder.
Package spec provides the config-first OpenAPI route specification builder.

Jump to

Keyboard shortcuts

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