본문 바로가기
Go

[go/gin] Basic REST API 만들기 튜토리얼 (1) : 서버 실행 및 데이터 바인딩

by weero 2021. 12. 16.

IDE : Goland(고랜드)

 

<Tucker의 Go 언어 프로그래밍>이라는 책을 참고해 Go 언어로 웹 서버를 만들어보려 한다. (웹에 대한 기본 지식을 전제함)

책에는 Gin 프레임워크에 대한 내용은 없어서 따로 찾아보면서 진행했다.

(REST API in Golang using Gin Gonic : https://www.youtube.com/playlist?list=PL8-bdB4cHmXynirCIPtW0G5mCnaoMfr5u)

 

00. Gin 패키지 설치

go get -u github.com/gin-gonic/gin

 

 

 

01. Gin으로 API 서버 만들기

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()	// default settings
	r.GET("", func(c *gin.Context) {
		c.String(http.StatusOK, "hello world!")	// 1: http status, 2: 응답으로 보내고 싶은 데이터
	})	// 1: url, 2: handler function

	r.Run("localhost:8080")	// api를 호스트할 url과 포트번호
}

gin.Deafult() 함수로 Gin 엔진을 만들 수 있다. 만약 로거가 없는 엔진을 사용하고 싶다면 New() 함수를 사용한다.

 

위 결과를 실행해보면 아래의 결과를 확인할 수 있다.

 

실행 결과

 

만약 JSON 형식을 보내고 싶다면 gin.H를 이용해 map 형식으로 보내주면 된다.

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()	// default settings
	r.GET("", func(c *gin.Context) {
		c.JSONP(http.StatusOK, gin.H{
			"responsibleData":"hello world!",
		}) // gin.H{} : map you can configure
	})	// 1: url, 2: handler function

	r.Run("localhost:8080")	// api를 호스트할 url과 포트번호
}

 

위의 결과를 웹브라우저에서 실행해도 되지만 더 정확한 결과를 확인해보고 싶다면 Postman 등의 툴을 사용하는 것이 더 좋다.

 

 

 

 

02. HTTP 파라미터 받기

 

main 함수에 아래 내용도 추가해준다.

	r.GET("/:name", func(c *gin.Context){ // :은 gin에게 url 이후에 오는 것이 name 변수로 받아진다는 것
		var val = c.Param("name") // 파라미터 name의 값을 변수 val의 값으로 초기화
		c.JSON(http.StatusOK, gin.H{
			"value":val,
		})

	})

 

실행 결과

 

로그에도 잘 들어와 있다.

 

url 뒤 파라미터로 들어가는 문자열은 공백도 허용된다. (하지만 파라미터에 /가 들어가면 404 에러가 발생하거나 원하는 결과가 나오지 않을 수 있다.)

 

 

 

03. Body로 들어오는 요청 처리

Body로 들어오는 요청을 처리하기 위해 모델 struct를 만들어준다.

type TestModel struct {
	Id int `json:"id" binding:"required"`
	Name string `json:"name" binding:"required"`
}

 

id와 name 값이 optional 하다면 binding을 빼주어도 되지만 필수라면 위처럼 binding:"required"를 추가해주면 된다.

 

위의 TestModel struct로 body 값을 파싱하려면 아래처럼 하면 된다.

	r.POST("/add", func(c *gin.Context) {
		var req = &Bind{}
        var data TestModel
		if err := c.ShouldBind(&data); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error":fmt.Sprintf("%v", err),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"data":data,
			})
		}
	})

Bind()나 shouldBind()를 통해 binding:"required"와 같이 처리한 키의 값이 안들어올 시 400 에러가 바로 처리된다.

위 코드에서 if문의 err 처리 부분이 없다면 TestModel에 값이 안 들어와도 통과되어 200이 나게 된다.

 

ShouldBind() + bind:"required"

err 만 return하고 따로 response 까지 날려주진 않는다. error를 처리하는 부분이 없다면 아래의 플로우도 그대로 실행하게 된다.

 

Bind() + binding:"required"

400 에러가 바로 난다.

 

ShouldBind()나 Bind()만 있을 때

에러가 잡히지 않는다.

 

Postman으로 실행해본 결과(정상 결과)

 

binding:"required" 였던 name의 값을 넣어주지 않았을 때

 

위 실행 결과에 대한 로그가 찍혀있다.

 

추가로 param과 query 모두 파싱할 경우는 아래처럼 한다.

 

Param

애초에 path에 있는 값이라 값이 비어있으면 다른 path로 인식하기 때문에 binding의 필요성이 불분명하다.

type Bind struct {
	Name string `uri:"name" binding:"required"`
}

r.POST("/:name", func(c *gin.Context) {
	req := &Bind{}
    err := c.BindUri(req)
    if err != nil
    	c.JSON(http.StatusBadRequest, err.Error())
        return
    }
    c.Status(http.StatusOK)
})

Query

key는 있어도 값을 주지 않으면 binding:"required"에서 걸리게 된다.

type Bind struct {
	Name string `form:"name" binding:"required"`
}

r.POST("/:name", func(c *gin.Context) {
	req := &Bind{}
    err := c.BindQuery(req)
    if err != nil
    	c.JSON(http.StatusBadRequest, err.Error())
        return
    }
    c.Status(http.StatusOK)
})

 

 

(이 부분 출처 : https://genius-kim-1047.tistory.com/59 )

 

 

전체 코드

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
)

type TestModel struct {
	Id   int    `json:"id" binding:"required"`
	Name string `json:"name" binding:"required"`
}

func main() {
	r := gin.Default() // default settings
	r.GET("", func(c *gin.Context) {
		c.JSONP(http.StatusOK, gin.H{
			"responsibleData": "hello world!",
		}) // gin.H{} : map you can configure
		//c.String(http.StatusOK, "hello world!")	// 1: http status, 2: 응답으로 보내고 싶은 데이터
	}) // 1: url, 2: handler function
	r.GET("/:name", func(c *gin.Context) { // :은 gin에게 url 이후에 오는 것이 name 변수로 받아진 다는 것
		var val = c.Param("name") // 파라미터 name의 값을 변수 val의 값으로 초기화
		c.JSON(http.StatusOK, gin.H{
			"value": val,
		})
	})
	r.POST("/add", func(c *gin.Context) {
		var data TestModel
		if err := c.ShouldBind(&data); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": fmt.Sprintf("%v", err),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"data": data,
			})
		}
	})

	r.Run("localhost:8080") // api를 호스트할 url과 포트번호
}