API documentation is crucial for any modern application, and for Go APIs Swagger (OpenAPI) has become the industry standard.
For Go developers, swaggo provides an elegant solution to generate comprehensive API documentation directly from code annotations.
Why Swagger Matters for Go APIs
When building REST APIs, documentation often becomes outdated as code evolves. Swagger solves this by generating documentation from your source code, ensuring it stays synchronized with your implementation. The interactive Swagger UI allows developers to test endpoints directly from the browser, significantly improving the developer experience.
For teams building microservices or public APIs, Swagger documentation becomes essential for:
- Client Generation: Automatically create client libraries in multiple languages
- Contract Testing: Validate requests and responses against defined schemas
- Team Collaboration: Provide a single source of truth for API contracts
- Developer Onboarding: New team members can explore APIs interactively
Getting Started with swaggo
The swaggo library is the most popular tool for adding Swagger support to Go applications. It works by parsing special comments in your code and generating OpenAPI 3.0 specification files.
Installation
First, install the swag CLI tool:
go install github.com/swaggo/swag/cmd/swag@latest
Then add the appropriate Swagger middleware package for your framework. For Gin:
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files
For Echo:
go get -u github.com/swaggo/echo-swagger
For Fiber:
go get -u github.com/gofiber/swagger
Basic Configuration
Start by adding general API information in your main.go file. Similar to how you'd structure a REST API in Go, the annotations should be clear and descriptive:
// @title Product API
// @version 1.0
// @description A product management API with Swagger documentation
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /api/v1
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
func main() {
// Your application code
}
Implementation with Gin Framework
Let's implement a complete example using Gin. First, define your data models with struct tags:
type Product struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"Laptop" binding:"required"`
Description string `json:"description" example:"High-performance laptop"`
Price float64 `json:"price" example:"999.99" binding:"required,gt=0"`
Stock int `json:"stock" example:"50"`
}
type ErrorResponse struct {
Error string `json:"error" example:"Invalid input"`
Message string `json:"message" example:"Product name is required"`
}
Now annotate your handler functions. When working with database operations, these annotations help document the data flow:
// GetProduct godoc
// @Summary Get product by ID
// @Description Retrieve a single product by its unique identifier
// @Tags products
// @Accept json
// @Produce json
// @Param id path int true "Product ID"
// @Success 200 {object} Product
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Router /products/{id} [get]
func GetProduct(c *gin.Context) {
id := c.Param("id")
// Implementation here
c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}
// CreateProduct godoc
// @Summary Create a new product
// @Description Add a new product to the catalog
// @Tags products
// @Accept json
// @Produce json
// @Param product body Product true "Product object"
// @Success 201 {object} Product
// @Failure 400 {object} ErrorResponse
// @Security Bearer
// @Router /products [post]
func CreateProduct(c *gin.Context) {
var product Product
if err := c.ShouldBindJSON(&product); err != nil {
c.JSON(400, ErrorResponse{Error: "Bad Request", Message: err.Error()})
return
}
// Save to database
c.JSON(201, product)
}
Generating Documentation
After annotating your code, generate the Swagger documentation:
swag init
This creates a docs folder with swagger.json, swagger.yaml, and Go files. Import and register the Swagger endpoint:
package main
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
_ "yourproject/docs" // Import generated docs
)
func main() {
r := gin.Default()
// API routes
v1 := r.Group("/api/v1")
{
v1.GET("/products/:id", GetProduct)
v1.POST("/products", CreateProduct)
}
// Swagger endpoint
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
Now access your interactive API documentation at http://localhost:8080/swagger/index.html.
Implementation with Echo Framework
Echo users follow a similar pattern but with Echo-specific middleware:
package main
import (
"github.com/labstack/echo/v4"
echoSwagger "github.com/swaggo/echo-swagger"
_ "yourproject/docs"
)
func main() {
e := echo.New()
// API routes
api := e.Group("/api/v1")
api.GET("/products/:id", getProduct)
api.POST("/products", createProduct)
// Swagger endpoint
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.Start(":8080")
}
Implementation with Fiber Framework
Fiber's implementation is equally straightforward:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
_ "yourproject/docs"
)
func main() {
app := fiber.New()
// API routes
api := app.Group("/api/v1")
api.Get("/products/:id", getProduct)
api.Post("/products", createProduct)
// Swagger endpoint
app.Get("/swagger/*", swagger.HandlerDefault)
app.Listen(":8080")
}
Advanced Swagger Annotations
Documenting Complex Request Bodies
For nested structures or arrays:
type CreateOrderRequest struct {
CustomerID int `json:"customer_id" example:"123" binding:"required"`
Items []OrderItem `json:"items" binding:"required,min=1"`
ShippingAddress Address `json:"shipping_address" binding:"required"`
}
type OrderItem struct {
ProductID int `json:"product_id" example:"1" binding:"required"`
Quantity int `json:"quantity" example:"2" binding:"required,min=1"`
}
type Address struct {
Street string `json:"street" example:"123 Main St" binding:"required"`
City string `json:"city" example:"New York" binding:"required"`
ZipCode string `json:"zip_code" example:"10001" binding:"required"`
}
// CreateOrder godoc
// @Summary Create a new order
// @Description Create an order with multiple items and shipping information
// @Tags orders
// @Accept json
// @Produce json
// @Param order body CreateOrderRequest true "Order details"
// @Success 201 {object} Order
// @Failure 400 {object} ErrorResponse
// @Failure 422 {object} ErrorResponse
// @Security Bearer
// @Router /orders [post]
func CreateOrder(c *gin.Context) {
// Implementation
}
Documenting File Uploads
// UploadImage godoc
// @Summary Upload product image
// @Description Upload an image file for a product
// @Tags products
// @Accept multipart/form-data
// @Produce json
// @Param id path int true "Product ID"
// @Param file formData file true "Image file"
// @Success 200 {object} map[string]string
// @Failure 400 {object} ErrorResponse
// @Security Bearer
// @Router /products/{id}/image [post]
func UploadImage(c *gin.Context) {
file, _ := c.FormFile("file")
// Handle upload
}
Query Parameters and Pagination
// ListProducts godoc
// @Summary List products with pagination
// @Description Get paginated list of products with optional filtering
// @Tags products
// @Accept json
// @Produce json
// @Param page query int false "Page number" default(1)
// @Param page_size query int false "Items per page" default(10)
// @Param category query string false "Filter by category"
// @Param min_price query number false "Minimum price"
// @Param max_price query number false "Maximum price"
// @Success 200 {array} Product
// @Failure 400 {object} ErrorResponse
// @Router /products [get]
func ListProducts(c *gin.Context) {
// Implementation with pagination
}
Authentication and Security
Document different authentication methods in your API. For multi-tenant applications, proper authentication documentation is crucial:
Bearer Token Authentication
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
API Key Authentication
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description API key for authentication
OAuth2 Authentication
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
Basic Authentication
// @securityDefinitions.basic BasicAuth
Apply security to specific endpoints:
// @Security Bearer
// @Security ApiKeyAuth
Customizing Swagger UI
You can customize the Swagger UI appearance and behavior:
// Custom configuration
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
// With custom title
r.GET("/swagger/*any", ginSwagger.WrapHandler(
swaggerFiles.Handler,
ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
ginSwagger.DefaultModelsExpandDepth(-1),
))
To disable Swagger in production:
if os.Getenv("ENV") != "production" {
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
Integrating with CI/CD
Automate Swagger documentation generation in your CI/CD pipeline:
# GitHub Actions example
name: Generate Swagger Docs
on: [push]
jobs:
swagger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install swag
run: go install github.com/swaggo/swag/cmd/swag@latest
- name: Generate Swagger docs
run: swag init
- name: Commit docs
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add docs/
git commit -m "Update Swagger documentation" || exit 0
git push
Best Practices
1. Consistent Annotation Style
Maintain consistent formatting across all endpoints:
// HandlerName godoc
// @Summary Brief description (under 50 chars)
// @Description Detailed description of what the endpoint does
// @Tags resource-name
// @Accept json
// @Produce json
// @Param name location type required "description"
// @Success 200 {object} ResponseType
// @Failure 400 {object} ErrorResponse
// @Router /path [method]
2. Use Descriptive Examples
Add realistic examples to help API consumers:
type User struct {
ID int `json:"id" example:"1"`
Email string `json:"email" example:"user@example.com"`
CreatedAt time.Time `json:"created_at" example:"2025-01-15T10:30:00Z"`
}
3. Document All Response Codes
Include all possible HTTP status codes:
// @Success 200 {object} Product
// @Success 201 {object} Product
// @Failure 400 {object} ErrorResponse "Bad Request"
// @Failure 401 {object} ErrorResponse "Unauthorized"
// @Failure 403 {object} ErrorResponse "Forbidden"
// @Failure 404 {object} ErrorResponse "Not Found"
// @Failure 422 {object} ErrorResponse "Validation Error"
// @Failure 500 {object} ErrorResponse "Internal Server Error"
4. Version Your API
Use proper versioning in base path:
// @BasePath /api/v1
And organize your code accordingly:
v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")
5. Group Related Endpoints
Use tags to organize endpoints logically:
// @Tags products
// @Tags orders
// @Tags users
6. Keep Documentation Updated
Run swag init before every commit or integrate it into your build process:
#!/bin/bash
# pre-commit hook
swag init
git add docs/
Testing Swagger Documentation
When working with serverless architectures like AWS Lambda, testing your API documentation becomes even more important:
func TestSwaggerGeneration(t *testing.T) {
// Verify swagger.json exists
_, err := os.Stat("./docs/swagger.json")
if err != nil {
t.Fatal("swagger.json not found, run 'swag init'")
}
// Verify swagger endpoint responds
r := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
Common Issues and Solutions
Documentation Not Updating
If changes don't appear, ensure you're regenerating docs:
swag init --parseDependency --parseInternal
The --parseDependency flag parses external dependencies, and --parseInternal parses internal packages.
Custom Types Not Recognized
For types from external packages, use swaggertype tag:
type CustomTime struct {
time.Time
}
func (CustomTime) SwaggerDoc() map[string]string {
return map[string]string{
"time": "RFC3339 timestamp",
}
}
Or use swaggertype tag:
type Product struct {
ID int `json:"id"`
UpdatedAt CustomTime `json:"updated_at" swaggertype:"string" format:"date-time"`
}
Arrays and Enums
Document array types and enums:
type Filter struct {
Status []string `json:"status" enums:"active,inactive,pending"`
Tags []string `json:"tags"`
}
// @Param status query string false "Status filter" Enums(active, inactive, pending)
Alternative Approaches
While swaggo is the most popular choice, other options exist:
go-swagger
A more feature-rich but complex alternative:
brew install go-swagger
swagger generate spec -o ./swagger.json
Manual OpenAPI Files
For complete control, write OpenAPI specifications manually in YAML:
openapi: 3.0.0
info:
title: Product API
version: 1.0.0
paths:
/products:
get:
summary: List products
responses:
'200':
description: Success
Then serve with:
r.StaticFile("/openapi.yaml", "./openapi.yaml")
Integrating with AI and LLMs
When building APIs that integrate with AI services, proper documentation becomes crucial. For example, when working with structured LLM outputs, Swagger helps document complex request and response schemas:
type LLMRequest struct {
Prompt string `json:"prompt" example:"Summarize this text"`
Model string `json:"model" example:"qwen2.5:latest"`
Temperature float64 `json:"temperature" example:"0.7" minimum:"0" maximum:"2"`
MaxTokens int `json:"max_tokens" example:"1000" minimum:"1"`
Schema map[string]interface{} `json:"schema,omitempty"`
}
// GenerateStructured godoc
// @Summary Generate structured LLM output
// @Description Generate text with constrained output schema
// @Tags llm
// @Accept json
// @Produce json
// @Param request body LLMRequest true "LLM parameters"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} ErrorResponse
// @Router /llm/generate [post]
func GenerateStructured(c *gin.Context) {
// Implementation
}
Performance Considerations
Swagger documentation has minimal performance impact:
-
Build Time:
swag inittakes 1-3 seconds for most projects - Runtime: Documentation is loaded once at startup
- Memory: Typically adds 1-2MB to binary size
- Response Time: No impact on API endpoints themselves
For very large APIs (100+ endpoints), consider:
- Splitting into multiple Swagger files
- Lazy-loading Swagger UI assets
- Serving documentation from a separate service
Security Considerations
When exposing Swagger documentation:
- Disable in Production (if API is internal):
if os.Getenv("ENV") == "production" {
// Don't register Swagger endpoint
return
}
- Add Authentication:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
- Rate Limit the Endpoint:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
- Never Expose Internal Details:
- Don't document internal endpoints
- Avoid exposing database schemas directly
- Sanitize error messages in documentation
Conclusion
Adding Swagger documentation to your Go API transforms the developer experience from guesswork to guided exploration. The swaggo library makes this process straightforward by generating comprehensive OpenAPI documentation from your code annotations.
Key takeaways:
- Start with basic annotations and expand gradually
- Keep documentation synchronized with code through CI/CD
- Use Swagger UI for interactive testing during development
- Document authentication, errors, and edge cases thoroughly
- Consider security implications when exposing documentation
Whether you're building microservices, public APIs, or internal tools, Swagger documentation pays dividends in reduced support burden, faster onboarding, and better API design. The initial investment in learning annotation syntax quickly becomes routine, and automated generation ensures your documentation never falls behind your implementation.
For Go developers, the combination of strong typing, code generation, and swaggo's annotation system creates a powerful workflow that makes API documentation a natural part of the development process rather than an afterthought.
Useful links
- Go Cheatsheet
- Building REST APIs in Go
- Comparing Go ORMs for PostgreSQL: GORM vs Ent vs Bun vs sqlc
- Multi-Tenancy Database Patterns with examples in Go
- LLMs with Structured Output: Ollama, Qwen3 & Python or Go
- AWS lambda performance: JavaScript vs Python vs Golang
Top comments (0)