Adding Swagger to Your Go API

Auto-generate OpenAPI docs from code annotations

Page content

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.

swagger api specs on agile board This nice image is generated by AI model Flux 1 dev.

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")

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 init takes 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:

  1. Disable in Production (if API is internal):
if os.Getenv("ENV") == "production" {
    // Don't register Swagger endpoint
    return
}
  1. Add Authentication:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. Rate Limit the Endpoint:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
  1. 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.

External Resources