본문 바로가기
Language/Go

Go - Go 와 Gin 을 활용한 RESTful API 개발

by ocwokocw 2021. 11. 28.

참조: https://go.dev/doc/tutorial/web-service-gin

- 개요

이번 튜토리얼에서는 Go와  Gin Web Framework(Gin) 을 활용하여 RESTful 웹 서비스 API 의 기본작성법을 알아보도록 한다.
 
Gin 은 웹서비스를 포함한 웹 어플리케이션을 구축하는데 있어 연관된 많은 작업들을 간소화시킨다. 이번 튜토리얼에서는 Gin을 이용해서 요청을 route 하고, 요청 세부사항을 추출하고 응답을 위해 JSON 을 마샬링한다.

- API endpoints 설계

빈티지 레코딩을 파는 상점에 접근하는 API 를 제공해야 한다고 가정해보자. 이를 위해 사용자가 앨범을 가져오거나 추가할 수 있는 endpoint 를 제공하도록 한다. API 를 개발할때에는 일반적으로 endpoint 설계부터 시작한다.
 
/albums
GET - 모든 앨범 목록을 가져오며, JSON 형으로 데이터를 반환한다. POST - JSON 으로 전송한 데이터로부터 새로운 앨범을 추가한다. 
 
/albums/:id
GET - ID 정보로 단건의 앨범을 얻는다. JSON 형으로 앨범 데이터를 반환한다.

- Directory 생성 및 초기화

directory 를 생성하고 module 초기화를 해준다.
 
hellgo % mkdir web-service-gin
hellgo % cd web-service-gin 
web-service-gin % go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin

- Test 데이터 생성

일반적으로는 데이터는 DB 에 저장되어 있지만 튜토리얼을 간소화하기 위해, 메모리에 데이터를 저장하도록 한다. main.go 파일을 생성하고 package main 을 선언한다. 그 아래에는 album struct 를 선언한다.
 
// album represents data about a record album.
type album struct {
    ID     string  `json:"id"`
    Title  string  `json:"title"`
    Artist string  `json:"artist"`
    Price  float64 `json:"price"`
}
 
 
`json:"artist"` 는 struct 내용이 JSON 으로 serialized 될  때 field 명을 지정한다. 이게 없으면 JSON 은 struct 의 대문자 field 명을 사용하는데 일반적인 JSON 형식에는 맞지 않기 때문에 붙여주었다.
 
그리고 그 아래에는 test 데이터를 추가해주도록 한다.
 
