이전 챕터에서
net/http + handler / service / repository 구조로 CRUD API를 만들었다면,
이번에는 그 구조를 Gin 프레임워크로 옮겨보는 단계다.
이번 챕터의 핵심은 분명하다.
- Gin 문법을 많이 아는 것 ❌
- 기존 구조를 깨지 않고 프레임워크만 교체 ⭕️
즉,
“Gin을 쓰면 코드 구조가 어떻게 달라지는가”가 아니라
**“달라지지 않아야 하는 부분은 무엇인가”**에 초점을 둔다.
왜 이 타이밍에 Gin을 보는가
Gin은 Go 웹 프레임워크 중 가장 많이 사용된다.
하지만 실무에서 Gin을 잘 쓰는 사람들의 공통점은 이거다.
Gin 이전에 net/http 흐름을 한 번은 이해했다
이 과정을 거치면
- Gin의 Context가 왜 필요한지
- 미들웨어가 어디에 끼어드는지
- 테스트를 어떻게 해야 하는지
가 훨씬 자연스럽게 연결된다.
이번 챕터에서 바꾸지 않을 것
먼저 바꾸지 않을 것부터 명확히 하자.
- service 계층
- repository 계층
- model 구조
- 비즈니스 로직
즉, HTTP 레이어만 Gin으로 교체한다.
handler (변경 대상)
service (유지)
repository (유지)
이 기준이 무너지면
프레임워크 종속 코드가 빠르게 퍼지기 시작한다.
Gin 도입 후 전체 구조
cmd/server/main.go
internal/handler/
internal/service/
internal/repository/
internal/model/
디렉터리 구조는 그대로 유지한다.
바뀌는 건 main.go와 handler 구현 방식뿐이다.
[이미지: net/http → Gin 전환 구조 비교]
main.go: 서버 시작 코드 변화
net/http 버전
mux := http.NewServeMux()
mux.HandleFunc("/users", handler.List)
http.ListenAndServe(":8080", mux)
Gin 버전
r := gin.New()
r.GET("/users", handler.List)
r.Run(":8080")
차이는 명확하다.
- ServeMux → Router
- HandlerFunc → Gin handler
- ListenAndServe → Run
하지만 서버 역할 자체는 동일하다.
Gin handler의 기본 형태
Gin에서는 handler 시그니처가 이렇게 바뀐다.
func (h *UserHandler) List(c *gin.Context) {
users, err := h.service.ListUsers()
if err != nil {
c.JSON(500, gin.H{"error": "internal error"})
return
}
c.JSON(200, users)
}
차이점은 다음 정도다.
- http.ResponseWriter, *http.Request → *gin.Context
- 응답 작성이 훨씬 간결해짐
- 상태 코드 + JSON 한 줄로 처리 가능
Gin Context는 무엇을 대신하는가
Gin의 Context는 사실상 다음을 묶어둔 객체다.
- 요청 정보 (*http.Request)
- 응답 작성 기능
- path / query / body 접근
- middleware 공유 데이터
즉,
net/http에서 흩어져 있던 것들을 한 객체로 모아둔 래퍼
라고 이해하면 과하지 않다.
요청 파라미터 처리 비교
Query Parameter
// net/http
id := r.URL.Query().Get("id")
// Gin
id := c.Query("id")
JSON Body
// Gin
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "invalid body"})
return
}
실제로 써보면
Gin이 HTTP 레이어 보일러플레이트를 많이 줄여준다는 걸 체감하게 된다.
[이미지: Gin 요청 처리 흐름]
라우팅 그룹 활용하기
Gin에서는 라우팅을 그룹으로 묶을 수 있다.
api := r.Group("/api")
{
api.GET("/users", handler.List)
api.POST("/users", handler.Create)
}
- URL 구조가 한눈에 들어옴
- 버전 관리(/v1)에도 유리
이건 ServeMux에서는 직접 구현해야 했던 부분이다.
미들웨어 적용 방식 비교
Gin 미들웨어 기본 형태
func LoggingMiddleware(c *gin.Context) {
log.Println(c.Request.Method, c.Request.URL.Path)
c.Next()
}
r.Use(LoggingMiddleware)
- 전역 미들웨어
- 그룹 단위 미들웨어
- 특정 라우트 전용 미들웨어
모두 명확하게 표현 가능하다.
이 구조는
이전 챕터에서 만든 net/http 미들웨어 개념과 완전히 동일한 역할이다.
중요한 설계 기준: Gin을 service로 넘기지 말 것
⚠️ 가장 중요한 주의사항
- *gin.Context를 service로 넘기지 않는다
- service는 HTTP를 몰라야 한다
- handler에서 데이터만 추출해서 전달한다
// ❌ 나쁜 예
service.DoSomething(c)
// ⭕️ 좋은 예
service.DoSomething(userID)
이 기준 하나만 지켜도
프레임워크 종속이 급격히 줄어든다.
Gin 환경에서의 테스트 전략
Gin handler도
httptest로 충분히 테스트 가능하다.
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/users", nil)
router.ServeHTTP(w, req)
- 실제 서버 실행 불필요
- 기존 테스트 전략 그대로 유지 가능
즉,
프레임워크 교체가 테스트 구조를 깨지 않는다.
net/http → Gin 전환의 핵심 정리
이 챕터에서 얻어야 할 감각은 이거다.
- Gin은 생산성을 높여주는 도구
- 구조를 대신 설계해주지는 않는다
- 잘 만든 구조 위에서 써야 효과가 난다
Gin을 썼다고 해서
코드가 자동으로 “좋아지지는 않는다”.
정리
Gin 전환의 핵심은
**“프레임워크를 바꿔도 구조는 그대로 유지되는가”**다.
- handler만 Gin에 맞게 수정
- service / repository는 그대로 유지
- HTTP 편의 기능만 선택적으로 활용
이 기준을 지키면
- Gin → Echo 전환
- HTTP → gRPC 확장
같은 작업도 훨씬 수월해진다.
다음 챕터로는
- PostgreSQL 연동 + migration
- 인증/인가(JWT) 미들웨어 설계
- 실무용 에러 처리 전략(API 에러 규약)
중 하나로 이어가면 좋다.
'backend' 카테고리의 다른 글
| Go API 에러 응답 규약 정리: 실무에서 흔들리지 않는 기준 만들기 (1) | 2026.02.04 |
|---|---|
| Go 인증/인가 구현하기: JWT 기반 인증 미들웨어 설계 (2) | 2026.02.03 |
| Go에서 DB 연동하기: database/sql과 Repository 패턴으로 구조 잡기 (0) | 2026.02.01 |
| Go로 간단한 CRUD API 만들기: REST 구조와 테스트 코드까지 연결하기 (1) | 2026.01.31 |
| Go HTTP 서버 확장하기: 라우팅 분리와 미들웨어 개념 정리 (0) | 2026.01.30 |