일반적으로 백엔드 개발은 JAVA가 정석으로 여겨져 왔지만, 성능적인 측면에서 Go 도 많이 사용되고 있는 추세라고 한다.
(참고: JAVA랑 Go 성능 벤치마크 비교 영상)
Go 언어로도 다양한 웹 프레임워크가 존재하는데, 이 중 최근 업무에서 많이 사용하고 있는 Gin 프레임워크에 대해 알아보고자 한다.
Go 웹 프레임워크 종류
Go에는 대표적으로 아래 5가지의 웹 프레임워크 종류가 있다.
(좀 더 많은 웹 프레임워크 종류는 https://blog.logrocket.com/top-go-frameworks-2025/ 참고)
| 프레임워크 종류 | 특징 | 선택 기준 |
| net/http | 표준 라이브러리 | 작은 프로젝트 또는 프레임워크 종속성을 피하고 핵심 기능만 구현하고자 할 때 사용 |
| Gin | httprouter 기반 빠른 라우팅 | 대부분의 RESTful API 개발에 사용 (성능, 생산성, 커뮤니티 지원 균형이 가장 뛰어남) |
| Echo | 간결함 + 미들웨어 구현 편리 | Gin보다 간결하고 미니멀한 코드 베이스를 원할 경우 사용 |
| Fiber | fasthttp 기반 빠른 라우팅 (Express.js와 유사) |
처리량이 중요하고, net/http 호환성이 크게 문제되지 않을 때 사용 |
| Revel | Rails/Django와 유사한 구조 | 개발 편의성이 중요할 때 사용 (러닝 커브 높음) |
이 중 속도 성능도 좋고 표준 라이브러리인 net/http와 호환성도 좋으면서 커뮤니티도 활발한 Gin을 주로 사용하고 있다.
Gin 공식 사이트에 따르면, 다음과 같은 매력적인 특징들이 있다고 한다.
- 빠른 속도
: Radix tree 기반 라우팅을 통해, 작은 메모리를 사용하고 reflection이 없음 - 미들웨어 지원
: 들어오는 HTTP 요청은 미들웨어 체인과 최종 액션으로 처리될 수 있음
(ex: Logger -> Authorization -> GZIP -> DB에 메시지 저장) - 크래시 방지
: HTTP 요청 중 발생한 패닉을 포착하고 복구해서 서버를 항상 가동 상태 유지 가능 - JSON 유효성 검사
: 요청이 들어온 JSON 파싱 및 유효성 검사 가능 (필수 값 존재 여부 등 체크) - Route 그룹화
: 인증 필요 vs 비필요 및 다른 API 버전 등을 그룹화해서 관리 (성능 저하 없이 무제한으로 중첩 가능) - 오류 관리
: HTTP 요청 중 발생한 모든 오류를 편리하게 수집하는 방법 제공
-> 최종적으로 미들웨어가 오류를 로그 파일이나 데이터베이스에 기록하거나 네트워크를 통해 전송 가능 - 내장 렌더링
: JSON, XML 및 HTML 렌더링을 위한 사용하기 쉬운 API 제공 - 확장성
: 새로운 미들웨어를 만드는 난이도가 매우 쉬움
Gin 구성 요소
- Engine
- 중앙 허브 역할
- git.Default() 또는 gin.New() 통해 생성
- router이자 middleware 스택을 관리하는 역할
- Go 표준 라이브러리의 net/http의 Handler 인터페이스 구현해서 HTTP 요청 처리
- 서버 실행하는 r.Run() 메서드 제공
- Context
- 들어오는 HTTP 요청에 대한 모든 정보를 담고, 응답을 생성하는 데에 사용되는 핵심 객체
- 요청(Request) 데이터(URL 매개변수, 쿼리, 본문, 헤더)에 접근
-> 응답(Response)을 JSON, XML, HTML 등으로 렌더링하거나 파일을 전송하는 기능 제공 - 요청 체인을 따라 미들웨어와 핸들러 간 데이터 전달 시 사용
- RouterGroup
- 경로(Route)들을 논리적으로 그룹화하고, 특정 경로 그룹에 미들웨어 적용
- 버전 관리(v1, v2)나 인증이 필요한 경로와 같이 접두사(Prefix)를 공유하는 경로들을 효율적으로 관리할 수 있게 함
- (Engine 자체가 기본 RouterGroup 역할)
- HandlerFunc
- 실제 요청을 처리하는 함수로, 비즈니스 로직이 구현되는 곳
- func(*gin.Context) 시그니처를 가지며, 라우트에 등록되어 요청이 들어오면 실행됨
- Middleware
- HandlerFunc와 동일한 시그니처를 가지며, 요청이 최종 핸들러에 도달하기 전/후에 실행되는 함수 체인
- 로깅, 인증/권한 부여, GZIP 압축, 패닉 복구(Recovery), CORS 등의 공통 기능을 처리하여 핸들러의 비즈니스 로직과 분리
요청 처리 흐름
- 요청 수신
: 클라이언트로부터 HTTP 요청이 Engine으로 들어온다. - 미들웨어 체인 구성
: Engine은 해당 요청 경로에 등록된 Middleware와 최종 HandlerFunc를 순서대로 묶어 실행 체인이 생성된다. - Context 생성
: 요청 정보를 담고 응답을 관리할 Context 객체가 생성된다. - 라우팅 (Routing)
: Engine은 들어온 URL 경로에 매핑된 실행 체인을 빠르게 찾는다.
(내부적으로 httprouter 패키지의 Radix Tree 기반 라우터 사용) - 미들웨어 실행
: 체인의 Middleware들이 차례로 실행된다.
(미들웨어는 c.Next()를 호출하여 다음 Middleware 또는 최종 Handler로 제어를 넘김) - 핸들러 실행
: 체인의 마지막에 있는 HandlerFunc가 실행된다.
(Context를 사용하여 요청 데이터를 파싱하고, 비즈니스 로직을 수행하며, 응답을 준비) - 응답 전송
: Handler 또는 Middleware에서 Context의 c.JSON(), c.String() 등의 메서드를 통해 최종 HTTP 응답을 작성하고, 클라이언트에 응답을 전송한다.
예시 코드
다음은 gemini와 함께 작성해본 프로젝트 예시이다. (gemini 없이는 못사는 요즘,,)
구체적으로 어떤 흐름으로 구성되는지 코드로 살펴보자면 다음과 같다.
gin-project/
├── main.go # 서버 엔진 및 라우터 초기화
├── go.mod
├── go.sum
└── internal/
├── handler/ # 실제 비즈니스 로직을 처리하는 함수
│ └── user.go # 사용자 관련 핸들러
├── middleware/ # 공통 기능을 처리하는 미들웨어
│ └── logger.go # 사용자 정의 로깅 미들웨어
└── router/ # 라우팅 및 미들웨어 연결 담당
└── router.go # 모든 경로를 정의
1. Middleware: internal/middleware/logger.go
요청 수신이 가장 먼저 실행되어 로깅을 처리하는 미들웨어이다. (gin에서 제공해주는 것이 아닌, 직접 정의한 것)
c.Next()를 통해 다음 핸들러로 제어를 넘기고, 핸들러 실행 후 돌아와서 응답 시간을 기록하는 형식이다.
// internal/middleware/logger.go
package middleware
import (
"log"
"time"
"github.com/gin-gonic/gin"
)
// CustomLogger는 요청 시작 및 응답 완료 시점을 로깅합니다.
func CustomLogger() gin.HandlerFunc {
return func(c *gin.Context) {
// [미들웨어 실행 - 요청 전]
// Context가 생성되었고, 라우팅이 매칭된 후 첫 미들웨어가 실행됨.
t := time.Now()
log.Printf("➡️ [Logger Middleware] Request Started: %s %s", c.Request.Method, c.Request.URL.Path)
// c.Next()를 호출하여 체인의 다음 미들웨어 또는 최종 핸들러로 제어를 넘김
c.Next()
// [미들웨어 실행 - 요청 후]
// 최종 핸들러 실행 후 제어가 미들웨어로 돌아옴.
latency := time.Since(t) // 응답 시간 계산
log.Printf("⬅️ [Logger Middleware] Request Finished: Status %d | Latency %v",
c.Writer.Status(), latency)
}
}
2. Handler(비즈니스 로직): internal/handler/user.go
실제 요청을 처리하고 응답을 생성하는 부분이다. 요청 데이터를 파싱해서 응답을 전송하는 역할을 한다.
// internal/handler/user.go
package handler
import (
"net/http"
"github.com/gin-gonic/gin"
)
// User 구조체 정의 (바인딩 및 응답 모델)
type User struct {
ID string `json:"id"`
Name string `json:"name" binding:"required"`
}
// 초기 데이터 (DB 대신 사용)
var mockUsers = []User{
{ID: "1", Name: "Homer Simpson"},
{ID: "2", Name: "Marge Simpson"},
}
// GetUsers는 모든 사용자 목록을 반환합니다.
// [핸들러 실행]
func GetUsers(c *gin.Context) {
// Context를 사용하여 응답을 준비하고 전송합니다.
c.JSON(http.StatusOK, gin.H{
"message": "사용자 목록 조회 성공",
"data": mockUsers,
})
// 응답 전송: Gin이 HTTP Response Writer를 통해 클라이언트에 데이터를 보냅니다.
}
// CreateUser는 새 사용자를 생성합니다.
// [핸들러 실행]
func CreateUser(c *gin.Context) {
var newUser User
// 요청 데이터 바인딩 및 유효성 검사 (Context 사용)
if err := c.ShouldBindJSON(&newUser); err != nil {
// 유효성 검사 실패 시 응답 전송
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 간단한 ID 할당 (비즈니스 로직)
newUser.ID = "3"
mockUsers = append(mockUsers, newUser)
// 성공 응답 전송
c.JSON(http.StatusCreated, gin.H{
"message": "사용자 생성 성공",
"data": newUser,
})
}
3. Routing 정의: internal/router/router.go
라우팅 및 미들웨어 체인을 구성하며, 이 부분에서 Engine 설정을 진행한다.
// internal/router/router.go
package router
import (
"github.com/gin-gonic/gin"
"gin-project/internal/handler"
"gin-project/internal/middleware"
)
// SetupRouter는 Gin 엔진을 초기화하고 모든 라우트를 등록합니다.
func SetupRouter() *gin.Engine {
// 1. Engine 초기화 (gin.Default는 Logger와 Recovery 미들웨어를 포함)
r := gin.Default()
// 2. 전역 미들웨어 등록 (가장 먼저 실행됨)
// [미들웨어 체인 구성]
r.Use(middleware.CustomLogger()) // 사용자 정의 로거 미들웨어 추가
// 3. 라우트 그룹 설정 (라우팅)
apiV1 := r.Group("/api/v1")
{
// /api/v1/users 경로 그룹
users := apiV1.Group("/users")
{
// GET /api/v1/users
users.GET("/", handler.GetUsers)
// POST /api/v1/users
users.POST("/", handler.CreateUser)
}
// 특정 경로 그룹에만 미들웨어 적용 가능 (예시)
// admin := apiV1.Group("/admin", middleware.AuthRequired())
}
return r
}
4. 애플리케이션 진입점: main.go
최종적으로 Engine 을 가져와 서버를 실행하는 메인 역할을 하는 부분이다.
// main.go
package main
import (
"log"
"gin-project/internal/router" // 분리된 router 패키지 임포트
)
func main() {
// 1. 라우터 설정 함수를 호출하여 Gin Engine을 가져옵니다.
r := router.SetupRouter()
// 2. 서버 실행 (응답 수신 대기)
log.Println("✅ Gin server starting on :8080")
if err := r.Run(":8080"); err != nil {
log.Fatalf("❌ Server failed to start: %v", err)
}
}
위의 코드를 따라 다음 순서대로 흐름이 진행된다.
- main.go 실행 -> router.SetupRouter() 호출
- router.go 에서 gin.Default로 Engine 생성 -> CustomLogger 미들웨어와 핸들러(GetUsers, CreateUser) 포함하는 실행 체인 구성
- 클라이언트에서 GET /api/v1/users 요청이 오면 -> Engine 이 해당 요청 수신
- 라우팅 후, 요청은 CustomLogger 미들웨어를 통과하면서 시작 로그가 출력됨
- 제어는 GetUsers 핸들러로 넘어가서 JSON 응답 작성하고 응답 전송 준비
- 제어는 다시 CustomLogger 미들웨어로 돌아와 응답 완료 및 지연시간 로그 출력
- 최종 응답 클라이언트로 전송
머메이드 차트로 흐름을 살펴보자면 다음과 같다.

이렇게 트렌디(?)한 go 언어 프레임워크 중, 가장 활발하게 많이 쓰이는 gin 웹 프레임워크 구조에 대해 살펴보았다.
아직 go 언어로 작업을 처음 해보는 입장이라 낯설기도 하면서 신기하기도 하다. (좀 매력적인거 같기..도..?!)
작업해보면서 또 새로 알게되는 것들은 하나씩 더 기록해 둬야겠다 !_!
References
Documentation
gin-gonic.com
'COMPUTER SCIENCE' 카테고리의 다른 글
| [Github] 효율적인 개발 프로세스 구축을 위한 필수템, Github Actions (0) | 2023.03.26 |
|---|---|
| [Git] fork 해 온 repository 내용 업데이트(pull) 하기 (1) | 2022.11.20 |
| [Github] branch 이름 변경 / remote branch 삭제 (0) | 2021.07.09 |
| [Github] branch 로컬/원격 접근 및 삭제 (0) | 2020.10.27 |
| [Hadoop] hadoop 설치 과정 (0) | 2020.09.24 |