[Go 2주 완성 #08] Day 14: 실전 미니 프로젝트 - REST API 서버 구축

[Go 2주 완성 #08] Day 14: 실전 미니 프로젝트 - REST API 서버 구축

이 글의 핵심

net/http로 REST API를 만들고 JSON 직렬화·핸들러·고루틴·에러 처리를 한 프로젝트에 묶습니다. 2주 커리큘럼 마무리 실전편, Day 14입니다.

시리즈 안내

📚 Go 2주 완성 시리즈 #08 (최종편) | 전체 목차 보기

이 글은 C++ 개발자를 위한 2주 완성 Go 언어 커리큘럼Day 14 내용입니다.

이전: #07 테스팅 ← | → 실무 심화: #09 context·우아한 종료


들어가며: 2주의 결실

지금까지 다룬 변수, 슬라이스, 구조체, 인터페이스, 에러 처리, 고루틴, 채널, 테스팅을 묶어 실전 REST API 서버를 구성해 봅니다. 한 바퀴 완성하면 Go로 서비스 코드를 시작할 때 참고할 뼈대가 됩니다.

프로젝트 개요:

  • TODO API 서버: CRUD 기능 완비
  • 표준 라이브러리만 사용: net/http, encoding/json
  • 동시성 활용: 백그라운드 작업 처리
  • 테스트 포함: 유닛 테스트와 통합 테스트

실무에서의 체감

C++ 위주로 서버를 다루던 환경에서 Go를 도입할 때 흔히 드는 인상은 문법과 툴체인이 단순해 보인다는 점입니다. 프로덕션에서는 그 단순함이 빌드·배포·동시성 코드 가독성으로 이어지는 경우가 많습니다.

자주 언급되는 장점:

  • 개발 속도: 팀·도메인에 따라 다르지만, 네트워크·CLI 코드를 빠르게 완성하기 쉬운 편입니다.
  • 안정성: GC가 있어 수동 할당 해제 부담이 줄어듭니다.
  • 배포: 단일 바이너리로 옮기기 쉬운 구조입니다.

목차

  1. 프로젝트 구조
  2. 데이터 모델과 저장소
  3. HTTP 핸들러 구현
  4. 라우팅과 미들웨어
  5. 백그라운드 작업
  6. 테스트 작성
  7. 실행과 배포

1. 프로젝트 구조

# 프로젝트 초기화
mkdir todo-api
cd todo-api
go mod init todo-api

# 디렉토리 구조
todo-api/
├── go.mod
├── main.go
├── handler.go
├── handler_test.go
├── store.go
└── store_test.go

2. 데이터 모델과 저장소

모델 정의

// model.go
package main

import "time"

type Todo struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Completed bool      `json:"completed"`
    CreatedAt time.Time `json:"created_at"`
}

type CreateTodoRequest struct {
    Title string `json:"title"`
}

type UpdateTodoRequest struct {
    Completed bool `json:"completed"`
}

저장소 구현

// store.go
package main

import (
    "errors"
    "sync"
    "time"
)

var (
    ErrNotFound = errors.New("todo not found")
)

type TodoStore interface {
    Create(title string) (*Todo, error)
    GetAll() ([]*Todo, error)
    GetByID(id int) (*Todo, error)
    Update(id int, completed bool) error
    Delete(id int) error
}

type InMemoryStore struct {
    mu     sync.RWMutex
    todos  map[int]*Todo
    nextID int
}

func NewInMemoryStore() *InMemoryStore {
    return &InMemoryStore{
        todos:  make(map[int]*Todo),
        nextID: 1,
    }
}

func (s *InMemoryStore) Create(title string) (*Todo, error) {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    todo := &Todo{
        ID:        s.nextID,
        Title:     title,
        Completed: false,
        CreatedAt: time.Now(),
    }
    
    s.todos[s.nextID] = todo
    s.nextID++
    
    return todo, nil
}

func (s *InMemoryStore) GetAll() ([]*Todo, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    
    todos := make([]*Todo, 0, len(s.todos))
    for _, todo := range s.todos {
        todos = append(todos, todo)
    }
    
    return todos, nil
}

func (s *InMemoryStore) GetByID(id int) (*Todo, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    
    todo, ok := s.todos[id]
    if !ok {
        return nil, ErrNotFound
    }
    
    return todo, nil
}

func (s *InMemoryStore) Update(id int, completed bool) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    todo, ok := s.todos[id]
    if !ok {
        return ErrNotFound
    }
    
    todo.Completed = completed
    return nil
}

