Go 기초부터 HTTP CRUD API까지
by rowing0328Intro
이번 포스팅에서는 LitmusChaos Korea Mentorship 3주 차 실습으로 진행한 Go 기초 내용을 정리한다.
실습은 단순히 문법을 나열하는 방식이 아니라,
`Hello, Go!` 출력부터 시작해 변수, 조건문, 반복문, 함수, 에러 처리, 컬렉션, 구조체와 메서드를 거쳐
마지막에는 Go 표준 라이브러리만으로 HTTP CRUD API를 구현하는 흐름으로 구성되어 있다.
전체 예제는 Go 1.22 이상을 기준으로 작성되어 있으며,
각 폴더는 독립적으로 `go run`이 가능하고 `go test ./...`로 전체 예제를 검증할 수 있다.
실습 환경 준비
먼저 Go가 설치되어 있는지 확인한다.
go version
정상적으로 설치되어 있다면 다음과 비슷한 결과가 출력된다.
go version go1.xx.x linux/amd64
macOS에서는 공식 설치 파일을 사용하거나 Homebrew로 설치할 수 있다.
brew install go
go version
Windows에서 WSL을 사용하는 경우에는 주의할 점이 있다.
PowerShell에 Go가 설치되어 있어도 WSL은 별도의 Linux 환경이기 때문에,
실습을 WSL에서 진행한다면 WSL 터미널 안에서 `go version`이 동작해야 한다.
저장소는 다음과 같이 준비한다.
git clone https://github.com/rowing0328/3week-golang.git
cd 3week-golang
Go Modules를 사용하는 프로젝트이므로 루트에는 `go.mod` 파일이 존재한다.
module github.com/LitmusChaos-Korea-Mentorship/3week-golang
go 1.22
`go.mod`가 이미 있다면 `go mod init`을 다시 실행할 필요는 없다.
전체 실습 흐름
3주 차 Go 실습은 다음 순서로 진행된다.
| 순서 | 폴더 | 주제 |
|---|---|---|
| 1 | `01-hello` | Go 프로그램 구조, `package main`, `fmt.Println` |
| 2 | `02-variables-types` | 변수 선언, 타입, 포맷 출력 |
| 3 | `03-conditionals-loops` | 조건문, `switch`, `for`, `range` |
| 4 | `04-functions-errors` | 함수, 다중 반환값, 에러 처리 |
| 5 | `05-collections` | 배열, 슬라이스, 맵 |
| 6 | `06-structs-methods` | 구조체, 메서드, 포인터 리시버 |
| 7 | `07-http-crud` | HTTP 서버, JSON, REST 스타일 CRUD |
각 실습은 폴더 단위로 실행할 수 있다.
go run ./01-hello
go run ./02-variables-types
go run ./03-conditionals-loops
go run ./04-functions-errors
go run ./05-collections
go run ./06-structs-methods
go run ./07-http-crud
전체 예제가 정상적으로 동작하는지 확인하려면 다음 명령을 사용한다.
go test ./...
1. Hello Go
첫 번째 실습은 Go 프로그램의 가장 기본적인 구조를 이해하는 것이다.
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
Go 파일은 반드시 `package` 선언으로 시작한다.
실행 가능한 프로그램을 만들 때는 `package main`을 사용하며,
프로그램의 시작점은 `func main()`이다.
`fmt`는 출력 기능을 제공하는 Go 표준 라이브러리 패키지다.
이번 실습에서는 단순 출력에서 한 걸음 더 나아가,
`io.Writer`를 받는 `run` 함수를 두고 `main`에서 `os.Stdout`을 넘겨 실행하는 형태로 작성되어 있다.
func run(w io.Writer) {
fmt.Fprintln(w, "Hello, Go!")
fmt.Fprintln(w, "Go 프로그램은 package main의 main 함수에서 시작합니다.")
fmt.Fprintln(w, "안녕하세요! 저는 Go를 배우는 멘티입니다.")
}
func main() {
run(os.Stdout)
}
이렇게 작성하면 출력 로직을 테스트하기 쉬워진다.
`main` 함수는 실제 실행을 담당하고, 핵심 로직은 `run` 함수에서 검증할 수 있기 때문이다.
2. 변수와 타입
두 번째 실습에서는 Go의 변수 선언 방식을 다룬다.
Go에서는 `var`를 사용해 타입을 명시할 수도 있고,
`:=`를 사용해 타입을 추론하게 만들 수도 있다.
var name string = "Alice"
var age int = 25
city := "Seoul"
score := 98.5
isStudent := true
주의할 점은 Go의 변수 선언 순서다.
Java나 C 계열 언어처럼 `String name`이 아니라,
Go에서는 `name string`처럼 변수 이름이 먼저 오고 타입이 뒤에 온다.
또한 `:=`는 함수 내부에서만 사용할 수 있는 단축 선언이다.
name := "Alice" // 새 변수 선언과 값 대입
name = "Bob" // 기존 변수 값 변경
반면 `=`는 이미 선언된 변수에 값을 다시 대입할 때 사용한다.
아직 선언되지 않은 변수에는 사용할 수 없다.
Go는 정적 타입 언어이기 때문에 한 번 정해진 변수 타입은 바뀌지 않는다.
예를 들어 `age := 25`로 선언했다면 `age = 26`은 가능하지만,
`age = "26"`처럼 문자열을 대입할 수는 없다.
출력에서는 `fmt.Printf`의 포맷을 함께 사용한다.
fmt.Fprintf(w, "type of score: %T\n", score)
fmt.Fprintf(w, "score with one decimal place: %.1f\n", score)
`%T`는 값의 타입을 출력하고, `%. 1f`는 실수를 소수점 한 자리까지 출력한다.
3. 조건문과 반복문
세 번째 실습에서는 `if`, `switch`, `for`, `range`를 다룬다.
Go의 조건문은 괄호 없이 작성한다.
if score >= 90 {
fmt.Fprintln(w, "grade: A")
} else if score >= 80 {
fmt.Fprintln(w, "grade: B")
} else {
fmt.Fprintln(w, "grade: F")
}
`switch`는 값에 따라 분기할 때 사용한다.
Go의 `switch`는 각 `case`가 기본적으로 자동 종료되므로,
다른 언어처럼 매번 `break`를 작성하지 않아도 된다.
switch day {
case "sat", "sun":
fmt.Fprintln(w, "weekend")
default:
fmt.Fprintln(w, "weekday")
}
반복문은 `for` 하나로 처리한다.
Go에는 `while` 키워드가 없다.
대신 `for 조건` 형태로 while처럼 사용할 수 있다.
n := 0
for n < 3 {
fmt.Fprintln(w, "n =", n)
n++
}
슬라이스를 순회할 때는 `range`를 사용한다.
fruits := []string{"apple", "banana", "cherry"}
for index, value := range fruits {
fmt.Fprintf(w, "[%d] %s\n", index, value)
}
실습 과제로는 1부터 100까지 숫자 중 짝수만 출력하는 반복문을 추가했다.
for number := 1; number <= 100; number++ {
if number%2 == 0 {
fmt.Fprintln(w, number)
}
}
4. 함수와 에러 처리
네 번째 실습에서는 함수 선언, 다중 반환값, 에러 처리 패턴을 다룬다.
Go 함수는 `func` 키워드로 선언한다.
func greet(name string) string {
return "안녕하세요, " + name + "님!"
}
Go의 특징 중 하나는 여러 값을 반환할 수 있다는 점이다.
이를 활용하면 결괏값과 에러를 함께 반환하는 함수를 만들 수 있다.
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("0으로 나눌 수 없습니다")
}
return a / b, nil
}
Go에서는 예외를 던지고 잡는 방식보다,
반환된 `error` 값을 직접 확인하는 코드가 일반적이다.
result, err := divide(10, 3)
if err != nil {
fmt.Fprintln(w, "error:", err)
} else {
fmt.Fprintf(w, "result: %.2f\n", result)
}
이 패턴은 Go 코드에서 매우 자주 등장한다.
처음에는 반복적으로 느껴질 수 있지만,
실패 가능성을 호출하는 쪽에서 명확하게 다룬다는 장점이 있다.
실습에서는 추가로 두 정수를 더하는 `add` 함수도 구현했다.
func add(a, b int) int {
return a + b
}
5. 배열, 슬라이스, 맵
다섯 번째 실습에서는 Go에서 데이터를 묶어 다루는 기본 자료구조를 살펴본다.
먼저 배열은 길이가 고정된 목록이다.
numbers := [3]int{10, 20, 30}
`[3] int`는 `int` 값을 정확히 3개 담는 배열 타입이다.
Go에서는 배열의 길이도 타입의 일부이므로 `[3] int`와 `[4] int`는 서로 다른 타입이다.
반면 슬라이스는 길이가 고정되지 않은 목록이다.
names := []string{"Alice", "Bob", "Carol"}
names = append(names, "Dave")
실무에서는 고정 길이가 중요한 경우가 아니라면 슬라이스를 더 자주 사용한다.
값 추가는 `append`를 사용하고,
반환된 새 슬라이스를 다시 변수에 저장해야 한다.
맵은 key-value 형태의 데이터를 다룰 때 사용한다.
scores := map[string]int{
"Alice": 90,
"Bob": 75,
}
scores["Carol"] = 88
scores["Mentee"] = 95
맵에서 값을 조회할 때는 값과 존재 여부를 함께 받을 수 있다.
aliceScore, ok := scores["Alice"]
if ok {
fmt.Fprintln(w, "Alice score:", aliceScore)
}
실습에서는 전체 점수의 평균을 계산하는 함수도 작성했다.
func averageScore(scores map[string]int) float64 {
if len(scores) == 0 {
return 0
}
total := 0
for _, score := range scores {
total += score
}
return float64(total) / float64(len(scores))
}
정수끼리 나누면 정수 나눗셈이 되기 때문에,
평균처럼 소수점이 필요한 값은 `float64`로 변환한 뒤 계산한다.
6. 구조체와 메서드
여섯 번째 실습에서는 관련 데이터를 하나의 타입으로 묶는 구조체를 다룬다.
type Person struct {
Name string
Age int
City string
Email string
}
구조체를 사용하면 여러 필드를 하나의 의미 있는 단위로 다룰 수 있다.
예제에서는 `Person` 타입에 자기소개, 성인 여부 확인, 생일 처리 메서드를 연결했다.
func (p Person) Introduce() string {
return fmt.Sprintf("저는 %s이고 %d살입니다. 사는 곳은 %s입니다. 이메일은 %s입니다.", p.Name, p.Age, p.City, p.Email)
}
func (p Person) IsAdult() bool {
return p.Age >= 18
}
func (p *Person) HaveBirthday() {
p.Age++
}
여기서 중요한 차이는 값 리시버와 포인터 리시버다.
`Introduce`와 `IsAdult`는 값을 읽기만 하므로 값 리시버를 사용했다.
반면 `HaveBirthday`는 `Age` 값을 실제로 증가시켜야 하므로 포인터 리시버를 사용했다.
person.HaveBirthday()
fmt.Fprintln(w, "after birthday:", person.Age)
포인터 리시버를 사용하면 메서드 내부에서 원본 구조체 값을 변경할 수 있다.
7. 표준 라이브러리로 HTTP CRUD API 만들기
마지막 실습에서는 Go 표준 라이브러리만 사용해 HTTP CRUD API를 만든다.
외부 웹 프레임워크 없이 `net/http`와 `encoding/json`만으로 서버를 구성한다.
사용자 데이터는 다음 구조체로 표현한다.
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
요청 본문은 별도의 `userRequest` 구조체로 받는다.
type userRequest struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
데이터 저장은 메모리 슬라이스를 사용하는 `Store`가 담당한다.
type Store struct {
users []User
nextID int
}
func NewStore() *Store {
return &Store{nextID: 1}
}
`Store`는 목록 조회, 단건 조회, 생성, 수정, 삭제 로직을 메서드로 제공한다.
func (s *Store) Create(req userRequest) User {
user := User{
ID: s.nextID,
Name: req.Name,
Age: req.Age,
Email: req.Email,
}
s.nextID++
s.users = append(s.users, user)
return user
}
HTTP 라우팅은 `http.NewServeMux`와 `http.HandleFunc` 방식으로 구성한다.
func newRouter(store *Store) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/users", usersHandler(store))
mux.HandleFunc("/users/", userByIDHandler(store))
return mux
}
서버 실행은 다음과 같다.
func main() {
store := NewStore()
fmt.Println("server started: http://localhost:8080")
if err := http.ListenAndServe(":8080", newRouter(store)); err != nil {
fmt.Println("server error:", err)
}
}
실행 후 다른 터미널에서 API를 테스트할 수 있다.
목록 조회
curl http://localhost:8080/users
나이 필터 목록 조회
curl http://localhost:8080/users?minAge=20
사용자 생성
curl -X POST http://localhost:8080/users \
-H 'Content-Type: application/json' \
-d '{"name":"Alice","age":25,"email":"alice@example.com"}'
단건 조회
curl http://localhost:8080/users/1
사용자 수정
curl -X PUT http://localhost:8080/users/1 \
-H 'Content-Type: application/json' \
-d '{"name":"Alice Kim","age":26,"email":"alice.kim@example.com"}'
사용자 삭제
curl -X DELETE http://localhost:8080/users/1
요청 JSON은 `json.NewDecoder`로 구조체에 디코딩한다.
var req userRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
응답 JSON은 `json.NewEncoder`로 작성한다.
func writeJSON(w http.ResponseWriter, status int, value any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(value)
}
에러 응답도 같은 JSON 형태로 통일했다.
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
이번 CRUD 예제에서 특히 좋았던 부분은 저장소 로직과 HTTP 핸들러 로직을 분리했다는 점이다.
`Store`는 데이터 처리에 집중하고,
핸들러는 HTTP 요청과 응답을 처리한다.
덕분에 저장소 로직과 HTTP 흐름을 각각 테스트하기 쉬워진다.
테스트로 실습 결과 검증하기
각 실습 폴더에는 `main_test.go`가 포함되어 있다.
전체 테스트는 다음 명령으로 실행한다.
go test ./...
실행 결과는 다음과 같이 모든 패키지가 통과해야 한다.
ok github.com/LitmusChaos-Korea-Mentorship/3week-golang/01-hello
ok github.com/LitmusChaos-Korea-Mentorship/3week-golang/02-variables-types
ok github.com/LitmusChaos-Korea-Mentorship/3week-golang/03-conditionals-loops
ok github.com/LitmusChaos-Korea-Mentorship/3week-golang/04-functions-errors
ok github.com/LitmusChaos-Korea-Mentorship/3week-golang/05-collections
ok github.com/LitmusChaos-Korea-Mentorship/3week-golang/06-structs-methods
ok github.com/LitmusChaos-Korea-Mentorship/3week-golang/07-http-crud
특히 HTTP CRUD 실습에서는 `httptest`를 사용해 실제 서버를 띄우지 않고도 핸들러를 검증한다.
테스트 범위에는 다음 내용이 포함된다.
- 사용자 생성, 목록 조회, 단건 조회, 수정, 삭제
- `minAge` 쿼리 파라미터 필터링
- 잘못된 JSON 요청 검증
- 필수 필드 누락 검증
- 잘못된 HTTP 메서드 처리
처음 Go를 배울 때 테스트 코드를 함께 보는 것은 큰 도움이 된다.
단순히 코드가 실행되는지만 확인하는 것이 아니라,
어떤 입력에 어떤 결과가 나와야 하는지를 명확히 이해할 수 있기 때문이다.
마무리
이번 3주 차 실습에서는 Go의 기초 문법을 단계적으로 익힌 뒤,
마지막에 HTTP CRUD API를 직접 구현해 보았다.
처음에는 `package main`, `func main()`, 변수 선언처럼 작은 개념에서 출발하지만,
각 개념이 쌓이면 `net/http` 기반 API 서버까지 만들 수 있다는 점을 확인할 수 있었다.
개인적으로 이번 실습에서 중요하게 느낀 포인트는 다음과 같다.
- Go는 문법이 단순하지만 타입과 에러 처리를 명확하게 요구한다.
- `if err != nil` 패턴은 Go 코드의 기본 흐름이다.
- 배열보다 슬라이스를 더 자주 사용하게 된다.
- 구조체와 메서드를 사용하면 데이터를 의미 있는 단위로 묶을 수 있다.
- 포인터 리시버는 원본 값을 변경해야 할 때 필요하다.
- 표준 라이브러리만으로도 기본적인 HTTP API 서버를 만들 수 있다.
- 테스트 가능한 구조로 코드를 나누면 학습 예제도 훨씬 이해하기 쉬워진다.
이번 실습은 Go를 처음 접하는 입장에서 기초 문법과
간단한 백엔드 API 흐름을 함께 익히기에 좋은 구성이라고 느꼈다.
다음 단계에서는 메모리 슬라이스 대신 실제 데이터베이스를 연결하거나,
동시성 처리를 위해 `goroutine`, `channel`, `sync.Mutex` 같은 Go의 핵심 기능을 추가로 학습해 보면 좋을 것 같다.
참고 자료:
Download and install - The Go Programming Language
Download and install - The Go Programming Language
Documentation Download and install Download and install Download and install Go quickly with the steps described here. For other content on installing, you might be interested in: Download Go installation Select the tab for your computer's operating system
go.dev
Effective Go - The Go Programming Language
Effective Go - The Go Programming Language
Documentation Effective Go Effective Go Note: This document was written for Go's release in 2009 and is not actively updated. While it remains a good guide for using the core language, it does not cover significant changes to the language (generics), ecosy
go.dev
'📌ETC > Development Log' 카테고리의 다른 글
| 유지보수 게시판 조회 성능 개선하기 (0) | 2026.05.19 |
|---|---|
| Mac에서 LitmusChaos 설치하기 (0) | 2026.05.10 |
| MinIO 자동화 운영을 위한 환경설정과 윈도우 배치 파일 작성 (3) | 2025.07.28 |
| MinIO로 버킷 관리부터 이벤트 자동화까지 (3) | 2025.07.10 |
| Jenkins Pipeline와 DooD를 결합한 CI&CD 환경 구성하기 (0) | 2025.02.21 |
블로그의 정보
커밋 사이의 기록
rowing0328