// albums slice to seed record album data.
var albums = []album{
    {ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
    {ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
    {ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

- GET /albums 구현

사용자가 /albums GET 요청을 하면 모든 album 을 JSON 으로 반환하는 API 를 작성해보도록 하자. struct 코드 아래에 album 목록을 가져오기 위한 코드를 작성한다.
 
getAlbums 함수는 album struct 의 slice 로 부터 JSON 을 생성하고, response 에 JSON 을 보낸다. gin 을 사용하기 위해 아래부터 import 하도록 하자.
 
import (
	"github.com/gin-gonic/gin"
	"net/http"
)
 
 
그 후 getAlbums 함수를 작성해본다.
 
func getAlbums(c *gin.Context) {
	c.IndentedJSON(http.StatusOK, albums)
}
  • getAlbums 함수에서는 gin.Context 를 인자로 받았다. gin.Context 는 Gin 에서 가장 주요한 부분이다. 요청 세부사항을 전달하고, 유효성을 검사하며 JSON serialize 와 그 외의 작업도 수행한다.
  • struct 를 JSON 으로 serialize 하고 response 에 보내기 위해 Context.IndentedJSON 를 호출했다.
  • 만약 더 compact(공백이 없는)한 JSON 을 보내고 싶다면 Context.JSON 을 사용해도 되지만 IndentedJSON 함수가 디버깅이 수월하다. 또한 일반적인 상황에서는 크기차이가 많이 나지는 않는다.
main.go 파일 상단에 아래 코드를 복사하여 handler 함수에 endpoint 경로를 지정한다. 코드에 직관적으로 나타나있듯이 getAlbums 가 /albums endpoint 요청 경로를 다루도록 설정한다.
 
func main() {
	router := gin.Default();
	router.GET("/albums", getAlbums)

	router.Run("localhost:8080")
}
  • Default 를 이용하여 Gin router 를 초기화했다.
  • GET 함수를 이용하여 /albums GET HTTP 요청을 getAlbums handler 함수와 맵핑했다. getAlbums() 가 아니라 getAlbums 로 전달해야 한다. getAlbums() 는 호출한 결과이기 때문에 handler 함수를 맵핑하려면 함수명만 작성해야 한다.
  • Run 함수를 사용해서 router 를 http.Server 에 붙이고, 서버를 실행하였다.

- GET /albums Test

종속성으로 Gin 모듈을 추적해야 한다. 커맨드에서 go get 을 사용해서 github.com/gin-gonic/gin 모듈을 종속성으로 모듈에 추가한다.
 
go get . 은 "현재 directory 코드를 위한 종속성을 가져온다."는 의미이다.
go run . 으로 프로그램을 실행해보자.
 
web-service-gin % go run .
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /albums                   --> main.getAlbums (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on localhost:8080
 
 
HTTP 서버가 실행되었다. 이제 curl 을 사용하여 요청을 날려보자. 만약 curl 이 없거나 설치하기 귀찮다면 브라우저 주소창에 치거나 postman 을 이용해서 요청을 날려주면 되겠다.
 
hellgo % curl http://localhost:8080/albums
[
    {
        "id": "1",
        "title": "Blue Train",
        "artist": "John Coltrane",
        "price": 56.99
    },
    {
        "id": "2",
        "title": "Jeru",
        "artist": "Gerry Mulligan",
        "price": 17.99
    },
    {
        "id": "3",
        "title": "Sarah Vaughan and Clifford Brown",
        "artist": "Sarah Vaughan",
        "price": 39.99
    }
]% 

- POST /albums 구현

사용자가 /albums 에 대한 POST 요청을 날릴 때, 데이터가 등록되는 API 를 만들어보자. getAlbums 밑에 postAlbums 함수를 구현해보도록 하자.
 
func postAlbums(c *gin.Context) {
	var newAlbum album
	
	if err := c.BindJSON(&newAlbum); err != nil {
		return
	}
	
	albums = append(albums, newAlbum)
	c.IndentedJSON(http.StatusCreated, newAlbum)
}
  • Context.BindJSON 을 사용해서 request body 를 newAlbum 변수에 맵핑하였다.
  • albums slice 에 album 을 추가했다.
  • album 이 성공적으로 등록되었다는 의미로 상태코드 201(http.StatusCreated) 를 응답에 추가했다.
router.POST 로 /albums POST 요청을 postAlbums 함수와 맵핑해준다.
 
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)

- POST /albums test

go run . 으로 서버를 띄운 후 POST curl 요청을 만들어서 test 해본다.
 
hellgo % curl http://localhost:8080/albums \
> --include \
> --header "Content-Type: application/json" \
> --request "POST" \
> --data '{"id": "4", "title": "Test title", "artist": "Test artist", "price": 49.99}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Sun, 28 Nov 2021 14:03:10 GMT
Content-Length: 93

{
    "id": "4",
    "title": "Test title",
    "artist": "Test artist",
    "price": 49.99
}%
 
 
HTTP/1.1 201 Created 부터는 응답이고 그 이전까지는 curl POST 요청이다. curl GET 요청으로 전체 앨범 목록을 가져오면 추가한 id 4 데이터도 반환함을 확인해볼 수 있다.

- GET /albums/:id 구현

모든 album 목록이 아니라 특정 요소에 대해서만 조회하도록 해보자. 이를 위해 id path parameter 를 사용한다.
 
postAlbums 밑에 getAlbumByID 함수를 추가한다. request path 에서 ID 를 추출한 다음 해당 ID 와 맞는 앨범을 반환한다.
 
func getAlbumByID(c *gin.Context) {
	id := c.Param("id")

	for _, a := range albums {
		if a.ID == id {
			c.IndentedJSON(http.StatusOK, a)
			return
		}
	}

	c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found."})
}
  • Context.Param 을 사용해서 URL 로 부터 id path parameter 를 추출했다.
  • albums slice 를 순회하면서 해당 ID 와 매칭되는 album이 있으면 해당 album 을 JSON 으로 serialize 하고 200 을 반환했다.
  • 만약 album 을 찾을 수 없다면 404 를 반환하였다.
getAlbumByID 도 핸들러로 추가해준다.
 
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)

- GET /albums/:id test

존재하는 album id 와 존재하지 않는 album id 를 GET 요청으로 날리면 결과는 아래와 같다.
 
hellgo % curl "http://localhost:8080/albums/2"
{
    "id": "2",
    "title": "Jeru",
    "artist": "Gerry Mulligan",
    "price": 17.99
}%                                                                                                                                                                                                                                      hellgo % curl "http://localhost:8080/albums/44"
{
    "message": "album not found."
}%      

'Language > Go' 카테고리의 다른 글

Go - Iteration & Benchmark  (0) 2021.12.04
Go - Go with TDD  (0) 2021.12.04
Go - compile 과 install  (0) 2021.11.28
Go - test  (0) 2021.11.28
Go - multiple input 과 return  (0) 2021.11.28

댓글