func (s *InMemoryStore) Delete(id int) error {
    s.mu.Lock()
    defer s.mu.Unlock()
    
    if _, ok := s.todos[id]; !ok {
        return ErrNotFound
    }
    
    delete(s.todos, id)
    return nil
}

3. HTTP 핸들러 구현

// handler.go
package main

import (
    "encoding/json"
    "errors"
    "fmt"
    "net/http"
    "strconv"
    "strings"
)

type TodoHandler struct {
    store TodoStore
}

func NewTodoHandler(store TodoStore) *TodoHandler {
    return &TodoHandler{store: store}
}

// GET /todos - 모든 TODO 조회
func (h *TodoHandler) GetTodos(w http.ResponseWriter, r *http.Request) {
    todos, err := h.store.GetAll()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(todos)
}

// GET /todos/{id} - 특정 TODO 조회
func (h *TodoHandler) GetTodo(w http.ResponseWriter, r *http.Request) {
    id, err := extractID(r.URL.Path)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    
    todo, err := h.store.GetByID(id)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            http.Error(w, "Todo not found", http.StatusNotFound)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(todo)
}

// POST /todos - 새 TODO 생성
func (h *TodoHandler) CreateTodo(w http.ResponseWriter, r *http.Request) {
    var req CreateTodoRequest
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    if req.Title == "" {
        http.Error(w, "Title is required", http.StatusBadRequest)
        return
    }
    
    todo, err := h.store.Create(req.Title)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(todo)
}

// PUT /todos/{id} - TODO 업데이트
func (h *TodoHandler) UpdateTodo(w http.ResponseWriter, r *http.Request) {
    id, err := extractID(r.URL.Path)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    
    var req UpdateTodoRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }
    
    if err := h.store.Update(id, req.Completed); err != nil {
        if errors.Is(err, ErrNotFound) {
            http.Error(w, "Todo not found", http.StatusNotFound)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusNoContent)
}

// DELETE /todos/{id} - TODO 삭제
func (h *TodoHandler) DeleteTodo(w http.ResponseWriter, r *http.Request) {
    id, err := extractID(r.URL.Path)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }
    
    if err := h.store.Delete(id); err != nil {
        if errors.Is(err, ErrNotFound) {
            http.Error(w, "Todo not found", http.StatusNotFound)
            return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusNoContent)
}

// 헬퍼: URL에서 ID 추출
func extractID(path string) (int, error) {
    parts := strings.Split(path, "/")
    if len(parts) < 3 {
        return 0, errors.New("invalid path")
    }
    
    id, err := strconv.Atoi(parts[len(parts)-1])
    if err != nil {
        return 0, err
    }
    
    return id, nil
}

4. 라우팅과 미들웨어

// main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// 로깅 미들웨어
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        log.Printf("%s %s", r.Method, r.URL.Path)
        
        next(w, r)
        
        log.Printf("Completed in %v", time.Since(start))
    }
}

// CORS 미들웨어
func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
        
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next(w, r)
    }
}

func main() {
    store := NewInMemoryStore()
    handler := NewTodoHandler(store)
    
    // 라우팅
    mux := http.NewServeMux()
    
    // /todos 엔드포인트
    mux.HandleFunc("/todos", corsMiddleware(loggingMiddleware(
        func(w http.ResponseWriter, r *http.Request) {
            switch r.Method {
            case http.MethodGet:
                handler.GetTodos(w, r)
            case http.MethodPost:
                handler.CreateTodo(w, r)
            default:
                http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
            }
        },
    )))
    
    // /todos/{id} 엔드포인트
    mux.HandleFunc("/todos/", corsMiddleware(loggingMiddleware(
        func(w http.ResponseWriter, r *http.Request) {
            switch r.Method {
            case http.MethodGet:
                handler.GetTodo(w, r)
            case http.MethodPut:
                handler.UpdateTodo(w, r)
            case http.MethodDelete:
                handler.DeleteTodo(w, r)
            default:
                http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
            }
        },
    )))
    
    // 헬스 체크
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        fmt.Fprintln(w, "OK")
    })
    
    // 백그라운드 작업 시작
    go backgroundWorker(store)
    
    // 서버 시작
    addr := ":8080"
    log.Printf("Server starting on %s", addr)
    if err := http.ListenAndServe(addr, mux); err != nil {
        log.Fatal(err)
    }
}

5. 백그라운드 작업

// background.go
package main

import (
    "log"
    "time"
)

