1. 프로젝트 생성
터미널
mkdir my-api && cd my-api
go mod init my-api
go get github.com/go-chi/chi/v5
go get github.com/go-chi/cors
go get github.com/google/uuidChi는 가볍고 빠른 Go 라우터입니다. Express.js와 비슷한 미들웨어 패턴을 사용합니다.
2. 첫 API 작성
main.go
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
)
func main() {
r := chi.NewRouter()
// Middleware
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"http://localhost:*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowedHeaders: []string{"Content-Type"},
AllowCredentials: true,
}))
// Routes
r.Get("/health", healthHandler)
r.Get("/api/hello", helloHandler)
// Start server
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Server starting on port %s", port)
http.ListenAndServe(":"+port, r)
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
})
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"message": "Hello, " + name + "!",
})
}실행
터미널
go run main.go
# 테스트
curl http://localhost:8080/health
curl http://localhost:8080/api/hello?name=Choorai3. CRUD API 추가
handlers.go
package main
import (
"encoding/json"
"net/http"
"sync"
"time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)
// Project model
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
// In-memory storage
var (
projects = make(map[string]*Project)
mu sync.RWMutex
)
// List projects
func listProjects(w http.ResponseWriter, r *http.Request) {
mu.RLock()
defer mu.RUnlock()
items := make([]*Project, 0, len(projects))
for _, p := range projects {
items = append(items, p)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"items": items,
"total": len(items),
})
}
// Create project
func createProject(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Description string `json:"description"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, `{"error":"invalid body"}`, http.StatusBadRequest)
return
}
project := &Project{
ID: uuid.New().String(),
Name: req.Name,
Description: req.Description,
CreatedAt: time.Now(),
}
mu.Lock()
projects[project.ID] = project
mu.Unlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(project)
}
// Get project
func getProject(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
mu.RLock()
project, exists := projects[id]
mu.RUnlock()
if !exists {
http.Error(w, `{"error":"not found"}`, http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(project)
}
// Delete project
func deleteProject(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
mu.Lock()
defer mu.Unlock()
if _, exists := projects[id]; !exists {
http.Error(w, `{"error":"not found"}`, http.StatusNotFound)
return
}
delete(projects, id)
w.WriteHeader(http.StatusNoContent)
}
// Router setup
func setupRoutes(r chi.Router) {
r.Route("/api/v1/projects", func(r chi.Router) {
r.Get("/", listProjects)
r.Post("/", createProject)
r.Get("/{id}", getProject)
r.Delete("/{id}", deleteProject)
})
}sync.RWMutex
Go에서 동시성 안전을 위해 sync.RWMutex를 사용합니다.
읽기는 여러 goroutine이 동시에, 쓰기는 하나만 가능합니다.
4. Docker로 배포
Dockerfile
# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server .
# Runtime stage
FROM alpine:3.20
WORKDIR /app
RUN adduser -D -g '' appuser
COPY --from=builder /app/server .
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8080
CMD ["./server"]멀티스테이지 빌드로 최종 이미지에는 바이너리만 포함됩니다. 결과 이미지 크기는 약 15-20MB입니다.
터미널
# 로컬 테스트
docker build -t my-go-api .
docker run -p 8080:8080 my-go-api
# 이미지 크기 확인 (< 20MB!)
docker images my-go-api
# Cloud Run 배포
gcloud run deploy my-go-api \
--source . \
--region asia-northeast3 \
--allow-unauthenticated \
--min-instances 0 \
--max-instances 1프레임워크 비교
| 항목 | Go (Chi) | Hono | FastAPI |
|---|---|---|---|
| 이미지 크기 | ~15MB | ~150MB | ~200MB |
| Cold Start | ~100ms | ~300ms | ~500ms |
| 메모리 사용 | ~20MB | ~50MB | ~80MB |
| 학습 곡선 | 중간 | 쉬움 | 쉬움 |
| 타입 시스템 | 정적 | 동적 (TS) | 동적 (Pydantic) |
언제 Go를 선택할까?
전체 예제 코드
B2B Admin API의 Go 버전을 확인하세요: examples/b2b-admin/api-go