Go API에 Swagger 추가하기
코드 애노테이션에서 OpenAPI 문서를 자동으로 생성
모던 애플리케이션에서 API 문서화는 매우 중요합니다. Go API의 Swagger(OpenAPI)는 현재 업계 표준이 되었습니다. Go 개발자에게 swaggo는 코드 주석에서 포괄적인 API 문서를 직접 생성하는 우아한 해결책을 제공합니다.
이 아름다운 이미지는 AI 모델 Flux 1 dev로 생성되었습니다.
Go API에서 Swagger가 중요한 이유
REST API를 구축할 때, 코드가 진화함에 따라 문서가 종종 오래된 상태로 남아있게 됩니다. Swagger는 소스 코드에서 문서를 생성함으로써 이 문제를 해결하며, 문서가 구현과 항상 동기화되도록 보장합니다. 인터랙티브 Swagger UI는 개발자가 브라우저에서 엔드포인트를 직접 테스트할 수 있게 하여 개발자 경험을 크게 향상시킵니다.
마이크로서비스나 공개 API를 구축하는 팀들에게 Swagger 문서는 다음 사항에서 필수적입니다:
- 클라이언트 생성: 여러 언어로 클라이언트 라이브러리를 자동으로 생성합니다
- 계약 테스트: 정의된 스키마에 대해 요청과 응답을 검증합니다
- 팀 협업: API 계약에 대한 단일 정보 원본을 제공합니다
- 개발자 온보딩: 새로운 팀원이 API를 인터랙티브하게 탐색할 수 있습니다
swaggo로 시작하기
swaggo 라이브러리는 Go 애플리케이션에 Swagger 지원을 추가하는 가장 인기 있는 도구입니다. 이 도구는 코드 내의 특수 주석을 파싱하여 OpenAPI 3.0 사양 파일을 생성하는 방식으로 작동합니다.
설치
먼저, swag CLI 도구를 설치합니다:
go install github.com/swaggo/swag/cmd/swag@latest
그 다음 프레임워크에 맞는 Swagger 미들웨어 패키지를 추가합니다. Gin의 경우:
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files
Echo의 경우:
go get -u github.com/swaggo/echo-swagger
Fiber의 경우:
go get -u github.com/gofiber/swagger
기본 설정
main.go 파일에 전체적인 API 정보를 추가하여 시작합니다. Go에서의 REST API 구현에서 구조를 잡는 방식과 유사하게, 주석은 명확하고 설명적으로 작성되어야 합니다:
// @title Product API
// @version 1.0
// @description Swagger 문서화를 갖춘 제품 관리 API
// @termsOfService http://swagger.io/terms/
// @contact.name API 지원
// @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 "Bearer" 후 공백과 JWT 토큰을 입력하세요.
func main() {
// 애플리케이션 코드
}
Gin 프레임워크로 구현
Gin을 사용하여 완전한 예제를 구현해 보겠습니다. 먼저, 구조체 태그를 사용하여 데이터 모델을 정의합니다:```go
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"
}
이제 핸들러 함수에 주석을 추가합니다. [데이터베이스 연산](https://www.glukhov.org/ko/app-architecture/data-access/comparing-go-orms-gorm-ent-bun-sqlc/ "PostgreSQL을 위한 Go ORM 비교: GORM vs Ent vs Bun vs sqlc - 코드 예제 포함")을 다룰 때, 이러한 주석은 데이터 흐름을 문서화하는 데 도움이 됩니다:
```go
// GetProduct godoc
// @Summary ID로 제품 가져오기
// @Description 고유 식별자로 단일 제품 검색
// @Tags products
// @Accept json
// @Produce json
// @Param id path int true "제품 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")
// 구현 부분
c.JSON(200, Product{ID: 1, Name: "Laptop", Price: 999.99})
}
// CreateProduct godoc
// @Summary 새 제품 생성
// @Description 카탈로그에 새 제품 추가
// @Tags products
// @Accept json
// @Produce json
// @Param product body Product true "제품 객체"
// @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
}
// 데이터베이스에 저장
c.JSON(201, product)
}
문서 생성
코드에 주석을 추가한 후, Swagger 문서를 생성합니다:
swag init
이 과정을 통해 docs 폴더가 생성되며 swagger.json, swagger.yaml 및 Go 파일들이 포함되어 있습니다. Swagger 엔드포인트를 가져오고 등록합니다:
package main
import (
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
_ "yourproject/docs" // 생성된 문서 가져오기
)
func main() {
r := gin.Default()
// API 라우트
v1 := r.Group("/api/v1")
{
v1.GET("/products/:id", GetProduct)
v1.POST("/products", CreateProduct)
}
// Swagger 엔드포인트
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.Run(":8080")
}
이제 http://localhost:8080/swagger/index.html에서 인터랙티브 API 문서를 액세스할 수 있습니다.
Echo 프레임워크로 구현
Echo 사용자는 유사한 패턴을 따르지만 Echo 전용 미들웨어를 사용합니다:
package main
import (
"github.com/labstack/echo/v4"
echoSwagger "github.com/swaggo/echo-swagger"
_ "yourproject/docs"
)
func main() {
e := echo.New()
// API 라우트
api := e.Group("/api/v1")
api.GET("/products/:id", getProduct)
api.POST("/products", createProduct)
// Swagger 엔드포인트
e.GET("/swagger/*", echoSwagger.WrapHandler)
e.Start(":8080")
}
Fiber 프레임워크로 구현
Fiber의 구현도 마찬가지로 간단합니다:
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
_ "yourproject/docs"
)
func main() {
app := fiber.New()
// API 라우트
api := app.Group("/api/v1")
api.Get("/products/:id", getProduct)
api.Post("/products", createProduct)
// Swagger 엔드포인트
app.Get("/swagger/*", swagger.HandlerDefault)
app.Listen(":8080")
}
고급 Swagger 주석
복잡한 요청 본문 문서화
중첩 구조나 배열의 경우:
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 새 주문 생성
// @Description 여러 항목과 배송 정보를 포함한 주문 생성
// @Tags orders
// @Accept json
// @Produce json
// @Param order body CreateOrderRequest true "주문 상세 정보"
// @Success 201 {object} Order
// @Failure 400 {object} ErrorResponse
// @Failure 422 {object} ErrorResponse
// @Security Bearer
// @Router /orders [post]
func CreateOrder(c *gin.Context) {
// 구현
}
파일 업로드 문서화
// UploadImage godoc
// @Summary 제품 이미지 업로드
// @Description 제품을 위한 이미지 파일 업로드
// @Tags products
// @Accept multipart/form-data
// @Produce json
// @Param id path int true "제품 ID"
// @Param file formData file true "이미지 파일"
// @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")
// 업로드 처리
}
쿼리 파라미터 및 페이지네이션
// ListProducts godoc
// @Summary 페이지네이션과 함께 제품 목록 보기
// @Description 선택적 필터링이 가능한 페이지네이션 제품 목록 가져오기
// @Tags products
// @Accept json
// @Produce json
// @Param page query int false "페이지 번호" default(1)
// @Param page_size query int false "페이지당 항목 수" default(10)
// @Param category query string false "카테고리로 필터링"
// @Param min_price query number false "최소 가격"
// @Param max_price query number false "최대 가격"
// @Success 200 {array} Product
// @Failure 400 {object} ErrorResponse
// @Router /products [get]
func ListProducts(c *gin.Context) {
// 페이지네이션 구현
}
인증 및 보안
API에서 다양한 인증 방법을 문서화합니다. 멀티테넌트 애플리케이션에서 적절한 인증 문서화는 매우 중요합니다:
Bearer 토큰 인증
// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description "Bearer" 후 공백과 JWT 토큰을 입력하세요.
API 키 인증
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name X-API-Key
// @description 인증용 API 키
OAuth2 인증
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write 쓰기 접근 권한 부여
// @scope.admin 관리 정보에 대한 읽기 및 쓰기 접근 권한 부여
기본 인증 (Basic Authentication)
// @securityDefinitions.basic BasicAuth
특정 엔드포인트에 보안을 적용합니다:
// @Security Bearer
// @Security ApiKeyAuth
Swagger UI 커스텀화
Swagger UI의外观과 동작을 커스텀할 수 있습니다:
// 커스텀 설정
url := ginSwagger.URL("http://localhost:8080/swagger/doc.json")
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
// 커스텀 제목과 함께
r.GET("/swagger/*any", ginSwagger.WrapHandler(
swaggerFiles.Handler,
ginSwagger.URL("http://localhost:8080/swagger/doc.json"),
ginSwagger.DefaultModelsExpandDepth(-1),
))
프로덕션에서 Swagger를 비활성화하려면:
if os.Getenv("ENV") != "production" {
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
CI/CD 통합
CI/CD 파이프라인에서 Swagger 문서 생성을 자동화합니다:
# GitHub Actions 예제
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
모범 사례
1. 일관된 주석 스타일
모든 엔드포인트에서 일관된 포맷을 유지합니다:
// HandlerName godoc
// @Summary 간략한 설명 (50자 이내)
// @Description 엔드포인트가 수행하는 작업에 대한 자세한 설명
// @Tags 리소스-이름
// @Accept json
// @Produce json
// @Param name location type required "설명"
// @Success 200 {object} ResponseType
// @Failure 400 {object} ErrorResponse
// @Router /path [method]
2. 설명적인 예제 사용
API 소비자들에게 도움이 되는 현실적인 예제를 추가합니다:
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. 모든 응답 코드 문서화
모든 가능한 HTTP 상태 코드를 포함합니다:
// @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. API 버전 관리
베이스 패스에 적절한 버전 관리를 사용합니다:
// @BasePath /api/v1
그리고 코드를 accordingly 조직화합니다:
v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")
5. 관련 엔드포인트 그룹화
태그를 사용하여 엔드포인트를 논리적으로 조직화합니다:
// @Tags products
// @Tags orders
// @Tags users
6. 문서 최신화 유지
모든 커밋 전에 swag init을 실행하거나 빌드 프로세스에 통합합니다:
#!/bin/bash
# pre-commit hook
swag init
git add docs/
Swagger 문서 테스트
AWS Lambda와 같은 서버리스 아키텍처를 다룰 때, API 문서 테스트는 더욱 중요해집니다:
func TestSwaggerGeneration(t *testing.T) {
// swagger.json이 존재하는지 확인
_, err := os.Stat("./docs/swagger.json")
if err != nil {
t.Fatal("swagger.json not found, run 'swag init'")
}
// swagger 엔드포인트가 응답하는지 확인
r := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/swagger/index.html", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
일반적인 문제 및 해결책
문서가 업데이트되지 않는 경우
변경 사항이 표시되지 않으면 문서가 재생성되고 있는지 확인합니다:
swag init --parseDependency --parseInternal
--parseDependency 플래그는 외부 종속성을 파싱하고, --parseInternal은 내부 패키지를 파싱합니다.
커스텀 타입이 인식되지 않는 경우
외부 패키지에서 온 타입에 대해 swaggertype 태그를 사용합니다:
type CustomTime struct {
time.Time
}
func (CustomTime) SwaggerDoc() map[string]string {
return map[string]string{
"time": "RFC3339 타임스탬프",
}
}
또는 swaggertype 태그를 사용하세요:
type Product struct {
ID int `json:"id"`
UpdatedAt CustomTime `json:"updated_at" swaggertype:"string" format:"date-time"`
}
배열 및 열거형 (Enums)
배열 타입과 열거형을 문서화합니다:
type Filter struct {
Status []string `json:"status" enums:"active,inactive,pending"`
Tags []string `json:"tags"`
}
// @Param status query string false "상태 필터" Enums(active, inactive, pending)
대안적 접근법
swaggo가 가장 인기 있는 선택이지만, 다른 옵션들도 존재합니다:
go-swagger
기능은 더 풍부하지만 복잡한 대안입니다:
brew install go-swagger
swagger generate spec -o ./swagger.json
수동 OpenAPI 파일
완전한 제어를 위해 OpenAPI 사양을 YAML로 수동으로 작성합니다:
openapi: 3.0.0
info:
title: Product API
version: 1.0.0
paths:
/products:
get:
summary: List products
responses:
'200':
description: Success
그리고 다음과 같이 서빙합니다:
r.StaticFile("/openapi.yaml", "./openapi.yaml")
AI 및 LLM 통합
AI 서비스와 통합되는 API를 구축할 때, 적절한 문서화는 필수적입니다. 예를 들어, 구조화된 LLM 출력을 다룰 때, Swagger는 복잡한 요청 및 응답 스키마를 문서화하는 데 도움이 됩니다:
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 구조화된 LLM 출력 생성
// @Description 출력 스키마가 제약된 텍스트 생성
// @Tags llm
// @Accept json
// @Produce json
// @Param request body LLMRequest true "LLM 파라미터"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} ErrorResponse
// @Router /llm/generate [post]
func GenerateStructured(c *gin.Context) {
// 구현
}
성능 고려 사항
Swagger 문서는 성능에 미미한 영향을 미칩니다:
- 빌드 시간: 대부분의 프로젝트에서
swag init은 1~3초가 소요됩니다 - 런타임: 문서는 시작 시 한 번만 로드됩니다
- 메모리: 바이너리 크기에 일반적으로 1~2MB가 추가됩니다
- 응답 시간: API 엔드포인트 자체에는 영향을 미치지 않습니다
매우 대규모 API(엔드포인트 100개 이상)의 경우 다음 사항을 고려하세요:
- 여러 Swagger 파일로 분리하기
- Swagger UI 에셋을 지연 로드하기
- 독립적인 서비스에서 문서를 서빙하기
보안 고려 사항
Swagger 문서를 공개할 때:
- 프로덕션에서 비활성화 (API가 내부인 경우):
if os.Getenv("ENV") == "production" {
// Swagger 엔드포인트 등록하지 않기
return
}
- 인증 추가:
authorized := r.Group("/swagger")
authorized.Use(AuthMiddleware())
authorized.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
- 엔드포인트에 레이트 리미팅 적용:
r.GET("/swagger/*any", RateLimitMiddleware(), ginSwagger.WrapHandler(swaggerFiles.Handler))
- 내부 세부 정보 노출 금지:
- 내부 엔드포인트를 문서화하지 않기
- 데이터베이스 스키마를 직접 노출하지 않기
- 문서 내 오류 메시지를 정화(Sanitize)하기
결론
Go API에 Swagger 문서를 추가하면 개발자 경험을 추측에서 안내된 탐색으로 변화시킵니다. swaggo 라이브러리는 코드 주석에서 포괄적인 OpenAPI 문서를 생성하여 이 과정을 단순화합니다.
핵심 요점:
- 기본 주석으로 시작하여 점진적으로 확장하기
- CI/CD를 통해 문서와 코드의 동기를 유지하기
- 개발 중에 인터랙티브 테스트를 위해 Swagger UI 사용하기
- 인증, 오류, 가장자리의 경우(Edge cases)를 철저히 문서화하기
- 문서 공개 시 보안 함의를 고려하기
마이크로서비스, 공개 API, 또는 내부 도구를 구축하는 경우를 막론하고, Swagger 문서는 지원 부담 감소, 더 빠른 온보딩, 더 나은 API 설계를 통해 이익을 제공합니다. 주석 문법을 배우는 초기 투자는 신속하게 루틴이 되며, 자동화된 생성은 문서가 구현에 뒤처지지 않도록 보장합니다.
Go 개발자에게, 강력 타입, 코드 생성, swaggo의 주석 시스템의 조합은 API 문서를 사후적인 생각이 아닌 개발 프로세스의 자연스러운 일부로 만드는 강력한 워크플로우를 생성합니다.
만약 밑바탕 되는 HTTP 스택이 여전히 미지인 상태라면, 2026년 Go 웹 프레임워크 비교: net/http, chi, Gin, Echo, Fiber가 동일한 워크로드에서 5가지 옵션을 모두 벤치마크하여 문서화 툴링이 연결되기 전에 프레임워크 결정을 내릴 수 있게 해줍니다.
유용한 링크
- Go 치트시트
- Go에서 REST API 구축
- 2026년 Go 웹 프레임워크 비교: net/http, chi, Gin, Echo, Fiber
- PostgreSQL을 위한 Go ORM 비교: GORM vs Ent vs Bun vs sqlc
- Go로 예제를 통한 멀티테넌시 데이터베이스 패턴
- 구조화된 출력을 사용하는 LLM: Ollama, Qwen3 & Python 또는 Go
- AWS lambda 성능: JavaScript vs Python vs Golang