func backgroundWorker(store TodoStore) {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            // 주기적 작업: 통계 출력
            todos, err := store.GetAll()
            if err != nil {
                log.Printf("Background worker error: %v", err)
                continue
            }
            
            completed := 0
            for _, todo := range todos {
                if todo.Completed {
                    completed++
                }
            }
            
            log.Printf("Stats: Total=%d, Completed=%d, Pending=%d",
                len(todos), completed, len(todos)-completed)
        }
    }
}

// 만료된 TODO 정리 (예시)
func cleanupWorker(store TodoStore) {
    ticker := time.NewTicker(1 * time.Hour)
    defer ticker.Stop()
    
    for range ticker.C {
        todos, err := store.GetAll()
        if err != nil {
            log.Printf("Cleanup error: %v", err)
            continue
        }
        
        now := time.Now()
        for _, todo := range todos {
            // 7일 이상 된 완료 항목 삭제
            if todo.Completed && now.Sub(todo.CreatedAt) > 7*24*time.Hour {
                if err := store.Delete(todo.ID); err != nil {
                    log.Printf("Delete error: %v", err)
                }
            }
        }
    }
}

6. 테스트 작성

저장소 테스트

// store_test.go
package main

import (
    "errors"
    "testing"
)

func TestInMemoryStore_Create(t *testing.T) {
    store := NewInMemoryStore()
    
    todo, err := store.Create("Test todo")
    if err != nil {
        t.Fatal(err)
    }
    
    if todo.ID != 1 {
        t.Errorf("got ID %d; want 1", todo.ID)
    }
    
    if todo.Title != "Test todo" {
        t.Errorf("got title %s; want 'Test todo'", todo.Title)
    }
    
    if todo.Completed {
        t.Error("new todo should not be completed")
    }
}

func TestInMemoryStore_GetAll(t *testing.T) {
    store := NewInMemoryStore()
    
    // 초기에는 비어있음
    todos, err := store.GetAll()
    if err != nil {
        t.Fatal(err)
    }
    if len(todos) != 0 {
        t.Errorf("got %d todos; want 0", len(todos))
    }
    
    // 3개 추가
    store.Create("Todo 1")
    store.Create("Todo 2")
    store.Create("Todo 3")
    
    todos, err = store.GetAll()
    if err != nil {
        t.Fatal(err)
    }
    if len(todos) != 3 {
        t.Errorf("got %d todos; want 3", len(todos))
    }
}

func TestInMemoryStore_Update(t *testing.T) {
    store := NewInMemoryStore()
    
    todo, _ := store.Create("Test")
    
    // 완료로 업데이트
    err := store.Update(todo.ID, true)
    if err != nil {
        t.Fatal(err)
    }
    
    // 확인
    updated, _ := store.GetByID(todo.ID)
    if !updated.Completed {
        t.Error("todo should be completed")
    }
}

func TestInMemoryStore_Delete(t *testing.T) {
    store := NewInMemoryStore()
    
    todo, _ := store.Create("Test")
    
    // 삭제
    err := store.Delete(todo.ID)
    if err != nil {
        t.Fatal(err)
    }
    
    // 확인
    _, err = store.GetByID(todo.ID)
    if !errors.Is(err, ErrNotFound) {
        t.Error("deleted todo should not be found")
    }
}

HTTP 핸들러 테스트

// handler_test.go
package main

import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestCreateTodo(t *testing.T) {
    store := NewInMemoryStore()
    handler := NewTodoHandler(store)
    
    reqBody := CreateTodoRequest{Title: "Test Todo"}
    body, _ := json.Marshal(reqBody)
    
    req := httptest.NewRequest(http.MethodPost, "/todos", bytes.NewBuffer(body))
    req.Header.Set("Content-Type", "application/json")
    
    w := httptest.NewRecorder()
    handler.CreateTodo(w, req)
    
    if w.Code != http.StatusCreated {
        t.Errorf("got status %d; want %d", w.Code, http.StatusCreated)
    }
    
    var todo Todo
    if err := json.NewDecoder(w.Body).Decode(&todo); err != nil {
        t.Fatal(err)
    }
    
    if todo.Title != "Test Todo" {
        t.Errorf("got title %s; want 'Test Todo'", todo.Title)
    }
}

func TestGetTodos(t *testing.T) {
    store := NewInMemoryStore()
    handler := NewTodoHandler(store)
    
    // 데이터 준비
    store.Create("Todo 1")
    store.Create("Todo 2")
    
    req := httptest.NewRequest(http.MethodGet, "/todos", nil)
    w := httptest.NewRecorder()
    
    handler.GetTodos(w, req)
    
    if w.Code != http.StatusOK {
        t.Errorf("got status %d; want %d", w.Code, http.StatusOK)
    }
    
    var todos []*Todo
    if err := json.NewDecoder(w.Body).Decode(&todos); err != nil {
        t.Fatal(err)
    }
    
    if len(todos) != 2 {
        t.Errorf("got %d todos; want 2", len(todos))
    }
}

