Go의 표준 라이브러리 net/http부터 Gin, Chi, Fiber, Echo까지 주요 웹 프레임워크의 아키텍처, 성능 특성, 미들웨어 구조를 비교하고 프로젝트별 선택 기준을 제시합니다.
Go의 net/http 패키지는 프로덕션 수준의 HTTP 서버를 구현할 수 있는 강력한 표준 라이브러리입니다. 많은 서드파티 프레임워크도 내부적으로 net/http를 기반으로 합니다.
package main
import (
"encoding/json"
"log/slog"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/health", healthHandler)
mux.HandleFunc("GET /api/users/{id}", getUserHandler)
mux.HandleFunc("POST /api/users", createUserHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
slog.Info("서버 시작", "addr", server.Addr)
if err := server.ListenAndServe(); err != nil {
slog.Error("서버 종료", "error", err)
}
}
func getUserHandler(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Go 1.22+
// ...
}Go 1.22에서 http.ServeMux가 대폭 개선되었습니다. 이전에는 경로 파라미터나 HTTP 메서드 기반 라우팅이 불가능했지만, 이제 기본 기능으로 지원됩니다.
mux := http.NewServeMux()
// HTTP 메서드 지정
mux.HandleFunc("GET /articles", listArticles)
mux.HandleFunc("POST /articles", createArticle)
// 경로 파라미터
mux.HandleFunc("GET /articles/{slug}", getArticle)
mux.HandleFunc("PUT /articles/{slug}", updateArticle)
mux.HandleFunc("DELETE /articles/{slug}", deleteArticle)
// 와일드카드 -- 나머지 경로 캡처
mux.HandleFunc("GET /files/{path...}", serveFile)Go 1.22의 라우터 개선은 단순한 REST API에서는 외부 라이브러리 없이도 충분합니다. 그러나 라우트 그룹, 미들웨어 체이닝, 자동 OPTIONS 응답, 정규식 경로 매칭 등이 필요하다면 서드파티 라우터가 여전히 유용합니다.
Gin은 Go 웹 프레임워크 중 가장 높은 인지도를 가진 프레임워크입니다. httprouter 기반의 빠른 라우팅, 풍부한 미들웨어 생태계, 직관적인 API가 특징입니다.
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // Logger + Recovery 미들웨어 포함
// 라우트 그룹
api := r.Group("/api/v1")
{
users := api.Group("/users")
{
users.GET("", listUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}
}
r.Run(":8080")
}
func getUser(c *gin.Context) {
id := c.Param("id")
user, err := userService.FindByID(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "사용자를 찾을 수 없습니다"})
return
}
c.JSON(http.StatusOK, user)
}Gin은 요청 데이터 바인딩과 유효성 검사를 내장하고 있습니다.
type CreateUserRequest struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
}
func createUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// req는 유효성 검사를 통과한 상태
}Chi는 net/http와 100% 호환되는 경량 라우터입니다. 표준 http.Handler 인터페이스를 그대로 사용하므로, 표준 라이브러리 미들웨어와 완벽하게 호환됩니다.
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
r := chi.NewRouter()
// 미들웨어 스택
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
r.Route("/api/v1", func(r chi.Router) {
r.Route("/users", func(r chi.Router) {
r.Get("/", listUsers)
r.Post("/", createUser)
r.Route("/{userID}", func(r chi.Router) {
r.Use(UserCtx) // 라우트 특정 미들웨어
r.Get("/", getUser)
r.Put("/", updateUser)
r.Delete("/", deleteUser)
})
})
})
http.ListenAndServe(":8080", r)
}
// 표준 http.Handler 시그니처
func getUser(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "userID")
// ...
}Chi의 가장 큰 장점은 표준 라이브러리와의 완벽한 호환성입니다.
// 표준 http.Handler를 그대로 사용
r.Handle("/static/*", http.StripPrefix("/static/",
http.FileServer(http.Dir("./public")),
))
// 표준 미들웨어 패턴
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("요청 처리",
"method", r.Method,
"path", r.URL.Path,
"duration", time.Since(start),
)
})
}Fiber는 fasthttp 기반의 고성능 프레임워크입니다. Express.js에서 영감을 받은 API 설계로, Node.js 개발자들이 친숙하게 느낄 수 있습니다.
package main
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New(fiber.Config{
Prefork: false,
ServerHeader: "MyAPI",
StrictRouting: true,
})
// 미들웨어
app.Use(logger.New())
app.Use(recover.New())
// 라우팅
api := app.Group("/api/v1")
users := api.Group("/users")
users.Get("/", listUsers)
users.Post("/", createUser)
users.Get("/:id", getUser)
users.Put("/:id", updateUser)
users.Delete("/:id", deleteUser)
app.Listen(":8080")
}
func getUser(c *fiber.Ctx) error {
id := c.Params("id")
// Fiber는 자체 Ctx를 사용 -- net/http 비호환
return c.JSON(user)
}Fiber는 net/http 대신 fasthttp를 사용하므로, net/http 기반 미들웨어와 호환되지 않습니다. context.Context 전달도 표준 방식과 다릅니다. 기존 net/http 생태계와의 통합이 중요하다면 주의가 필요합니다.
Echo는 고성능과 사용 편의성의 균형을 잡은 프레임워크입니다. 자동 TLS, HTTP/2, 그리고 풍부한 내장 미들웨어를 제공합니다.
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// 미들웨어
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// 라우팅
api := e.Group("/api/v1")
users := api.Group("/users")
users.GET("", listUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
e.Logger.Fatal(e.Start(":8080"))
}
func getUser(c echo.Context) error {
id := c.Param("id")
user, err := userService.FindByID(c.Request().Context(), id)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, "사용자를 찾을 수 없습니다")
}
return c.JSON(http.StatusOK, user)
}type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2,max=50"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
func createUser(c echo.Context) error {
var req CreateUserRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if err := c.Validate(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// ...
} net/http Gin Chi Fiber Echo
기반 표준 lib net/http net/http fasthttp net/http
라우팅 속도 보통 매우 빠름 빠름 매우 빠름 빠름
미들웨어 수동 구현 내장 풍부 net/http 자체 생태계 내장 풍부
데이터 바인딩 없음 내장 별도 lib 내장 내장
유효성 검사 없음 내장 별도 lib 내장 별도 설정
net/http 호환 완벽 부분적 완벽 비호환 부분적
학습 곡선 낮음 낮음 매우 낮음 낮음 낮음
GitHub Stars 표준 lib ~80k ~18k ~35k ~30k실무 추천 조합: 새 프로젝트를 시작한다면, Chi 또는 Gin을 추천합니다. Chi는 표준 라이브러리와의 호환성이 뛰어나 장기적으로 유지보수가 쉽고, Gin은 풍부한 생태계와 빠른 프로토타이핑에 유리합니다. 11장의 실전 프로젝트에서는 Chi를 기반으로 API 서버를 구축합니다.
각 프레임워크에서 미들웨어를 구현하는 방식을 비교합니다.
// net/http -- 함수 래핑 패턴
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !isValid(token) {
http.Error(w, "인증 실패", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// Gin -- gin.HandlerFunc 체이닝
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if !isValid(token) {
c.AbortWithStatusJSON(401, gin.H{"error": "인증 실패"})
return
}
c.Next()
}
}
// Chi -- net/http 동일 패턴
// Chi는 표준 http.Handler 미들웨어를 그대로 사용
// Fiber -- fiber.Handler 체이닝
func authMiddleware(c *fiber.Ctx) error {
token := c.Get("Authorization")
if !isValid(token) {
return c.Status(401).JSON(fiber.Map{"error": "인증 실패"})
}
return c.Next()
}이번 장에서 살펴본 핵심 내용을 정리합니다.
net/http와 100% 호환되어 표준 라이브러리 미들웨어를 그대로 사용할 수 있습니다net/http 생태계와 호환되지 않습니다7장에서는 데이터베이스 연동을 다룹니다. database/sql, sqlx, GORM, pgx를 비교하고, 커넥션 풀링, 트랜잭션, 마이그레이션, 쿼리 빌더 패턴을 실전 예제와 함께 살펴봅니다.
이 글이 도움이 되셨나요?
Go의 에러 값 철학, errors.Is/As를 활용한 에러 검사, fmt.Errorf와 %w를 사용한 에러 래핑, 커스텀 에러 타입, 센티널 에러, panic/recover의 올바른 사용법을 다룹니다.
Go에서 데이터베이스를 다루는 방법을 database/sql, sqlx, GORM, pgx를 통해 비교하고, 커넥션 풀링, 트랜잭션, 마이그레이션, 쿼리 빌더 패턴을 실전 예제로 다룹니다.
Go 백엔드의 미들웨어 패턴, JWT 인증 구현, CORS 설정, 레이트 리미팅, 요청 유효성 검사, OpenAPI/Swagger 문서 생성, log/slog를 활용한 구조화된 로깅을 다룹니다.