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을 사용하여 완전한 예제를 구현해 보겠습니다. 먼저 구조체 태그와 함께 데이터 모델을 정의합니다:
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"`
}
이제 핸들러 함수에 주석을 추가합니다. 데이터베이스 작업을 수행할 때, 이러한 주석은 데이터 흐름을 문서화하는 데 도움이 됩니다:
// 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 관리 정보에 대한 읽기 및 쓰기 권한 부여
기본 인증
// @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: Swagger 문서 생성
on: [push]
jobs:
swagger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Go 설정
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: swag 설치
run: go install github.com/swaggo/swag/cmd/swag@latest
- name: Swagger 문서 생성
run: swag init
- name: 문서 커밋
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git add docs/
git commit -m "Swagger 문서 업데이트" || exit 0
git push
최고의 실천 방법
1. 일관된 주석 스타일 유지
모든 엔드포인트에서 일관된 포맷을 유지하세요:
// HandlerName godoc
// @Summary 간단한 설명 (50자 미만)
// @Description 엔드포인트가 수행하는 작업에 대한 자세한 설명
// @Tags 리소스 이름
// @Accept json
// @Produce json
// @Param 이름 위치 유형 필수 "설명"
// @Success 200 {object} 응답 유형
// @Failure 400 {object} ErrorResponse
// @Router /경로 [메서드]
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
코드를 다음과 같이 구성하세요:
v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")
5. 관련 엔드포인트 그룹화
논리적으로 엔드포인트를 구성하기 위해 태그를 사용하세요:
// @Tags products
// @Tags orders
// @Tags users
6. 문서 업데이트 유지
각 커밋 전에 swag init을 실행하거나 빌드 프로세스에 통합하세요:
#!/bin/bash
# pre-commit 훅
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"`
}
배열 및 열거형
배열 타입 및 열거형을 문서화하세요:
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 파일
완전한 제어를 위해 YAML로 OpenAPI 명세를 수동으로 작성할 수 있습니다:
openapi: 3.0.0
info:
title: Product API
version: 1.0.0
paths:
/products:
get:
summary: 제품 목록 보기
responses:
'200':
description: 성공
그런 다음 다음과 같이 제공하세요:
r.StaticFile("/openapi.yaml", "./openapi.yaml")
AI 및 LLM과의 통합
AI 서비스와 통합하는 API를 구축할 때, 문서화는 매우 중요합니다. 예를 들어, 구조화된 LLM 출력을 사용할 때, Swagger는 복잡한 요청 및 응답 스키마를 문서화하는 데 도움이 됩니다:
type LLMRequest struct {
Prompt string `json:"prompt" example:"이 텍스트를 요약하세요"`
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 엔드포인트 자체에는 영향을 주지 않습니다.
100개 이상의 엔드포인트가 있는 매우 큰 API의 경우 다음을 고려하세요:
- 여러 개의 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))
- 내부 세부 정보 노출 금지:
- 내부 엔드포인트를 문서화하지 않음
- 데이터베이스 스키마를 직접 노출하지 않음
- 문서화에서 오류 메시지를 정화
결론
Go API에 Swagger 문서를 추가하면 개발자 경험을 추측에서 지도된 탐색으로 바꿉니다. swaggo 라이브러리는 코드 주석에서 포괄적인 OpenAPI 문서를 생성함으로써 이 과정을 간단하게 만듭니다.
핵심 요약:
- 기본 주석으로 시작하고 점진적으로 확장
- CI/CD를 통해 문서와 코드를 동기화
- 개발 중에 상호작용형 테스트를 위해 Swagger UI 사용
- 인증, 오류, 경계 사례를 철저히 문서화
- 문서를 노출할 때 보안 고려사항을 고려
마이크로서비스, 공개 API, 내부 도구를 구축하던지간에 Swagger 문서는 지원 부담 감소, 더 빠른 온보딩, 더 나은 API 설계에 이점을 제공합니다. 학습 주석 구문에 대한 초기 투자금은 빠르게 일상이 되며, 자동 생성은 문서가 구현을 따라가지 않도록 보장합니다.
Go 개발자에게 강한 타이핑, 코드 생성 및 swaggo의 주석 시스템의 조합은 API 문서화를 개발 과정의 자연스러운 일부로 만들어주는 강력한 워크플로우를 생성합니다.
유용한 링크
- Go 빠른 참조
- Go에서 REST API 구현
- PostgreSQL을 위한 Go ORMs 비교: GORM vs Ent vs Bun vs sqlc
- Go에서 다중 테넌트 데이터베이스 패턴
- 구조화된 출력과 LLM: Ollama, Qwen3 및 Python 또는 Go
- AWS Lambda 성능: JavaScript vs Python vs Golang