7. 실행과 배포

로컬 실행

# 개발 모드 (자동 재시작 없음)
go run main.go handler.go store.go background.go

# 또는 빌드 후 실행
go build -o todo-api
./todo-api

API 테스트

# 헬스 체크
curl http://localhost:8080/health

# TODO 생성
curl -X POST http://localhost:8080/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Learn Go"}'

# 모든 TODO 조회
curl http://localhost:8080/todos

# 특정 TODO 조회
curl http://localhost:8080/todos/1

# TODO 업데이트
curl -X PUT http://localhost:8080/todos/1 \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

# TODO 삭제
curl -X DELETE http://localhost:8080/todos/1

Docker로 배포

# Dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN go build -o todo-api

FROM alpine:latest
RUN apk --no-cache add ca-certificates

WORKDIR /root/
COPY --from=builder /app/todo-api .

EXPOSE 8080
CMD ["./todo-api"]
# Docker 빌드 및 실행
docker build -t todo-api .
docker run -p 8080:8080 todo-api

프로덕션 개선 사항

// 환경 변수로 설정 관리
package main

import (
    "os"
    "strconv"
)

type Config struct {
    Port         string
    ReadTimeout  time.Duration
    WriteTimeout time.Duration
}

func LoadConfig() *Config {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    
    return &Config{
        Port:         port,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }
}

func main() {
    config := LoadConfig()
    
    server := &http.Server{
        Addr:         ":" + config.Port,
        Handler:      mux,
        ReadTimeout:  config.ReadTimeout,
        WriteTimeout: config.WriteTimeout,
    }
    
    log.Printf("Server starting on %s", server.Addr)
    if err := server.ListenAndServe(); err != nil {
        log.Fatal(err)
    }
}

정리: Day 14 학습 체크리스트

완료해야 할 항목

  • REST API 설계 (CRUD 엔드포인트)
  • net/http로 HTTP 서버 구현
  • JSON 직렬화/역직렬화
  • 에러 처리와 HTTP 상태 코드
  • 미들웨어 패턴 구현
  • 고루틴으로 백그라운드 작업
  • HTTP 핸들러 테스트
  • Docker로 배포

2주 완성 최종 체크리스트

1주 차:

  • ✅ Go 기본 문법과 철학
  • ✅ 포인터와 자료구조
  • ✅ 구조체와 메서드
  • ✅ 인터페이스와 다형성

2주 차:

  • ✅ 에러 처리와 defer
  • ✅ 고루틴과 채널
  • ✅ 의존성 관리와 테스팅
  • ✅ 실전 REST API 프로젝트

C++에서 Go로 전환 완료!

graph TD
    A[C++ 개발자] --> B[Day 1-2: 기본 문법]
    B --> C[Day 3-4: 자료구조]
    C --> D[Day 5-6: 객체지향]
    D --> E[Day 7: 인터페이스]
    E --> F[Day 8-9: 에러 처리]
    F --> G[Day 10-11: 동시성]
    G --> H[Day 12-13: 테스팅]
    H --> I[Day 14: 실전 프로젝트]
    I --> J[Go 개발자!]
    
    style A fill:#ffcccc
    style J fill:#ccffcc

다음 학습 방향

프로젝트를 완성했다면:

  1. 데이터베이스 연동: PostgreSQL, MySQL, MongoDB
  2. 인증/인가: JWT, OAuth2
  3. 고급 패턴: 미들웨어 체인, 의존성 주입
  4. 성능 최적화: 프로파일링, 캐싱
  5. 배포: Kubernetes, Cloud Run, AWS Lambda

추천 프로젝트:

  • CLI 도구 (cobra 라이브러리)
  • 웹 크롤러 (colly 라이브러리)
  • 채팅 서버 (WebSocket)
  • gRPC 서버
  • 마이크로서비스

마무리: 2주 완성을 축하합니다!

당신이 이룬 것

2주 전, C++만 알던 당신이 이제는:

  • ✅ Go 문법을 자유롭게 사용
  • ✅ 고루틴으로 동시성 프로그래밍
  • ✅ 채널로 안전한 통신
  • ✅ 인터페이스로 유연한 설계
  • ✅ REST API 서버 구축

C++과 Go, 언제 무엇을 쓸까?

