a story

Go스터디: 7주차(31장) 본문

Book Study/Tucker의 Go Programming

Go스터디: 7주차(31장)

한명 2023. 11. 9. 23:02

이 글은 골든래빗 ‘Tucker의 Go 언어 프로그래밍의 31장 써머리입니다.

이 책의 마지막 스토디 노트입니다.

Todo 리스트 웹 서비스 만들기

Todo 리스트 웹 서비스는 프론트 엔드 코드와 백엔드 코드로 나눠진다.

프론트 엔드는 웹서비스의 화면을 담당하고, 백엔드는 데이터와 로직을 담당한다.

구현 순서

  1. 먼저 RESTful API에 맞춰 서비스를 정의한다.
  2. Todo 구조체를 만든다.
  3. RESTful API에 맞춰 각 핸들러를 만든다.
  4. 화면을 구성하는 HTML 문서를 만든다.
  5. 프론트엔드 동작을 나타내는 자바스크립트 코드를 만든다.
  6. 웹 브라우저로 동작을 확인한다.

시작 하기 전에 웹서버를 만들기 앞서 gorilla/mux 외 두 가지 패키지를 더 설치한다.

  • urfave/negroni 패키지: 자주 사용되는 웹 핸들러를 제공하는 패키지이다. 추가로 로그 기능, panic 복구 기능, 파일 서버 기능을 제공한다.
  • unrolled/render 패키지: 웹 서버 응답으로 HTML, JSON, TEXT 같은 포맷을 간단히 사용할 수 있다.
$ go mod init goprojects/todo31
$ go get github.com/gorilla/mux
$ go get github.com/urfave/negroni
$ go get github.com/unrolled/render

 

이제 백엔드의 RESTful API를 아래와 같이 작성한다.

// ch31/ex31.1/ex31.1.go
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"sort"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/unrolled/render"
	"github.com/urfave/negroni"
)

var rd *render.Render

type Todo struct { // 할 일 정보를 담는 Todo 구조체
	ID        int    `json:"id,omitempty"` // json 포맷으로 변환 옵션 -> JSON 포맷으로 변환시 ID가 아닌 id로 변환됨
	Name      string `json:"name"`
	Completed bool   `json:"completed,omitempty"`
}

var todoMap map[int]Todo
var lastID int = 0

func MakeWebHandler() http.Handler { // 웹 서버 핸들러 생성
	rd = render.New()
	todoMap = make(map[int]Todo)
	mux := mux.NewRouter()
	mux.Handle("/", http.FileServer(http.Dir("public"))) // "/"" 경로에 요청이 들어올 때 public 아래 폴더의 파일을 제공하는 파일 서버
	// "/todos" 에 대해서 GET, POST, DELETE, PUT에 대한 핸들러 구현
	mux.HandleFunc("/todos", GetTodoListHandler).Methods("GET")
	mux.HandleFunc("/todos", PostTodoHandler).Methods("POST")
	mux.HandleFunc("/todos/{id:[0-9]+}", RemoveTodoHandler).Methods("DELETE")
	mux.HandleFunc("/todos/{id:[0-9]+}", UpdateTodoHandler).Methods("PUT")
	return mux
}

type Todos []Todo // ID로 정렬하는 인터페이스

func (t Todos) Len() int {
	return len(t)
}

func (t Todos) Swap(i, j int) {
	t[i], t[j] = t[j], t[i]
}

func (t Todos) Less(i, j int) bool {
	return t[i].ID > t[j].ID
}

func GetTodoListHandler(w http.ResponseWriter, r *http.Request) {
	list := make(Todos, 0)
	for _, todo := range todoMap {
		list = append(list, todo)
	}
	sort.Sort(list)
	rd.JSON(w, http.StatusOK, list) // ID로 정렬하여 전체 목록 반환
}

func PostTodoHandler(w http.ResponseWriter, r *http.Request) {
	var todo Todo
	err := json.NewDecoder(r.Body).Decode(&todo)
	if err != nil {
		log.Fatal(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	lastID++ // 새로운 ID로 등록하고 만든 Todo 반환
	todo.ID = lastID
	todoMap[lastID] = todo
	rd.JSON(w, http.StatusCreated, todo)
}

type Success struct {
	Success bool `json:"success"`
}

func RemoveTodoHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r) // ID에 해당하는 할 일 삭제
	id, _ := strconv.Atoi(vars["id"])
	if _, ok := todoMap[id]; ok {
		delete(todoMap, id)
		rd.JSON(w, http.StatusOK, Success{true})
	} else {
		rd.JSON(w, http.StatusNotFound, Success{false})
	}
}

func UpdateTodoHandler(w http.ResponseWriter, r *http.Request) {
	var newTodo Todo // ID에 해당하는 할 일 수정
	err := json.NewDecoder(r.Body).Decode(&newTodo)
	if err != nil {
		log.Fatal(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	if todo, ok := todoMap[id]; ok {
		todo.Name = newTodo.Name
		todo.Completed = newTodo.Completed
		rd.JSON(w, http.StatusOK, Success{true})
	} else {
		rd.JSON(w, http.StatusBadRequest, Success{false})
	}
}

func main() {
	m := MakeWebHandler()  // 기본 핸들러 (핸들러들이 등록된 mux가 반환됨)
	n := negroni.Classic() // negroni 기본 핸들러
	n.UseHandler(m)        // negroni 기본 핸들러로 만든 핸들러 MakeWebHandler 을 감싼다.
	// HTTP 요청 수신 시 negroni에서 제공하는 부가 기능 핸들러들을 수행하고 난 뒤, MakeWebHandler()를 수행한다.

	log.Println("Started App")
	err := http.ListenAndServe(":3000", n) // negroni 기본 핸들러가 동작함
	if err != nil {
		panic(err)
	}
}

 

프론트 엔드는 핵심이 아니므로 github(https://github.com/tuckersGo/musthaveGo/tree/master/ch31/ex31.1/public)의 파일을 참조해서 웹서버의 위치에 /public 폴더를 만들고 넣는다.

 

실행해보면 아래와 같이 실행된다.

 

negroni 를 사용해서 로그도 그럴싸하게 남는다.

$ go run .\\main.go
2023/11/09 22:57:37 Started App
[negroni] 2023-11-09T22:57:51+09:00 | 200 |      213.3309ms | localhost:3000 | GET /
[negroni] 2023-11-09T22:57:51+09:00 | 200 |      22.9704ms | localhost:3000 | GET /todo.css
[negroni] 2023-11-09T22:57:51+09:00 | 200 |      39.5097ms | localhost:3000 | GET /todo.js
[negroni] 2023-11-09T22:57:51+09:00 | 200 |      976.8µs | localhost:3000 | GET /todos
[negroni] 2023-11-09T22:57:51+09:00 | 404 |      257.9µs | localhost:3000 | GET /favicon.ico
[negroni] 2023-11-09T22:58:04+09:00 | 201 |      0s | localhost:3000 | POST /todos
[negroni] 2023-11-09T22:58:16+09:00 | 201 |      0s | localhost:3000 | POST /todos
[negroni] 2023-11-09T22:58:22+09:00 | 201 |      0s | localhost:3000 | POST /todos

'Book Study > Tucker의 Go Programming' 카테고리의 다른 글

Go스터디: 6주차(27~30장)  (0) 2023.11.05
Go스터디: 5주차(23~26장)  (0) 2023.10.29
Go스터디: 4주차(18~22장)  (1) 2023.10.22
Go스터디: 3주차(12~17장)  (1) 2023.10.15
Go스터디: 2주차(3~11장)  (1) 2023.10.08