성능 분석까지 다뤘다면, 이제는
지금까지 배운 내용을 실제 코드로 엮어보는 단계로 넘어가는 게 자연스럽다.
이번 챕터에서는
외부 프레임워크 없이 표준 라이브러리 net/http만 사용해서
가장 기본적인 HTTP API 서버를 만들어본다.
이 글의 목적은
“화려한 기능”이 아니라,
Go 서버 코드의 기본 뼈대가 어떻게 생겼는지를 이해하는 데 있다.
왜 net/http부터 시작하는가
Go 웹 개발을 검색하면
Gin, Echo 같은 프레임워크가 먼저 등장한다.
하지만 실무 기준으로 보면,
- 프레임워크도 결국 net/http 위에 있음
- 기본 구조를 모르면 디버깅이 어려움
- 단순한 서비스에는 표준 라이브러리로도 충분
이라는 이유로
net/http를 한 번은 직접 써보는 게 좋다.
가장 단순한 HTTP 서버
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "pong")
})
http.ListenAndServe(":8080", nil)
}
- /ping 요청에 "pong" 응답
- :8080 포트에서 서버 실행
curl http://localhost:8080/ping
pong
이 코드 하나로
이미 동작하는 HTTP 서버가 완성된다.
[이미지: Go net/http 기본 서버 구조]
Handler 개념 이해하기
net/http의 핵심 개념은 Handler다.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
즉,
- 요청을 받으면
- 응답을 작성한다
는 역할만 수행하면 된다.
http.HandleFunc는
이 Handler를 함수 형태로 감싸주는 헬퍼다.
Request와 Response 기본 사용
Request에서 값 읽기
r.Method
r.URL.Path
r.URL.Query().Get("id")
id := r.URL.Query().Get("id")
- 쿼리 파라미터 접근
- 헤더, 바디도 모두 r에서 읽는다
Response 작성하기
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
혹은 단순히
fmt.Fprintln(w, "ok")
⚠️ 주의할 점
- WriteHeader는 한 번만 호출
- Write가 먼저 호출되면 상태 코드는 자동으로 200
JSON 응답 만들기
API 서버에서는 문자열보다
JSON 응답이 기본이다.
type PingResponse struct {
Message string `json:"message"`
}
func pingHandler(w http.ResponseWriter, r *http.Request) {
resp := PingResponse{Message: "pong"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
이 패턴은 이후 거의 모든 API에서 반복된다.
[이미지: Go JSON 응답 처리 흐름]
메서드 분기 처리
하나의 엔드포인트에서
HTTP 메서드를 나눠 처리하는 경우도 많다.
func userHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// 조회
case http.MethodPost:
// 생성
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
처음에는 단순하지만,
엔드포인트가 늘어나면 이 방식은 금방 복잡해진다.
그래서 이후에 라우팅 구조를 정리하게 된다.
서버 종료 처리 (graceful shutdown)
운영 환경에서는
서버를 그냥 끊어버리면 문제가 된다.
server := &http.Server{
Addr: ":8080",
}
go server.ListenAndServe()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
<-ctx.Done()
stop()
server.Shutdown(context.Background())
- 새로운 요청은 받지 않음
- 진행 중인 요청은 마무리
실무 서버에서는
이 패턴이 거의 필수에 가깝다.
[이미지: Go graceful shutdown 흐름]
기본적인 구조 정리
실무에서는 보통 이런 형태로 정리된다.
cmd/server/main.go
internal/handler/
internal/service/
- main: 서버 설정, 라우팅
- handler: HTTP 요청/응답 처리
- service: 비즈니스 로직
이렇게 나누면
테스트, 유지보수 모두 훨씬 수월해진다.
이 단계에서 중요한 포인트
⚠️ 중요한 기준
- 처음부터 프레임워크에 의존하지 않는다
- HTTP 흐름을 직접 한 번은 경험한다
- “동작 원리”를 이해하는 데 집중한다
여기서 익힌 개념이 있으면,
이후 Gin이나 Echo를 써도
“왜 이렇게 동작하는지”가 보이기 시작한다.
정리
net/http 기반 API 서버는
Go 서버 개발의 가장 기본적인 출발점이다.
- Handler 구조 이해
- Request / Response 흐름 파악
- JSON 응답 패턴 익히기
- graceful shutdown까지 경험
다음 챕터에서는 이 서버를 조금 더 현실적으로 확장해서
라우팅 분리, 미들웨어 개념, 간단한 REST API 구조로 이어가면 좋다.
'backend' 카테고리의 다른 글
| Go HTTP 서버 확장하기: 라우팅 분리와 미들웨어 개념 정리 (0) | 2026.01.30 |
|---|---|
| Go로 간단한 HTTP API 서버 만들기: net/http 기반 기본 구조 이해하기 (0) | 2026.01.27 |
| Go 성능 분석과 최적화 입문: pprof로 병목 지점 찾는 방법 (1) | 2026.01.24 |
| Go 빌드와 실행, 배포 흐름 정리: 로컬부터 운영까지 한 번에 이해하기 (0) | 2026.01.23 |
| Go 테스트 코드 작성 정리: testing 패키지와 Go식 테스트 문화 (1) | 2026.01.22 |