backend

Go로 간단한 HTTP API 서버 만들기: net/http 기반 기본 구조 이해하기

mirabo01 2026. 1. 25. 22:17

성능 분석까지 다뤘다면, 이제는
지금까지 배운 내용을 실제 코드로 엮어보는 단계로 넘어가는 게 자연스럽다.

이번 챕터에서는
외부 프레임워크 없이 표준 라이브러리 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 구조로 이어가면 좋다.