C++를 선택해야 할 때:

  • 극저지연이 필요한 시스템 (HFT, 게임 엔진)
  • 하드웨어 제어 (임베디드, 드라이버)
  • 레거시 코드베이스 유지보수
  • 메모리 사용량이 극도로 제한적인 환경

Go를 선택해야 할 때:

  • 웹 API, 마이크로서비스
  • CLI 도구, DevOps 도구
  • 클라우드 네이티브 애플리케이션
  • 동시성이 중요한 서버
  • 빠른 개발과 배포가 중요한 프로젝트

계속 성장하기

// Go 개발자로서의 다음 단계
package main

func main() {
    skills := []string{
        "Go 고급 패턴",
        "데이터베이스 연동",
        "gRPC와 Protobuf",
        "Kubernetes 연동",
        "성능 최적화",
    }
    
    for _, skill := range skills {
        go learn(skill)  // 병렬로 학습!
    }
}

📚 시리즈 네비게이션

이전 글목차완료!
← #07 테스팅📑 전체 목차🎉 시리즈 완료!

Go 2주 완성 시리즈: 커리큘럼#01 기본 문법#02 자료구조#03 객체지향#04 인터페이스#05 에러 처리#06 고루틴·채널#07 테스팅#08 REST API#09 context·우아한 종료

다음 읽을 글:

  • [Go 심화 #09] context.Context·타임아웃·우아한 종료 — HTTP·배포 시 필수
  • C++ 개발자의 뇌 구조로 이해하는 Go 언어 - 더 깊은 개념 매핑
  • C++ vs Go 성능·동시성 비교 - 성능 벤치마크

한 줄 요약: 2주간의 학습으로 C++ 개발자에서 Go 개발자로 전환 완료! 이제 클라우드 네이티브 시대의 필수 언어를 마스터했습니다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [Go 2주 완성 #07] Day 12~13: 의존성 관리와 테스팅 - CMake보다 쉬운 세상
  • [Go 심화 #09] context·우아한 종료
  • C++ 개발자를 위한 2주 완성 Go 언어(Golang) 마스터 커리큘럼
  • C++ REST API 클라이언트 완벽 가이드 | CRUD·인증·에러 처리·프로덕션 패턴 [#21-3]

이 글에서 다루는 키워드 (관련 검색어)

Go REST API, net/http, encoding/json, Go 웹서버, 미니 프로젝트, Golang API, Go 2주 완성, C++ 개발자 Go 등으로 검색하시면 이 글이 도움이 됩니다.


🎉 시리즈 코어(2주) 완료를 축하합니다!

Go 2주 완성 시리즈 본편(#01~#08)을 마치셨습니다. 이제 실전 프로젝트에 Go를 적용해 보세요.

프로덕션으로 한 단계 올리기(강력 권장):

  • #09 context.Context·타임아웃·우아한 종료WithTimeout, r.Context, http.Server.Shutdown

더 깊이 배우기:

  • C++ 개발자의 뇌 구조로 이해하는 Go - 개념 매핑 심화
  • C++ vs Go 성능·동시성 비교 - 성능 벤치마크

다른 시리즈:

  • C++ 실전 가이드 시리즈
  • C++ 고성능 네트워크 가이드

실전 팁

실무에서 바로 적용할 수 있는 팁입니다.

디버깅 팁

  • 문제가 발생하면 먼저 컴파일러 경고를 확인하세요
  • 간단한 테스트 케이스로 문제를 재현하세요

성능 팁

  • 프로파일링 없이 최적화하지 마세요
  • 측정 가능한 지표를 먼저 설정하세요

코드 리뷰 팁

  • 코드 리뷰에서 자주 지적받는 부분을 미리 체크하세요
  • 팀의 코딩 컨벤션을 따르세요


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. 2주간 배운 Go 언어의 모든 것을 통합하여 완전한 REST API 서버를 구축합니다. net/http, JSON 처리, 고루틴 활용, 에러 처리까지 실무에 바로 적용 가능한 프로젝트입니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


관련 글

  • [Go 심화 #09] context.Context로 타임아웃·취소·우아한 종료 다루기 — C++와의 비교
  • Python REST API | Flask/Django로 API 서버 만들기
  • C++ 개발자를 위한 2주 완성 Go 언어(Golang) 마스터 커리큘럼
  • C++ HTTP 클라이언트 완벽 가이드 | REST API 호출·연결 풀·타임아웃·프로덕션 패턴
  • C++ JSON 파싱 완벽 가이드 | nlohmann·RapidJSON·커스텀 타입·에러 처리·프로덕션 패턴