์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Go ์–ธ์–ด๋กœ RESTful API ๊ฐœ๋ฐœํ•˜๊ธฐ: ์ดˆ๋ณด์ž๋„ ์‰ฝ๊ฒŒ ๋”ฐ๋ผํ•˜๋Š” ์™„๋ฒฝ ๊ฐ€์ด๋“œ ๐Ÿš€

๐Ÿš€ Go ์–ธ์–ด๋กœ RESTful API ๊ฐœ๋ฐœํ•˜๊ธฐ: ์ดˆ๋ณด์ž๋„ ์‰ฝ๊ฒŒ ๋”ฐ๋ผํ•˜๋Š” ์™„๋ฒฝ ๊ฐ€์ด๋“œ ๐Ÿš€

GET /api/v1/users 200 OK { "users": [ { "id": 1, "name": "๊ณ ํผ", "email": "gopher@go.dev" }, { "id": 2, "name": "๊ณ ๋žญ", "email": "golang@go.dev" } ] }

์•ˆ๋…•ํ•˜์„ธ์š”, ๊ฐœ๋ฐœ์ž ์—ฌ๋Ÿฌ๋ถ„! ๐Ÿ™Œ ์˜ค๋Š˜์€ 2025๋…„ 3์›” 24์ผ ๊ธฐ์ค€์œผ๋กœ ์ตœ์‹  ํŠธ๋ Œ๋“œ๋ฅผ ๋ฐ˜์˜ํ•œ Go ์–ธ์–ด๋กœ RESTful API ๊ฐœ๋ฐœํ•˜๊ธฐ์— ๋Œ€ํ•ด ์•Œ์•„๋ณผ๊ฒŒ์š”. ์š”์ฆ˜ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์—์„œ Go(๊ณ  ์–ธ์–ด)๊ฐ€ ์ง„์งœ ํ•ซํ•˜๋‹ค๋Š” ๊ฑฐ ๋‹ค๋“ค ์•„์‹œ์ฃ ? ์†๋„ ๋น ๋ฅด๊ณ , ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ์ฉ”๊ณ , ์‹ฌํ”Œํ•œ ๋ฌธ๋ฒ•๊นŒ์ง€... ์ง„์งœ ๊ฐœ๋ฐœ์ž๋“ค ์‚ฌ์ด์—์„œ ์ธ๊ธฐ ํญ๋ฐœ์ด์—์š”! ใ…‹ใ…‹ใ…‹

์ด ๊ธ€์„ ํ†ตํ•ด Go๋กœ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋ฐฐ์›Œ๋ณผ ๊ฑด๋ฐ์š”, ์ฝ”๋“œ ํ•œ ์ค„ ์•ˆ ์จ๋ณธ ์ดˆ๋ณด์ž๋ถ„๋“ค๋„ ๊ฑฑ์ • ๋…ธ๋…ธ! ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”. ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ๊ฟ€ํŒ๋“ค๋„ ๊ฐ€๋“ ์ค€๋น„ํ–ˆ๋‹ต๋‹ˆ๋‹ค. ๐Ÿ˜Ž

๐Ÿ“Œ ์ด ๊ธ€์—์„œ ๋ฐฐ์šธ ๋‚ด์šฉ:

  1. Go ์–ธ์–ด์˜ ๊ธฐ๋ณธ ๊ฐœ๋…๊ณผ RESTful API ์†Œ๊ฐœ
  2. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •ํ•˜๊ธฐ
  3. ์ฒซ ๋ฒˆ์งธ Go API ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ
  4. ๋ผ์šฐํŒ…๊ณผ ํ•ธ๋“ค๋Ÿฌ ๊ตฌํ˜„ํ•˜๊ธฐ
  5. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ํ•˜๊ธฐ
  6. ๋ฏธ๋“ค์›จ์–ด์™€ ์ธ์ฆ ๊ตฌํ˜„ํ•˜๊ธฐ
  7. ํ…Œ์ŠคํŠธ์™€ ๋ฐฐํฌ ๋ฐฉ๋ฒ•
  8. ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์™„์ „ํ•œ RESTful API ๊ตฌํ˜„

1. Go ์–ธ์–ด์™€ RESTful API: ์ฐฐ๋–ก๊ถํ•ฉ์ธ ์ด์œ  ๐Ÿ’‘

Go ์–ธ์–ด(์ผ๋ช… Golang)๋Š” 2009๋…„์— ๊ตฌ๊ธ€์—์„œ ๋งŒ๋“  ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์ธ๋ฐ, 2025๋…„ ํ˜„์žฌ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์—์„œ ์™„์ „ ๋Œ€์„ธ๋กœ ์ž๋ฆฌ์žก์•˜์–ด์š”. ํŠนํžˆ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜๊ฐ€ ๋Œ€์„ธ์ธ ์š”์ฆ˜, Go๋Š” ๊ทธ ๋‹จ์ˆœํ•จ๊ณผ ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ์œผ๋กœ ๊ฐœ๋ฐœ์ž๋“ค์˜ ๋งˆ์Œ์„ ์‚ฌ๋กœ์žก๊ณ  ์žˆ์ฃ .

RESTful API๋Š” ์›น ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ๋ฐฉ์‹ ์ค‘ ํ•˜๋‚˜๋กœ, HTTP ํ”„๋กœํ† ์ฝœ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๊ณ  ์กฐ์ž‘ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ด์š”. Go ์–ธ์–ด๋Š” ์ด๋Ÿฐ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•˜๊ธฐ์— ์™„๋ฒฝํ•œ ์–ธ์–ด๋ž๋‹ˆ๋‹ค!

Go ์–ธ์–ด์˜ ํŠน์ง• ๋น ๋ฅธ ์ปดํŒŒ์ผ ์†๋„ ๊ฐ•๋ ฅํ•œ ๋™์‹œ์„ฑ ์ง€์› (๊ณ ๋ฃจํ‹ด) ๊ฐ„๊ฒฐํ•œ ๋ฌธ๋ฒ• ์ •์  ํƒ€์ž… ์–ธ์–ด ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๋‚ด์žฅ RESTful API ํŠน์ง• ์ƒํƒœ๊ฐ€ ์—†๋Š”(Stateless) ํ†ต์‹  ๋ฆฌ์†Œ์Šค ์ค‘์‹ฌ ์„ค๊ณ„ HTTP ๋ฉ”์„œ๋“œ ํ™œ์šฉ JSON/XML ๋ฐ์ดํ„ฐ ๊ตํ™˜ ์บ์‹œ ๊ฐ€๋Šฅํ•œ ์‘๋‹ต +

Go ์–ธ์–ด๊ฐ€ RESTful API ๊ฐœ๋ฐœ์— ์ฐฐ๋–ก๊ถํ•ฉ์ธ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•„์š”:

  1. ๋น ๋ฅธ ์‹คํ–‰ ์†๋„ - ์ปดํŒŒ์ผ ์–ธ์–ด๋ผ์„œ ์ธํ„ฐํ”„๋ฆฌํ„ฐ ์–ธ์–ด๋ณด๋‹ค ์‹คํ–‰ ์†๋„๊ฐ€ ๋น ๋ฅด๊ณ , ์„œ๋ฒ„ ์‘๋‹ต ์‹œ๊ฐ„์ด ์งง์•„์š”.
  2. ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ - ๊ณ ๋ฃจํ‹ด(Goroutine)๊ณผ ์ฑ„๋„(Channel)์„ ํ†ตํ•ด ๋™์‹œ์— ๋งŽ์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”.
  3. ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฐ•์  - HTTP ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์ด ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ด๋ฏธ ํฌํ•จ๋˜์–ด ์žˆ์–ด์š”.
  4. ๊ฐ„๊ฒฐํ•œ ์ฝ”๋“œ - ์ ์€ ์ฝ”๋“œ๋กœ ๋งŽ์€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์›Œ์š”.

์ด์ œ Go๋กœ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ•˜๋‚˜์”ฉ ์•Œ์•„๋ณผ๊ฒŒ์š”. ์ง„์งœ ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ๋‚ด์šฉ์œผ๋กœ ์ค€๋น„ํ–ˆ์œผ๋‹ˆ ๋๊นŒ์ง€ ๋”ฐ๋ผ์™€์ฃผ์„ธ์š”! ๐Ÿ˜„

2. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •ํ•˜๊ธฐ ๐Ÿ› ๏ธ

Go ์–ธ์–ด๋กœ API๋ฅผ ๊ฐœ๋ฐœํ•˜๊ธฐ ์ „์— ๋จผ์ € ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•ด์•ผ ํ•ด์š”. 2025๋…„ ๊ธฐ์ค€ ์ตœ์‹  ๋ฒ„์ „์€ Go 1.22.x์ด๋‹ˆ ์ด ๋ฒ„์ „์œผ๋กœ ์„ค์น˜ํ•ด๋ณด๋„๋ก ํ• ๊ฒŒ์š”!

2.1 Go ์„ค์น˜ํ•˜๊ธฐ

์šด์˜์ฒด์ œ๋ณ„ ์„ค์น˜ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•„์š”:

Windows:

1. https://golang.org/dl/์—์„œ Windows์šฉ MSI ์ธ์Šคํ†จ๋Ÿฌ๋ฅผ ๋‹ค์šด๋กœ๋“œ

2. ๋‹ค์šด๋กœ๋“œํ•œ ์ธ์Šคํ†จ๋Ÿฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์•ˆ๋‚ด์— ๋”ฐ๋ผ ์„ค์น˜


macOS:

Homebrew๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด:

brew install go

Linux:

sudo apt-get update
sudo apt-get install golang-go

์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์„ค์น˜ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”:

go version

์ด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด "go version go1.22.x [OS/์•„ํ‚คํ…์ฒ˜]"์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™€์•ผ ํ•ด์š”!

2.2 ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •ํ•˜๊ธฐ

Go๋Š” ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ๊ฐ€ ์ค‘์š”ํ•ด์š”. 2025๋…„ ํ˜„์žฌ๋Š” Go Modules๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ‘œ์ค€์ด ๋˜์—ˆ์–ด์š”. ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ  ๋ชจ๋“ˆ์„ ์ดˆ๊ธฐํ™”ํ•ด๋ณผ๊ฒŒ์š”:

mkdir go-rest-api
cd go-rest-api
go mod init github.com/yourusername/go-rest-api

์—ฌ๊ธฐ์„œ github.com/yourusername/go-rest-api ๋ถ€๋ถ„์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ ๊ฒฝ๋กœ๋กœ ๋ฐ”๊ฟ”์ฃผ์„ธ์š”. ์‹ค์ œ GitHub ๋ ˆํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ์—†์–ด๋„ ๊ดœ์ฐฎ์•„์š”. ๊ทธ๋ƒฅ ๋ชจ๋“ˆ ์‹๋ณ„์ž๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค!

2.3 ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜ํ•˜๊ธฐ

Go๋กœ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ ์œ ์šฉํ•œ ํŒจํ‚ค์ง€๋“ค์ด ์žˆ์–ด์š”. 2025๋…„ ํ˜„์žฌ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•ด๋ณผ๊ฒŒ์š”:

go get -u github.com/gin-gonic/gin
go get -u github.com/go-sql-driver/mysql
go get -u github.com/golang-jwt/jwt/v5
go get -u github.com/joho/godotenv

๊ฐ ํŒจํ‚ค์ง€์˜ ์—ญํ• ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•„์š”:

  1. gin: ๊ฐ€๋ณ๊ณ  ๋น ๋ฅธ HTTP ์›น ํ”„๋ ˆ์ž„์›Œํฌ
  2. go-sql-driver/mysql: MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“œ๋ผ์ด๋ฒ„
  3. jwt: JSON Web Token ์ธ์ฆ์„ ์œ„ํ•œ ํŒจํ‚ค์ง€
  4. godotenv: ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ .env ํŒŒ์ผ์—์„œ ๋กœ๋“œํ•˜๋Š” ํŒจํ‚ค์ง€

์ด์ œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •์ด ์™„๋ฃŒ๋˜์—ˆ์–ด์š”! ๋ณธ๊ฒฉ์ ์œผ๋กœ API ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ์š”? ๐Ÿ˜Ž

3. ์ฒซ ๋ฒˆ์งธ Go API ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ ๐ŸŒŸ

์ž, ์ด์ œ Go๋กœ ์ฒซ ๋ฒˆ์งธ API ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ์š”! ์ง„์งœ ๊ฐ„๋‹จํ•œ "Hello, World!" API๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋ณผ ๊ฑฐ์˜ˆ์š”.

3.1 ๊ธฐ๋ณธ ์„œ๋ฒ„ ๊ตฌํ˜„ํ•˜๊ธฐ

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์— main.go ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์„ธ์š”:

package main

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

func main() {
    // Gin ์—”์ง„ ์ดˆ๊ธฐํ™”
    r := gin.Default()
    
    // ๋ฃจํŠธ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ํ•ธ๋“ค๋Ÿฌ ์„ค์ •
    r.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "Hello, RESTful World!",
        })
    })
    
    // ์„œ๋ฒ„ ์‹œ์ž‘
    r.Run(":8080")
}

์ด ์ฝ”๋“œ๋Š” ์ •๋ง ๊ฐ„๋‹จํ•œ RESTful API ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฑฐ์˜ˆ์š”. gin.Default()๋กœ Gin ์—”์ง„์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ , ๋ฃจํŠธ ๊ฒฝ๋กœ("/")์— GET ์š”์ฒญ์ด ์˜ค๋ฉด JSON ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •ํ–ˆ์–ด์š”.

์ด์ œ ํ„ฐ๋ฏธ๋„์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ด๋ณด์„ธ์š”:

go run main.go

์„œ๋ฒ„๊ฐ€ ์‹œ์ž‘๋˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8080์— ์ ‘์†ํ•˜๊ฑฐ๋‚˜, curl ๋ช…๋ น์–ด๋กœ API๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜ ์žˆ์–ด์š”:

curl http://localhost:8080

์‘๋‹ต์œผ๋กœ {"message":"Hello, RESTful World!"}๊ฐ€ ๋‚˜์˜ค๋ฉด ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค! ์ง์ง์ง ๐Ÿ‘๐Ÿ‘๐Ÿ‘

๐Ÿ” Gin ํ”„๋ ˆ์ž„์›Œํฌ๋ž€?

Gin์€ Go ์–ธ์–ด๋กœ ์ž‘์„ฑ๋œ ์›น ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ๋น ๋ฅธ ์„ฑ๋Šฅ๊ณผ ๋ฏธ๋“ค์›จ์–ด ์ง€์›, ๋ผ์šฐํŒ… ๊ธฐ๋Šฅ ๋“ฑ์„ ์ œ๊ณตํ•ด์š”. Express.js(Node.js)๋‚˜ Flask(Python)์™€ ๋น„์Šทํ•œ ์—ญํ• ์„ ํ•˜์ง€๋งŒ, Go์˜ ์žฅ์ ์„ ์‚ด๋ ค ๋” ๋น ๋ฅธ ์„ฑ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. 2025๋…„ ํ˜„์žฌ Go ์›น ๊ฐœ๋ฐœ์—์„œ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ ์ค‘ ํ•˜๋‚˜์—์š”!

3.2 API ๊ตฌ์กฐ ๊ฐœ์„ ํ•˜๊ธฐ

์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ main.go์— ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ , ๊ตฌ์กฐํ™”๋œ ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์ด ์ข‹์•„์š”. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฐœ์„ ํ•ด๋ณผ๊ฒŒ์š”:

go-rest-api/
โ”œโ”€โ”€ main.go
โ”œโ”€โ”€ go.mod
โ”œโ”€โ”€ go.sum
โ”œโ”€โ”€ controllers/
โ”‚   โ””โ”€โ”€ user_controller.go
โ”œโ”€โ”€ models/
โ”‚   โ””โ”€โ”€ user.go
โ”œโ”€โ”€ routes/
โ”‚   โ””โ”€โ”€ routes.go
โ””โ”€โ”€ middleware/
    โ””โ”€โ”€ auth.go

์ด๋Ÿฐ ๊ตฌ์กฐ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ๋‚˜๋ˆ„๋ฉด ์ฝ”๋“œ ๊ด€๋ฆฌ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ์š”. ๊ฐ ๋””๋ ‰ํ† ๋ฆฌ์˜ ์—ญํ• ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•„์š”:

  1. controllers: ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋“ค
  2. models: ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ จ ์ฝ”๋“œ
  3. routes: API ๋ผ์šฐํŠธ ์ •์˜
  4. middleware: ์ธ์ฆ ๋“ฑ์˜ ๋ฏธ๋“ค์›จ์–ด ํ•จ์ˆ˜๋“ค

์ด์ œ ๊ฐ ๋””๋ ‰ํ† ๋ฆฌ์™€ ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณผ๊ฒŒ์š”!

routes/routes.go

package routes

import (
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-rest-api/controllers"
)

// SetupRouter ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ๋ผ์šฐํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค
func SetupRouter() *gin.Engine {
    r := gin.Default()
    
    // ๊ธฐ๋ณธ ๋ผ์šฐํŠธ
    r.GET("/", controllers.HelloWorld)
    
    // API v1 ๊ทธ๋ฃน
    v1 := r.Group("/api/v1")
    {
        v1.GET("/users", controllers.GetUsers)
        v1.GET("/users/:id", controllers.GetUser)
        v1.POST("/users", controllers.CreateUser)
        v1.PUT("/users/:id", controllers.UpdateUser)
        v1.DELETE("/users/:id", controllers.DeleteUser)
    }
    
    return r
}

controllers/user_controller.go

package controllers

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

// HelloWorld๋Š” ๊ธฐ๋ณธ ์ธ์‚ฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func HelloWorld(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "message": "Hello, RESTful World!",
    })
}

// GetUsers๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetUsers(c *gin.Context) {
    // ์ž„์‹œ ๋ฐ์ดํ„ฐ (๋‚˜์ค‘์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ฌ ์˜ˆ์ •)
    users := []gin.H{
        {"id": 1, "name": "๊ณ ํผ", "email": "gopher@go.dev"},
        {"id": 2, "name": "๊ณ ๋žญ", "email": "golang@go.dev"},
    }
    
    c.JSON(http.StatusOK, gin.H{
        "users": users,
    })
}

// GetUser๋Š” ํŠน์ • ID์˜ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetUser(c *gin.Context) {
    id := c.Param("id")
    
    // ์‹ค์ œ๋กœ๋Š” DB์—์„œ ์กฐํšŒํ•ด์•ผ ํ•จ
    user := gin.H{"id": id, "name": "๊ณ ํผ", "email": "gopher@go.dev"}
    
    c.JSON(http.StatusOK, user)
}

// CreateUser๋Š” ์ƒˆ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
func CreateUser(c *gin.Context) {
    var user struct {
        Name  string `json:"name" binding:"required"`
        Email string `json:"email" binding:"required"`
    }
    
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    // ์‹ค์ œ๋กœ๋Š” DB์— ์ €์žฅํ•ด์•ผ ํ•จ
    c.JSON(http.StatusCreated, gin.H{
        "id": 3,
        "name": user.Name,
        "email": user.Email,
    })
}

// UpdateUser๋Š” ํŠน์ • ID์˜ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค
func UpdateUser(c *gin.Context) {
    id := c.Param("id")
    
    var user struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    // ์‹ค์ œ๋กœ๋Š” DB์—์„œ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•จ
    c.JSON(http.StatusOK, gin.H{
        "id": id,
        "name": user.Name,
        "email": user.Email,
        "message": "์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
    })
}

// DeleteUser๋Š” ํŠน์ • ID์˜ ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค
func DeleteUser(c *gin.Context) {
    id := c.Param("id")
    
    // ์‹ค์ œ๋กœ๋Š” DB์—์„œ ์‚ญ์ œํ•ด์•ผ ํ•จ
    c.JSON(http.StatusOK, gin.H{
        "message": "์‚ฌ์šฉ์ž ID " + id + "๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
    })
}

main.go (์ˆ˜์ •)

package main

import (
    "github.com/yourusername/go-rest-api/routes"
)

func main() {
    // ๋ผ์šฐํ„ฐ ์„ค์ •
    r := routes.SetupRouter()
    
    // ์„œ๋ฒ„ ์‹œ์ž‘
    r.Run(":8080")
}

์ด์ œ ๋‹ค์‹œ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ด๋ณด์„ธ์š”:

go run main.go

์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜ ์žˆ์–ด์š”:

  1. GET /api/v1/users - ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์กฐํšŒ
  2. GET /api/v1/users/1 - ID๊ฐ€ 1์ธ ์‚ฌ์šฉ์ž ์กฐํšŒ
  3. POST /api/v1/users - ์ƒˆ ์‚ฌ์šฉ์ž ์ƒ์„ฑ
  4. PUT /api/v1/users/1 - ID๊ฐ€ 1์ธ ์‚ฌ์šฉ์ž ์ •๋ณด ์—…๋ฐ์ดํŠธ
  5. DELETE /api/v1/users/1 - ID๊ฐ€ 1์ธ ์‚ฌ์šฉ์ž ์‚ญ์ œ

์ด๋ ‡๊ฒŒ ๊ธฐ๋ณธ์ ์ธ CRUD(Create, Read, Update, Delete) ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ RESTful API๊ฐ€ ์™„์„ฑ๋˜์—ˆ์–ด์š”! ์•„์ง์€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™์ด ์—†์ง€๋งŒ, ๊ธฐ๋ณธ ๊ตฌ์กฐ๋Š” ๊ฐ–์ถ”์—ˆ๋‹ต๋‹ˆ๋‹ค. ๐Ÿ‘

ํด๋ผ์ด์–ธํŠธ Go API ์„œ๋ฒ„ ๋ผ์šฐํ„ฐ ์ปจํŠธ๋กค๋Ÿฌ ๋ชจ๋ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค HTTP ์š”์ฒญ ์ฟผ๋ฆฌ/์ €์žฅ RESTful API ์•„ํ‚คํ…์ฒ˜

4. ๋ผ์šฐํŒ…๊ณผ ํ•ธ๋“ค๋Ÿฌ ์‹ฌํ™” ํ•™์Šต ๐Ÿ”„

์ด์ œ ๋ผ์šฐํŒ…๊ณผ ํ•ธ๋“ค๋Ÿฌ์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณผ๊ฒŒ์š”. RESTful API์—์„œ ๋ผ์šฐํŒ…์€ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ์ ์ ˆํ•œ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜๋กœ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ด์š”.

4.1 ๋ผ์šฐํŠธ ๊ทธ๋ฃนํ™”ํ•˜๊ธฐ

Gin์—์„œ๋Š” ๋ผ์šฐํŠธ๋ฅผ ๊ทธ๋ฃนํ™”ํ•ด์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ณตํ†ต ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์ง„ ์—”๋“œํฌ์ธํŠธ๋“ค์„ ๋” ๊น”๋”ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์ฃ . ์˜ˆ๋ฅผ ๋“ค์–ด, API ๋ฒ„์ „๋ณ„๋กœ ๊ทธ๋ฃนํ™”ํ•˜๊ฑฐ๋‚˜ ๋ฆฌ์†Œ์Šค๋ณ„๋กœ ๊ทธ๋ฃนํ™”ํ•  ์ˆ˜ ์žˆ์–ด์š”.

routes/routes.go ํŒŒ์ผ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ด๋ณผ๊ฒŒ์š”:

package routes

import (
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-rest-api/controllers"
    "github.com/yourusername/go-rest-api/middleware"
)

// SetupRouter ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ๋ผ์šฐํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค
func SetupRouter() *gin.Engine {
    r := gin.Default()
    
    // ๊ธฐ๋ณธ ๋ผ์šฐํŠธ
    r.GET("/", controllers.HelloWorld)
    
    // API v1 ๊ทธ๋ฃน
    v1 := r.Group("/api/v1")
    {
        // ์‚ฌ์šฉ์ž ๊ด€๋ จ ์—”๋“œํฌ์ธํŠธ
        users := v1.Group("/users")
        {
            users.GET("", controllers.GetUsers)
            users.GET("/:id", controllers.GetUser)
            users.POST("", controllers.CreateUser)
            users.PUT("/:id", controllers.UpdateUser)
            users.DELETE("/:id", controllers.DeleteUser)
        }
        
        // ์ƒํ’ˆ ๊ด€๋ จ ์—”๋“œํฌ์ธํŠธ (์˜ˆ์‹œ)
        products := v1.Group("/products")
        {
            products.GET("", controllers.GetProducts)
            products.GET("/:id", controllers.GetProduct)
            // ์ธ์ฆ์ด ํ•„์š”ํ•œ ์—”๋“œํฌ์ธํŠธ์—๋Š” ๋ฏธ๋“ค์›จ์–ด ์ ์šฉ
            products.POST("", middleware.AuthRequired(), controllers.CreateProduct)
            products.PUT("/:id", middleware.AuthRequired(), controllers.UpdateProduct)
            products.DELETE("/:id", middleware.AuthRequired(), controllers.DeleteProduct)
        }
    }
    
    return r
}

์ด๋Ÿฐ ์‹์œผ๋กœ ๋ผ์šฐํŠธ๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๋” ๊น”๋”ํ•ด์ง€๊ณ , ํŠน์ • ๊ทธ๋ฃน์—๋งŒ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•˜๊ธฐ๋„ ์‰ฌ์›Œ์ ธ์š”.

4.2 ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜

RESTful API์—์„œ๋Š” ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ๋งค๊ฐœ๋ณ€์ˆ˜ ์œ ํ˜•์„ ์‚ฌ์šฉํ•ด์š”:

  1. ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜(Path Parameters): URL ๊ฒฝ๋กœ์˜ ์ผ๋ถ€๋กœ ์‚ฌ์šฉ (์˜ˆ: /users/:id)
  2. ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜(Query Parameters): URL์˜ ? ๋’ค์— ์˜ค๋Š” ํ‚ค-๊ฐ’ ์Œ (์˜ˆ: /users?page=1&limit=10)

Gin์—์„œ ์ด ๋‘ ๊ฐ€์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ๊ฒŒ์š”:

// ๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉ ์˜ˆ์‹œ
func GetUser(c *gin.Context) {
    // URL์—์„œ :id ๋ถ€๋ถ„ ์ถ”์ถœ
    id := c.Param("id")
    
    // ๋กœ์ง ์ฒ˜๋ฆฌ...
    c.JSON(http.StatusOK, gin.H{"id": id, "name": "๊ณ ํผ"})
}

// ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉ ์˜ˆ์‹œ
func GetUsers(c *gin.Context) {
    // URL์—์„œ ?page=1&limit=10 ๊ฐ™์€ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”์ถœ
    page := c.DefaultQuery("page", "1")     // ๊ธฐ๋ณธ๊ฐ’ 1
    limit := c.DefaultQuery("limit", "10")  // ๊ธฐ๋ณธ๊ฐ’ 10
    
    // ๋กœ์ง ์ฒ˜๋ฆฌ...
    c.JSON(http.StatusOK, gin.H{
        "page": page,
        "limit": limit,
        "users": []gin.H{
            {"id": 1, "name": "๊ณ ํผ"},
            {"id": 2, "name": "๊ณ ๋žญ"},
        },
    })
}

4.3 ์š”์ฒญ ๋ฐ”๋”” ์ฒ˜๋ฆฌํ•˜๊ธฐ

POST๋‚˜ PUT ์š”์ฒญ์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ ๋ฐ”๋””๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ด์š”. Gin์—์„œ๋Š” ์ด๋Ÿฐ ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”:

func CreateUser(c *gin.Context) {
    // ์š”์ฒญ ๋ฐ”๋””๋ฅผ ๊ตฌ์กฐ์ฒด์— ๋ฐ”์ธ๋”ฉ
    var user struct {
        Name     string `json:"name" binding:"required"`
        Email    string `json:"email" binding:"required,email"`
        Password string `json:"password" binding:"required,min=6"`
    }
    
    // ๋ฐ”์ธ๋”ฉ ์‹œ๋„
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ†ต๊ณผ ํ›„ ๋กœ์ง ์ฒ˜๋ฆฌ...
    c.JSON(http.StatusCreated, gin.H{
        "message": "์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
        "user": gin.H{
            "name": user.Name,
            "email": user.Email,
        },
    })
}

Gin์˜ ๋ฐ”์ธ๋”ฉ ๊ธฐ๋Šฅ์€ ์ •๋ง ๊ฐ•๋ ฅํ•ด์š”! binding ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋™์œผ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊นŒ์ง€ ํ•ด์ค˜์„œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ผ์ผ์ด ๊ฒ€์‚ฌ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์–ด์š”. ์œ„ ์˜ˆ์‹œ์—์„œ๋Š” ์ด๋ฆ„๊ณผ ์ด๋ฉ”์ผ์ด ํ•„์ˆ˜์ด๊ณ , ์ด๋ฉ”์ผ์€ ์ด๋ฉ”์ผ ํ˜•์‹์ด์–ด์•ผ ํ•˜๋ฉฐ, ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ตœ์†Œ 6์ž ์ด์ƒ์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ์กฐ๊ฑด์„ ์„ค์ •ํ–ˆ์–ด์š”.

๐Ÿ’ก ํŒ: Gin์˜ ๋ฐ”์ธ๋”ฉ ํƒœ๊ทธ๋กœ ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์˜ ์˜ˆ:

  • required: ํ•„์ˆ˜ ํ•„๋“œ
  • email: ์ด๋ฉ”์ผ ํ˜•์‹
  • min, max: ์ตœ์†Œ/์ตœ๋Œ€ ๊ธธ์ด
  • len: ์ •ํ™•ํ•œ ๊ธธ์ด
  • eq, ne, gt, lt: ๊ฐ’ ๋น„๊ต
  • oneof: ์—ด๊ฑฐํ˜• ๊ฐ’

4.4 ์‘๋‹ต ํ˜•์‹ ํ‘œ์ค€ํ™”ํ•˜๊ธฐ

RESTful API์—์„œ๋Š” ์ผ๊ด€๋œ ์‘๋‹ต ํ˜•์‹์„ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ด์š”. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ‘œ์ค€ ์‘๋‹ต ํ˜•์‹์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•ด๋ณด์„ธ์š”:

// ์„ฑ๊ณต ์‘๋‹ต ํ—ฌํผ ํ•จ์ˆ˜
func ResponseSuccess(c *gin.Context, statusCode int, data interface{}) {
    c.JSON(statusCode, gin.H{
        "success": true,
        "data": data,
    })
}

// ์˜ค๋ฅ˜ ์‘๋‹ต ํ—ฌํผ ํ•จ์ˆ˜
func ResponseError(c *gin.Context, statusCode int, message string) {
    c.JSON(statusCode, gin.H{
        "success": false,
        "error": message,
    })
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
func GetUser(c *gin.Context) {
    id := c.Param("id")
    
    // ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ
    if id == "999" {
        ResponseError(c, http.StatusNotFound, "์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")
        return
    }
    
    // ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์€ ๊ฒฝ์šฐ
    user := gin.H{"id": id, "name": "๊ณ ํผ", "email": "gopher@go.dev"}
    ResponseSuccess(c, http.StatusOK, user)
}

์ด๋ ‡๊ฒŒ ์‘๋‹ต ํ˜•์‹์„ ํ‘œ์ค€ํ™”ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ์š”. ๋˜ํ•œ API ๋ฌธ์„œํ™”๋„ ๋” ๊ฐ„๊ฒฐํ•ด์ง€์ฃ !

5. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ํ•˜๊ธฐ ๐Ÿ’พ

์‹ค์ œ API์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์˜๊ตฌ์ ์œผ๋กœ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด์š”. Go์—์„œ๋Š” ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‰ฝ๊ฒŒ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ์–ด์š”. ์—ฌ๊ธฐ์„œ๋Š” MySQL์„ ์˜ˆ๋กœ ๋“ค์–ด๋ณผ๊ฒŒ์š”.

5.1 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ค์ •

๋จผ์ € models ๋””๋ ‰ํ† ๋ฆฌ์— db.go ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•ด๋ณผ๊ฒŒ์š”:

package models

import (
    "database/sql"
    "fmt"
    "log"
    "os"
    
    _ "github.com/go-sql-driver/mysql"
    "github.com/joho/godotenv"
)

var DB *sql.DB

// InitDB ํ•จ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค
func InitDB() {
    // .env ํŒŒ์ผ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
    err := godotenv.Load()
    if err != nil {
        log.Println("Warning: .env ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")
    }
    
    // ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ
    dbUser := os.Getenv("DB_USER")
    dbPassword := os.Getenv("DB_PASSWORD")
    dbHost := os.Getenv("DB_HOST")
    dbName := os.Getenv("DB_NAME")
    
    // ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
    if dbUser == "" {
        dbUser = "root"
    }
    if dbHost == "" {
        dbHost = "localhost:3306"
    }
    if dbName == "" {
        dbName = "go_api"
    }
    
    // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด ์ƒ์„ฑ
    dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        dbUser, dbPassword, dbHost, dbName)
    
    // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatalf("๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ: %v", err)
    }
    
    // ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ
    err = db.Ping()
    if err != nil {
        log.Fatalf("๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค Ping ์‹คํŒจ: %v", err)
    }
    
    // ์—ฐ๊ฒฐ ํ’€ ์„ค์ •
    db.SetMaxIdleConns(10)
    db.SetMaxOpenConns(100)
    
    DB = db
    log.Println("๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ฑ๊ณต!")
}

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— .env ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•ด์ฃผ์„ธ์š”:

DB_USER=root
DB_PASSWORD=your_password
DB_HOST=localhost:3306
DB_NAME=go_api

5.2 ์‚ฌ์šฉ์ž ๋ชจ๋ธ ๊ตฌํ˜„ํ•˜๊ธฐ

์ด์ œ models ๋””๋ ‰ํ† ๋ฆฌ์— user.go ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉ์ž ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•ด๋ณผ๊ฒŒ์š”:

package models

import (
    "database/sql"
    "errors"
    "time"
)

// User ๊ตฌ์กฐ์ฒด๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค
type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    Password  string    `json:"-"` // JSON ์‘๋‹ต์—์„œ ์ œ์™ธ
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// GetAllUsers ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetAllUsers() ([]User, error) {
    query := `SELECT id, name, email, created_at, updated_at FROM users`
    
    rows, err := DB.Query(query)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var users []User
    
    for rows.Next() {
        var user User
        err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt)
        if err != nil {
            return nil, err
        }
        users = append(users, user)
    }
    
    return users, nil
}

// GetUserByID ํ•จ์ˆ˜๋Š” ID๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค
func GetUserByID(id int) (User, error) {
    query := `SELECT id, name, email, created_at, updated_at FROM users WHERE id = ?`
    
    var user User
    err := DB.QueryRow(query, id).Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt)
    
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return User{}, errors.New("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")
        }
        return User{}, err
    }
    
    return user, nil
}

// CreateUser ํ•จ์ˆ˜๋Š” ์ƒˆ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
func CreateUser(name, email, password string) (User, error) {
    // ์ด๋ฉ”์ผ ์ค‘๋ณต ํ™•์ธ
    var count int
    err := DB.QueryRow("SELECT COUNT(*) FROM users WHERE email = ?", email).Scan(&count)
    if err != nil {
        return User{}, err
    }
    
    if count > 0 {
        return User{}, errors.New("์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค")
    }
    
    // ํ˜„์žฌ ์‹œ๊ฐ„
    now := time.Now()
    
    // ์‚ฌ์šฉ์ž ์ƒ์„ฑ
    query := `INSERT INTO users (name, email, password, created_at, updated_at) 
              VALUES (?, ?, ?, ?, ?)`
    
    result, err := DB.Exec(query, name, email, password, now, now)
    if err != nil {
        return User{}, err
    }
    
    // ์ƒ์„ฑ๋œ ID ๊ฐ€์ ธ์˜ค๊ธฐ
    id, err := result.LastInsertId()
    if err != nil {
        return User{}, err
    }
    
    // ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ๋ฐ˜ํ™˜
    return User{
        ID:        int(id),
        Name:      name,
        Email:     email,
        CreatedAt: now,
        UpdatedAt: now,
    }, nil
}

// UpdateUser ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค
func UpdateUser(id int, name, email string) (User, error) {
    // ์‚ฌ์šฉ์ž ์กด์žฌ ํ™•์ธ
    _, err := GetUserByID(id)
    if err != nil {
        return User{}, err
    }
    
    // ํ˜„์žฌ ์‹œ๊ฐ„
    now := time.Now()
    
    // ์‚ฌ์šฉ์ž ์—…๋ฐ์ดํŠธ
    query := `UPDATE users SET name = ?, email = ?, updated_at = ? WHERE id = ?`
    
    _, err = DB.Exec(query, name, email, now, id)
    if err != nil {
        return User{}, err
    }
    
    // ์—…๋ฐ์ดํŠธ๋œ ์‚ฌ์šฉ์ž ๋ฐ˜ํ™˜
    return GetUserByID(id)
}

// DeleteUser ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค
func DeleteUser(id int) error {
    // ์‚ฌ์šฉ์ž ์กด์žฌ ํ™•์ธ
    _, err := GetUserByID(id)
    if err != nil {
        return err
    }
    
    // ์‚ฌ์šฉ์ž ์‚ญ์ œ
    query := `DELETE FROM users WHERE id = ?`
    
    _, err = DB.Exec(query, id)
    return err
}

5.3 ์ปจํŠธ๋กค๋Ÿฌ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

์ด์ œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์„œ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ด๋ณผ๊ฒŒ์š”:

package controllers

import (
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-rest-api/models"
    "net/http"
    "strconv"
)

// GetUsers๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetUsers(c *gin.Context) {
    users, err := models.GetAllUsers()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": true,
        "data": users,
    })
}

// GetUser๋Š” ํŠน์ • ID์˜ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetUser(c *gin.Context) {
    idStr := c.Param("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "error": "์ž˜๋ชป๋œ ID ํ˜•์‹์ž…๋‹ˆ๋‹ค",
        })
        return
    }
    
    user, err := models.GetUserByID(id)
    if err != nil {
        c.JSON(http.StatusNotFound, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": true,
        "data": user,
    })
}

// CreateUser๋Š” ์ƒˆ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
func CreateUser(c *gin.Context) {
    var input struct {
        Name     string `json:"name" binding:"required"`
        Email    string `json:"email" binding:"required,email"`
        Password string `json:"password" binding:"required,min=6"`
    }
    
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    user, err := models.CreateUser(input.Name, input.Email, input.Password)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusCreated, gin.H{
        "success": true,
        "data": user,
    })
}

// UpdateUser๋Š” ํŠน์ • ID์˜ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค
func UpdateUser(c *gin.Context) {
    idStr := c.Param("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "error": "์ž˜๋ชป๋œ ID ํ˜•์‹์ž…๋‹ˆ๋‹ค",
        })
        return
    }
    
    var input struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    user, err := models.UpdateUser(id, input.Name, input.Email)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": true,
        "data": user,
    })
}

// DeleteUser๋Š” ํŠน์ • ID์˜ ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค
func DeleteUser(c *gin.Context) {
    idStr := c.Param("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "error": "์ž˜๋ชป๋œ ID ํ˜•์‹์ž…๋‹ˆ๋‹ค",
        })
        return
    }
    
    err = models.DeleteUser(id)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    c.JSON(http.StatusOK, gin.H{
        "success": true,
        "message": "์‚ฌ์šฉ์ž๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
    })
}

5.4 main.go ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

๋งˆ์ง€๋ง‰์œผ๋กœ main.go ํŒŒ์ผ์„ ์—…๋ฐ์ดํŠธํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ดˆ๊ธฐํ™”ํ•˜๋„๋ก ํ•ด๋ณผ๊ฒŒ์š”:

package main

import (
    "github.com/yourusername/go-rest-api/models"
    "github.com/yourusername/go-rest-api/routes"
)

func main() {
    // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ดˆ๊ธฐํ™”
    models.InitDB()
    
    // ๋ผ์šฐํ„ฐ ์„ค์ •
    r := routes.SetupRouter()
    
    // ์„œ๋ฒ„ ์‹œ์ž‘
    r.Run(":8080")
}

์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๋™๋œ ์™„์ „ํ•œ RESTful API๊ฐ€ ์™„์„ฑ๋˜์—ˆ์–ด์š”! ๐ŸŽ‰

๐Ÿ’ก ์ฐธ๊ณ : ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ‰๋ฌธ์œผ๋กœ ์ €์žฅํ•˜๋ฉด ์•ˆ ๋ผ์š”! bcrypt์™€ ๊ฐ™์€ ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”:

import "golang.org/x/crypto/bcrypt"

// ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
    return User{}, err
}

// ํ•ด์‹œ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ €์žฅ
// DB.Exec(query, name, email, string(hashedPassword), now, now)

6. ๋ฏธ๋“ค์›จ์–ด์™€ ์ธ์ฆ ๊ตฌํ˜„ํ•˜๊ธฐ ๐Ÿ”’

RESTful API์—์„œ ์ธ์ฆ๊ณผ ๊ถŒํ•œ ๊ด€๋ฆฌ๋Š” ๋งค์šฐ ์ค‘์š”ํ•ด์š”. Gin์˜ ๋ฏธ๋“ค์›จ์–ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.

6.1 JWT ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด ๊ตฌํ˜„ํ•˜๊ธฐ

JWT(JSON Web Token)๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์—์š”. middleware ๋””๋ ‰ํ† ๋ฆฌ์— auth.go ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด JWT ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ๊ฒŒ์š”:

package middleware

import (
    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
    "net/http"
    "os"
    "strings"
    "time"
)

// JWT ์‹œํฌ๋ฆฟ ํ‚ค
var jwtSecret = []byte(getJWTSecret())

// getJWTSecret ํ•จ์ˆ˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ JWT ์‹œํฌ๋ฆฟ ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค
func getJWTSecret() string {
    secret := os.Getenv("JWT_SECRET")
    if secret == "" {
        return "your-secret-key" // ๊ธฐ๋ณธ๊ฐ’ (์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•จ)
    }
    return secret
}

// Claims ๊ตฌ์กฐ์ฒด๋Š” JWT ํด๋ ˆ์ž„์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค
type Claims struct {
    UserID int `json:"user_id"`
    jwt.RegisteredClaims
}

// GenerateToken ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž ID๋กœ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
func GenerateToken(userID int) (string, error) {
    // ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ • (์˜ˆ: 24์‹œ๊ฐ„)
    expirationTime := time.Now().Add(24 * time.Hour)
    
    // ํด๋ ˆ์ž„ ์ƒ์„ฑ
    claims := &Claims{
        UserID: userID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(expirationTime),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            NotBefore: jwt.NewNumericDate(time.Now()),
        },
    }
    
    // ํ† ํฐ ์ƒ์„ฑ
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    
    // ํ† ํฐ ์„œ๋ช…
    tokenString, err := token.SignedString(jwtSecret)
    if err != nil {
        return "", err
    }
    
    return tokenString, nil
}

// ValidateToken ํ•จ์ˆ˜๋Š” JWT ํ† ํฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค
func ValidateToken(tokenString string) (*Claims, error) {
    // ํ† ํฐ ํŒŒ์‹ฑ
    claims := &Claims{}
    token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
        return jwtSecret, nil
    })
    
    if err != nil {
        return nil, err
    }
    
    // ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    if !token.Valid {
        return nil, jwt.ErrSignatureInvalid
    }
    
    return claims, nil
}

// AuthRequired ๋ฏธ๋“ค์›จ์–ด๋Š” ์š”์ฒญ์— ์œ ํšจํ•œ JWT ํ† ํฐ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค
func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        // Authorization ํ—ค๋”์—์„œ ํ† ํฐ ์ถ”์ถœ
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "success": false,
                "error": "์ธ์ฆ ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค",
            })
            c.Abort()
            return
        }
        
        // "Bearer " ์ ‘๋‘์‚ฌ ์ œ๊ฑฐ
        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
        
        // ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
        claims, err := ValidateToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{
                "success": false,
                "error": "์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค",
            })
            c.Abort()
            return
        }
        
        // ์‚ฌ์šฉ์ž ID๋ฅผ ์ปจํ…์ŠคํŠธ์— ์ €์žฅ
        c.Set("userID", claims.UserID)
        
        // ๋‹ค์Œ ํ•ธ๋“ค๋Ÿฌ๋กœ ์ง„ํ–‰
        c.Next()
    }
}

6.2 ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

์ด์ œ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ๊ฒŒ์š”. controllers ๋””๋ ‰ํ† ๋ฆฌ์— auth_controller.go ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”:

package controllers

import (
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-rest-api/middleware"
    "github.com/yourusername/go-rest-api/models"
    "golang.org/x/crypto/bcrypt"
    "net/http"
)

// Login ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
func Login(c *gin.Context) {
    var input struct {
        Email    string `json:"email" binding:"required,email"`
        Password string `json:"password" binding:"required"`
    }
    
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "error": err.Error(),
        })
        return
    }
    
    // ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ
    user, err := models.GetUserByEmail(input.Email)
    if err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{
            "success": false,
            "error": "์ด๋ฉ”์ผ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
        })
        return
    }
    
    // ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
    err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(input.Password))
    if err != nil {
        c.JSON(http.StatusUnauthorized, gin.H{
            "success": false,
            "error": "์ด๋ฉ”์ผ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค",
        })
        return
    }
    
    // JWT ํ† ํฐ ์ƒ์„ฑ
    token, err := middleware.GenerateToken(user.ID)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "error": "ํ† ํฐ ์ƒ์„ฑ ์‹คํŒจ",
        })
        return
    }
    
    // ์‘๋‹ต
    c.JSON(http.StatusOK, gin.H{
        "success": true,
        "data": gin.H{
            "token": token,
            "user": gin.H{
                "id": user.ID,
                "name": user.Name,
                "email": user.Email,
            },
        },
    })
}

models/user.go ํŒŒ์ผ์— GetUserByEmail ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”:

// GetUserByEmail ํ•จ์ˆ˜๋Š” ์ด๋ฉ”์ผ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค
func GetUserByEmail(email string) (User, error) {
    query := `SELECT id, name, email, password, created_at, updated_at FROM users WHERE email = ?`
    
    var user User
    err := DB.QueryRow(query, email).Scan(&user.ID, &user.Name, &user.Email, &user.Password, &user.CreatedAt, &user.UpdatedAt)
    
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return User{}, errors.New("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")
        }
        return User{}, err
    }
    
    return user, nil
}

6.3 ๋ผ์šฐํŠธ์— ๋กœ๊ทธ์ธ ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ

routes/routes.go ํŒŒ์ผ์— ๋กœ๊ทธ์ธ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”:

// SetupRouter ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ๋ผ์šฐํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค
func SetupRouter() *gin.Engine {
    r := gin.Default()
    
    // ๊ธฐ๋ณธ ๋ผ์šฐํŠธ
    r.GET("/", controllers.HelloWorld)
    
    // ์ธ์ฆ ๊ด€๋ จ ๋ผ์šฐํŠธ
    r.POST("/login", controllers.Login)
    
    // API v1 ๊ทธ๋ฃน
    v1 := r.Group("/api/v1")
    {
        // ์‚ฌ์šฉ์ž ๊ด€๋ จ ์—”๋“œํฌ์ธํŠธ
        users := v1.Group("/users")
        {
            users.GET("", controllers.GetUsers)
            users.GET("/:id", controllers.GetUser)
            users.POST("", controllers.CreateUser)
            
            // ์ธ์ฆ์ด ํ•„์š”ํ•œ ์—”๋“œํฌ์ธํŠธ
            authUsers := users.Group("")
            authUsers.Use(middleware.AuthRequired())
            {
                authUsers.PUT("/:id", controllers.UpdateUser)
                authUsers.DELETE("/:id", controllers.DeleteUser)
            }
        }
        
        // ๋‹ค๋ฅธ ๋ฆฌ์†Œ์Šค...
    }
    
    return r
}

์ด์ œ ์ธ์ฆ ์‹œ์Šคํ…œ์ด ๊ตฌํ˜„๋˜์—ˆ์–ด์š”! ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธํ•ด์„œ JWT ํ† ํฐ์„ ๋ฐ›๊ณ , ์ด ํ† ํฐ์„ ์‚ฌ์šฉํ•ด ๋ณดํ˜ธ๋œ ์—”๋“œํฌ์ธํŠธ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์š”. ๐Ÿ”

ํด๋ผ์ด์–ธํŠธ 1. ๋กœ๊ทธ์ธ ์š”์ฒญ API ์„œ๋ฒ„ ์ธ์ฆ ์ฒ˜๋ฆฌ JWT ์ƒ์„ฑ API ๋กœ์ง ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ 2. JWT ํ† ํฐ ๋ฐ˜ํ™˜ 3. ํ† ํฐ๊ณผ ํ•จ๊ป˜ API ์š”์ฒญ ๋ฏธ๋“ค์›จ์–ด ํ† ํฐ ๊ฒ€์ฆ ๊ถŒํ•œ ํ™•์ธ ์š”์ฒญ ์ „์ฒ˜๋ฆฌ ์‘๋‹ต ํ›„์ฒ˜๋ฆฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค JWT ์ธ์ฆ ํ๋ฆ„๋„ RESTful API์˜ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๊ด€๋ฆฌ

7. ํ…Œ์ŠคํŠธ์™€ ๋ฐฐํฌ ๋ฐฉ๋ฒ• ๐Ÿงช

API ๊ฐœ๋ฐœ์ด ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด ํ…Œ์ŠคํŠธ์™€ ๋ฐฐํฌ ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ€์•ผ ํ•ด์š”. Go๋Š” ํ…Œ์ŠคํŠธ ๋„๊ตฌ๊ฐ€ ๋‚ด์žฅ๋˜์–ด ์žˆ์–ด ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”.

7.1 API ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๊ธฐ

Go์˜ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์€ *_test.go ํ˜•์‹์œผ๋กœ ์ž‘์„ฑํ•ด์š”. controllers ๋””๋ ‰ํ† ๋ฆฌ์— user_controller_test.go ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ์š”:

package controllers

import (
    "bytes"
    "encoding/json"
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-rest-api/models"
    "github.com/yourusername/go-rest-api/routes"
    "net/http"
    "net/http/httptest"
    "testing"
)

// ํ…Œ์ŠคํŠธ ์ „์— Gin์„ ํ…Œ์ŠคํŠธ ๋ชจ๋“œ๋กœ ์„ค์ •
func init() {
    gin.SetMode(gin.TestMode)
}

// TestGetUsers ํ•จ์ˆ˜๋Š” GetUsers ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค
func TestGetUsers(t *testing.T) {
    // ํ…Œ์ŠคํŠธ์šฉ ๋ผ์šฐํ„ฐ ์„ค์ •
    r := routes.SetupRouter()
    
    // ํ…Œ์ŠคํŠธ์šฉ HTTP ์š”์ฒญ ์ƒ์„ฑ
    req, err := http.NewRequest(http.MethodGet, "/api/v1/users", nil)
    if err != nil {
        t.Fatalf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %v", err)
    }
    
    // ์‘๋‹ต ๋ ˆ์ฝ”๋” ์ƒ์„ฑ
    w := httptest.NewRecorder()
    
    // ์š”์ฒญ ์‹คํ–‰
    r.ServeHTTP(w, req)
    
    // ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ
    if w.Code != http.StatusOK {
        t.Errorf("์˜ˆ์ƒ ์ƒํƒœ ์ฝ”๋“œ %d, ์‹ค์ œ %d", http.StatusOK, w.Code)
    }
    
    // ์‘๋‹ต ๋ณธ๋ฌธ ํ™•์ธ
    var response struct {
        Success bool           `json:"success"`
        Data    []models.User `json:"data"`
    }
    
    err = json.Unmarshal(w.Body.Bytes(), &response)
    if err != nil {
        t.Fatalf("์‘๋‹ต ํŒŒ์‹ฑ ์‹คํŒจ: %v", err)
    }
    
    // ์„ฑ๊ณต ํ”Œ๋ž˜๊ทธ ํ™•์ธ
    if !response.Success {
        t.Errorf("์˜ˆ์ƒ ์„ฑ๊ณต ์ƒํƒœ true, ์‹ค์ œ %v", response.Success)
    }
}

// TestCreateUser ํ•จ์ˆ˜๋Š” CreateUser ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค
func TestCreateUser(t *testing.T) {
    // ํ…Œ์ŠคํŠธ์šฉ ๋ผ์šฐํ„ฐ ์„ค์ •
    r := routes.SetupRouter()
    
    // ํ…Œ์ŠคํŠธ์šฉ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ
    userData := map[string]string{
        "name":     "ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž",
        "email":    "test@example.com",
        "password": "password123",
    }
    
    // JSON์œผ๋กœ ๋ณ€ํ™˜
    jsonData, err := json.Marshal(userData)
    if err != nil {
        t.Fatalf("JSON ๋ณ€ํ™˜ ์‹คํŒจ: %v", err)
    }
    
    // ํ…Œ์ŠคํŠธ์šฉ HTTP ์š”์ฒญ ์ƒ์„ฑ
    req, err := http.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBuffer(jsonData))
    if err != nil {
        t.Fatalf("์š”์ฒญ ์ƒ์„ฑ ์‹คํŒจ: %v", err)
    }
    req.Header.Set("Content-Type", "application/json")
    
    // ์‘๋‹ต ๋ ˆ์ฝ”๋” ์ƒ์„ฑ
    w := httptest.NewRecorder()
    
    // ์š”์ฒญ ์‹คํ–‰
    r.ServeHTTP(w, req)
    
    // ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ ํ™•์ธ
    if w.Code != http.StatusCreated {
        t.Errorf("์˜ˆ์ƒ ์ƒํƒœ ์ฝ”๋“œ %d, ์‹ค์ œ %d", http.StatusCreated, w.Code)
    }
    
    // ์‘๋‹ต ๋ณธ๋ฌธ ํ™•์ธ
    var response struct {
        Success bool        `json:"success"`
        Data    models.User `json:"data"`
    }
    
    err = json.Unmarshal(w.Body.Bytes(), &response)
    if err != nil {
        t.Fatalf("์‘๋‹ต ํŒŒ์‹ฑ ์‹คํŒจ: %v", err)
    }
    
    // ์„ฑ๊ณต ํ”Œ๋ž˜๊ทธ ํ™•์ธ
    if !response.Success {
        t.Errorf("์˜ˆ์ƒ ์„ฑ๊ณต ์ƒํƒœ true, ์‹ค์ œ %v", response.Success)
    }
    
    // ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ํ™•์ธ
    if response.Data.Name != userData["name"] {
        t.Errorf("์˜ˆ์ƒ ์ด๋ฆ„ %s, ์‹ค์ œ %s", userData["name"], response.Data.Name)
    }
    if response.Data.Email != userData["email"] {
        t.Errorf("์˜ˆ์ƒ ์ด๋ฉ”์ผ %s, ์‹ค์ œ %s", userData["email"], response.Data.Email)
    }
}

ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

go test -v ./controllers

7.2 Docker๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐฐํฌ

Go ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹จ์ผ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ์ปดํŒŒ์ผ๋˜์–ด ๋ฐฐํฌ๊ฐ€ ๋งค์šฐ ๊ฐ„๋‹จํ•ด์š”. Docker๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋”์šฑ ํŽธ๋ฆฌํ•˜๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— Dockerfile์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”:

FROM golang:1.22-alpine AS builder

WORKDIR /app

# ์˜์กด์„ฑ ๋ณต์‚ฌ ๋ฐ ๋‹ค์šด๋กœ๋“œ
COPY go.mod go.sum ./
RUN go mod download

# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ
COPY . .

# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นŒ๋“œ
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# ์ตœ์ข… ์ด๋ฏธ์ง€
FROM alpine:latest

WORKDIR /app

# ํƒ€์ž„์กด ์„ค์ •
RUN apk --no-cache add tzdata
ENV TZ=Asia/Seoul

# ๋นŒ๋“œ๋œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ณต์‚ฌ
COPY --from=builder /app/main .
COPY .env .

# ํฌํŠธ ๋…ธ์ถœ
EXPOSE 8080

# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰
CMD ["./main"]

Docker ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

# ์ด๋ฏธ์ง€ ๋นŒ๋“œ
docker build -t go-rest-api .

# ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰
docker run -p 8080:8080 go-rest-api

7.3 ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค์— ๋ฐฐํฌํ•˜๊ธฐ

2025๋…„ ํ˜„์žฌ Go ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๊ฐ€ ์žˆ์–ด์š”. ๋ช‡ ๊ฐ€์ง€ ์ธ๊ธฐ ์žˆ๋Š” ์˜ต์…˜์„ ์†Œ๊ฐœํ• ๊ฒŒ์š”:

  1. AWS Elastic Beanstalk: Docker ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•ด ์‰ฝ๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด์š”.
  2. Google Cloud Run: ์„œ๋ฒ„๋ฆฌ์Šค ์ปจํ…Œ์ด๋„ˆ ํ”Œ๋žซํผ์œผ๋กœ, ํŠธ๋ž˜ํ”ฝ์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ํ™•์žฅ๋ผ์š”.
  3. Heroku: Go ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‰ฝ๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋Š” PaaS ์„œ๋น„์Šค์—์š”.
  4. DigitalOcean App Platform: ๊ฐ„๋‹จํ•œ ์„ค์ •์œผ๋กœ Go ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด์š”.

๊ฐ ์„œ๋น„์Šค๋งˆ๋‹ค ๋ฐฐํฌ ๋ฐฉ๋ฒ•์ด ๋‹ค๋ฅด์ง€๋งŒ, ๋Œ€๋ถ€๋ถ„ Git ์ €์žฅ์†Œ์™€ ์—ฐ๋™ํ•˜๊ฑฐ๋‚˜ Docker ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ’ก ํŒ: ์žฌ๋Šฅ๋„ท(https://www.jaenung.net)๊ณผ ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋Š” Go๋กœ ๊ฐœ๋ฐœ๋œ API๋ฅผ ํ™œ์šฉํ•œ ๋‹ค์–‘ํ•œ ์„œ๋น„์Šค๊ฐ€ ์ œ๊ณต๋˜๊ณ  ์žˆ์–ด์š”. ์—ฌ๋Ÿฌ๋ถ„์ด ๊ฐœ๋ฐœํ•œ API๋ฅผ ์žฌ๋Šฅ๋„ท์— ์—ฐ๋™ํ•˜๋ฉด ๋” ๋งŽ์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”!

8. ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์™„์ „ํ•œ RESTful API ๊ตฌํ˜„ ๐Ÿš€

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์‹ค์ „ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ๊ฒŒ์š”. ์ด ํ”„๋กœ์ ํŠธ๋Š” ๊ฐ„๋‹จํ•œ ๋ธ”๋กœ๊ทธ API๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ๊ธ€์„ ์ž‘์„ฑํ•˜๊ณ  ๋Œ“๊ธ€์„ ๋‹ฌ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์š”.

8.1 ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

go-blog-api/
โ”œโ”€โ”€ main.go
โ”œโ”€โ”€ go.mod
โ”œโ”€โ”€ go.sum
โ”œโ”€โ”€ .env
โ”œโ”€โ”€ controllers/
โ”‚   โ”œโ”€โ”€ auth_controller.go
โ”‚   โ”œโ”€โ”€ user_controller.go
โ”‚   โ”œโ”€โ”€ post_controller.go
โ”‚   โ””โ”€โ”€ comment_controller.go
โ”œโ”€โ”€ models/
โ”‚   โ”œโ”€โ”€ db.go
โ”‚   โ”œโ”€โ”€ user.go
โ”‚   โ”œโ”€โ”€ post.go
โ”‚   โ””โ”€โ”€ comment.go
โ”œโ”€โ”€ routes/
โ”‚   โ””โ”€โ”€ routes.go
โ”œโ”€โ”€ middleware/
โ”‚   โ””โ”€โ”€ auth.go
โ””โ”€โ”€ utils/
    โ”œโ”€โ”€ response.go
    โ””โ”€โ”€ validator.go

8.2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ

MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์Šคํ‚ค๋งˆ๋ฅผ ์„ค๊ณ„ํ•ด๋ณผ๊ฒŒ์š”:

-- ์‚ฌ์šฉ์ž ํ…Œ์ด๋ธ”
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- ๊ฒŒ์‹œ๊ธ€ ํ…Œ์ด๋ธ”
CREATE TABLE posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

-- ๋Œ“๊ธ€ ํ…Œ์ด๋ธ”
CREATE TABLE comments (
    id INT AUTO_INCREMENT PRIMARY KEY,
    post_id INT NOT NULL,
    user_id INT NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

8.3 API ์—”๋“œํฌ์ธํŠธ ์„ค๊ณ„

๋ธ”๋กœ๊ทธ API์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๊ณ„ํ•ด๋ณผ๊ฒŒ์š”:

  1. ์ธ์ฆ
    • POST /register - ํšŒ์›๊ฐ€์ž…
    • POST /login - ๋กœ๊ทธ์ธ
  2. ์‚ฌ์šฉ์ž
    • GET /api/v1/users - ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ
    • GET /api/v1/users/:id - ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ
    • PUT /api/v1/users/:id - ์‚ฌ์šฉ์ž ์ •๋ณด ์—…๋ฐ์ดํŠธ (์ธ์ฆ ํ•„์š”)
    • DELETE /api/v1/users/:id - ์‚ฌ์šฉ์ž ์‚ญ์ œ (์ธ์ฆ ํ•„์š”)
  3. ๊ฒŒ์‹œ๊ธ€
    • GET /api/v1/posts - ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ
    • GET /api/v1/posts/:id - ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ
    • POST /api/v1/posts - ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ (์ธ์ฆ ํ•„์š”)
    • PUT /api/v1/posts/:id - ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • (์ธ์ฆ ํ•„์š”)
    • DELETE /api/v1/posts/:id - ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ (์ธ์ฆ ํ•„์š”)
  4. ๋Œ“๊ธ€
    • GET /api/v1/posts/:id/comments - ๊ฒŒ์‹œ๊ธ€์˜ ๋ชจ๋“  ๋Œ“๊ธ€ ์กฐํšŒ
    • POST /api/v1/posts/:id/comments - ๋Œ“๊ธ€ ์ž‘์„ฑ (์ธ์ฆ ํ•„์š”)
    • PUT /api/v1/comments/:id - ๋Œ“๊ธ€ ์ˆ˜์ • (์ธ์ฆ ํ•„์š”)
    • DELETE /api/v1/comments/:id - ๋Œ“๊ธ€ ์‚ญ์ œ (์ธ์ฆ ํ•„์š”)

8.4 ํ•ต์‹ฌ ์ฝ”๋“œ ๊ตฌํ˜„

์ด ํ”„๋กœ์ ํŠธ์˜ ํ•ต์‹ฌ ์ฝ”๋“œ ์ผ๋ถ€๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ๊ฒŒ์š”. ๋จผ์ € ๊ฒŒ์‹œ๊ธ€ ๋ชจ๋ธ๋ถ€ํ„ฐ ์‚ดํŽด๋ณผ๊ฒŒ์š”:

models/post.go

package models

import (
    "database/sql"
    "errors"
    "time"
)

// Post ๊ตฌ์กฐ์ฒด๋Š” ๊ฒŒ์‹œ๊ธ€ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค
type Post struct {
    ID        int       `json:"id"`
    UserID    int       `json:"user_id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    User      *User     `json:"user,omitempty"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// GetAllPosts ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetAllPosts(page, limit int) ([]Post, error) {
    offset := (page - 1) * limit
    
    query := `
        SELECT p.id, p.user_id, p.title, p.content, p.created_at, p.updated_at,
               u.id, u.name, u.email, u.created_at, u.updated_at
        FROM posts p
        JOIN users u ON p.user_id = u.id
        ORDER BY p.created_at DESC
        LIMIT ? OFFSET ?
    `
    
    rows, err := DB.Query(query, limit, offset)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var posts []Post
    
    for rows.Next() {
        var post Post
        var user User
        
        err := rows.Scan(
            &post.ID, &post.UserID, &post.Title, &post.Content, &post.CreatedAt, &post.UpdatedAt,
            &user.ID, &user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt,
        )
        if err != nil {
            return nil, err
        }
        
        post.User = &user
        posts = append(posts, post)
    }
    
    return posts, nil
}

// GetPostByID ํ•จ์ˆ˜๋Š” ID๋กœ ๊ฒŒ์‹œ๊ธ€์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค
func GetPostByID(id int) (Post, error) {
    query := `
        SELECT p.id, p.user_id, p.title, p.content, p.created_at, p.updated_at,
               u.id, u.name, u.email, u.created_at, u.updated_at
        FROM posts p
        JOIN users u ON p.user_id = u.id
        WHERE p.id = ?
    `
    
    var post Post
    var user User
    
    err := DB.QueryRow(query, id).Scan(
        &post.ID, &post.UserID, &post.Title, &post.Content, &post.CreatedAt, &post.UpdatedAt,
        &user.ID, &user.Name, &user.Email, &user.CreatedAt, &user.UpdatedAt,
    )
    
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return Post{}, errors.New("๊ฒŒ์‹œ๊ธ€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")
        }
        return Post{}, err
    }
    
    post.User = &user
    return post, nil
}

// CreatePost ํ•จ์ˆ˜๋Š” ์ƒˆ ๊ฒŒ์‹œ๊ธ€์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
func CreatePost(userID int, title, content string) (Post, error) {
    // ํ˜„์žฌ ์‹œ๊ฐ„
    now := time.Now()
    
    // ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ
    query := `INSERT INTO posts (user_id, title, content, created_at, updated_at) 
              VALUES (?, ?, ?, ?, ?)`
    
    result, err := DB.Exec(query, userID, title, content, now, now)
    if err != nil {
        return Post{}, err
    }
    
    // ์ƒ์„ฑ๋œ ID ๊ฐ€์ ธ์˜ค๊ธฐ
    id, err := result.LastInsertId()
    if err != nil {
        return Post{}, err
    }
    
    // ์ƒ์„ฑ๋œ ๊ฒŒ์‹œ๊ธ€ ๋ฐ˜ํ™˜
    return GetPostByID(int(id))
}

// UpdatePost ํ•จ์ˆ˜๋Š” ๊ฒŒ์‹œ๊ธ€ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค
func UpdatePost(id, userID int, title, content string) (Post, error) {
    // ๊ฒŒ์‹œ๊ธ€ ์กด์žฌ ๋ฐ ์†Œ์œ ๊ถŒ ํ™•์ธ
    post, err := GetPostByID(id)
    if err != nil {
        return Post{}, err
    }
    
    if post.UserID != userID {
        return Post{}, errors.New("์ด ๊ฒŒ์‹œ๊ธ€์„ ์ˆ˜์ •ํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค")
    }
    
    // ํ˜„์žฌ ์‹œ๊ฐ„
    now := time.Now()
    
    // ๊ฒŒ์‹œ๊ธ€ ์—…๋ฐ์ดํŠธ
    query := `UPDATE posts SET title = ?, content = ?, updated_at = ? WHERE id = ?`
    
    _, err = DB.Exec(query, title, content, now, id)
    if err != nil {
        return Post{}, err
    }
    
    // ์—…๋ฐ์ดํŠธ๋œ ๊ฒŒ์‹œ๊ธ€ ๋ฐ˜ํ™˜
    return GetPostByID(id)
}

// DeletePost ํ•จ์ˆ˜๋Š” ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค
func DeletePost(id, userID int) error {
    // ๊ฒŒ์‹œ๊ธ€ ์กด์žฌ ๋ฐ ์†Œ์œ ๊ถŒ ํ™•์ธ
    post, err := GetPostByID(id)
    if err != nil {
        return err
    }
    
    if post.UserID != userID {
        return errors.New("์ด ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค")
    }
    
    // ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ
    query := `DELETE FROM posts WHERE id = ?`
    
    _, err = DB.Exec(query, id)
    return err
}

controllers/post_controller.go

package controllers

import (
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-blog-api/models"
    "github.com/yourusername/go-blog-api/utils"
    "net/http"
    "strconv"
)

// GetPosts๋Š” ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetPosts(c *gin.Context) {
    // ํŽ˜์ด์ง€๋„ค์ด์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ
    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
    
    // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    if page < 1 {
        page = 1
    }
    if limit < 1 || limit > 100 {
        limit = 10
    }
    
    posts, err := models.GetAllPosts(page, limit)
    if err != nil {
        utils.ResponseError(c, http.StatusInternalServerError, err.Error())
        return
    }
    
    utils.ResponseSuccess(c, http.StatusOK, gin.H{
        "posts": posts,
        "pagination": gin.H{
            "page":  page,
            "limit": limit,
        },
    })
}

// GetPost๋Š” ํŠน์ • ID์˜ ๊ฒŒ์‹œ๊ธ€์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func GetPost(c *gin.Context) {
    idStr := c.Param("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        utils.ResponseError(c, http.StatusBadRequest, "์ž˜๋ชป๋œ ID ํ˜•์‹์ž…๋‹ˆ๋‹ค")
        return
    }
    
    post, err := models.GetPostByID(id)
    if err != nil {
        utils.ResponseError(c, http.StatusNotFound, err.Error())
        return
    }
    
    utils.ResponseSuccess(c, http.StatusOK, post)
}

// CreatePost๋Š” ์ƒˆ ๊ฒŒ์‹œ๊ธ€์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค
func CreatePost(c *gin.Context) {
    // ์‚ฌ์šฉ์ž ID ๊ฐ€์ ธ์˜ค๊ธฐ (๋ฏธ๋“ค์›จ์–ด์—์„œ ์„ค์ •๋จ)
    userID, exists := c.Get("userID")
    if !exists {
        utils.ResponseError(c, http.StatusUnauthorized, "์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
        return
    }
    
    var input struct {
        Title   string `json:"title" binding:"required,min=3,max=200"`
        Content string `json:"content" binding:"required"`
    }
    
    if err := c.ShouldBindJSON(&input); err != nil {
        utils.ResponseError(c, http.StatusBadRequest, err.Error())
        return
    }
    
    post, err := models.CreatePost(userID.(int), input.Title, input.Content)
    if err != nil {
        utils.ResponseError(c, http.StatusInternalServerError, err.Error())
        return
    }
    
    utils.ResponseSuccess(c, http.StatusCreated, post)
}

// UpdatePost๋Š” ํŠน์ • ID์˜ ๊ฒŒ์‹œ๊ธ€ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค
func UpdatePost(c *gin.Context) {
    // ์‚ฌ์šฉ์ž ID ๊ฐ€์ ธ์˜ค๊ธฐ (๋ฏธ๋“ค์›จ์–ด์—์„œ ์„ค์ •๋จ)
    userID, exists := c.Get("userID")
    if !exists {
        utils.ResponseError(c, http.StatusUnauthorized, "์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
        return
    }
    
    idStr := c.Param("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        utils.ResponseError(c, http.StatusBadRequest, "์ž˜๋ชป๋œ ID ํ˜•์‹์ž…๋‹ˆ๋‹ค")
        return
    }
    
    var input struct {
        Title   string `json:"title" binding:"required,min=3,max=200"`
        Content string `json:"content" binding:"required"`
    }
    
    if err := c.ShouldBindJSON(&input); err != nil {
        utils.ResponseError(c, http.StatusBadRequest, err.Error())
        return
    }
    
    post, err := models.UpdatePost(id, userID.(int), input.Title, input.Content)
    if err != nil {
        if err.Error() == "์ด ๊ฒŒ์‹œ๊ธ€์„ ์ˆ˜์ •ํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค" {
            utils.ResponseError(c, http.StatusForbidden, err.Error())
        } else if err.Error() == "๊ฒŒ์‹œ๊ธ€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค" {
            utils.ResponseError(c, http.StatusNotFound, err.Error())
        } else {
            utils.ResponseError(c, http.StatusInternalServerError, err.Error())
        }
        return
    }
    
    utils.ResponseSuccess(c, http.StatusOK, post)
}

// DeletePost๋Š” ํŠน์ • ID์˜ ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค
func DeletePost(c *gin.Context) {
    // ์‚ฌ์šฉ์ž ID ๊ฐ€์ ธ์˜ค๊ธฐ (๋ฏธ๋“ค์›จ์–ด์—์„œ ์„ค์ •๋จ)
    userID, exists := c.Get("userID")
    if !exists {
        utils.ResponseError(c, http.StatusUnauthorized, "์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค")
        return
    }
    
    idStr := c.Param("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        utils.ResponseError(c, http.StatusBadRequest, "์ž˜๋ชป๋œ ID ํ˜•์‹์ž…๋‹ˆ๋‹ค")
        return
    }
    
    err = models.DeletePost(id, userID.(int))
    if err != nil {
        if err.Error() == "์ด ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค" {
            utils.ResponseError(c, http.StatusForbidden, err.Error())
        } else if err.Error() == "๊ฒŒ์‹œ๊ธ€์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค" {
            utils.ResponseError(c, http.StatusNotFound, err.Error())
        } else {
            utils.ResponseError(c, http.StatusInternalServerError, err.Error())
        }
        return
    }
    
    utils.ResponseSuccess(c, http.StatusOK, gin.H{
        "message": "๊ฒŒ์‹œ๊ธ€์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค",
    })
}

utils/response.go

package utils

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

// ResponseSuccess ํ•จ์ˆ˜๋Š” ์„ฑ๊ณต ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func ResponseSuccess(c *gin.Context, statusCode int, data interface{}) {
    c.JSON(statusCode, gin.H{
        "success": true,
        "data":    data,
    })
}

// ResponseError ํ•จ์ˆ˜๋Š” ์˜ค๋ฅ˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
func ResponseError(c *gin.Context, statusCode int, message string) {
    c.JSON(statusCode, gin.H{
        "success": false,
        "error":   message,
    })
}

routes/routes.go

package routes

import (
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-blog-api/controllers"
    "github.com/yourusername/go-blog-api/middleware"
)

// SetupRouter ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ๋ผ์šฐํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค
func SetupRouter() *gin.Engine {
    r := gin.Default()
    
    // ๊ธฐ๋ณธ ๋ผ์šฐํŠธ
    r.GET("/", controllers.HelloWorld)
    
    // ์ธ์ฆ ๊ด€๋ จ ๋ผ์šฐํŠธ
    r.POST("/register", controllers.Register)
    r.POST("/login", controllers.Login)
    
    // API v1 ๊ทธ๋ฃน
    v1 := r.Group("/api/v1")
    {
        // ์‚ฌ์šฉ์ž ๊ด€๋ จ ์—”๋“œํฌ์ธํŠธ
        users := v1.Group("/users")
        {
            users.GET("", controllers.GetUsers)
            users.GET("/:id", controllers.GetUser)
            
            // ์ธ์ฆ์ด ํ•„์š”ํ•œ ์—”๋“œํฌ์ธํŠธ
            authUsers := users.Group("")
            authUsers.Use(middleware.AuthRequired())
            {
                authUsers.PUT("/:id", controllers.UpdateUser)
                authUsers.DELETE("/:id", controllers.DeleteUser)
            }
        }
        
        // ๊ฒŒ์‹œ๊ธ€ ๊ด€๋ จ ์—”๋“œํฌ์ธํŠธ
        posts := v1.Group("/posts")
        {
            posts.GET("", controllers.GetPosts)
            posts.GET("/:id", controllers.GetPost)
            
            // ์ธ์ฆ์ด ํ•„์š”ํ•œ ์—”๋“œํฌ์ธํŠธ
            authPosts := posts.Group("")
            authPosts.Use(middleware.AuthRequired())
            {
                authPosts.POST("", controllers.CreatePost)
                authPosts.PUT("/:id", controllers.UpdatePost)
                authPosts.DELETE("/:id", controllers.DeletePost)
            }
            
            // ๋Œ“๊ธ€ ๊ด€๋ จ ์—”๋“œํฌ์ธํŠธ
            comments := posts.Group("/:id/comments")
            {
                comments.GET("", controllers.GetComments)
                
                // ์ธ์ฆ์ด ํ•„์š”ํ•œ ์—”๋“œํฌ์ธํŠธ
                authComments := comments.Group("")
                authComments.Use(middleware.AuthRequired())
                {
                    authComments.POST("", controllers.CreateComment)
                }
            }
        }
        
        // ๋Œ“๊ธ€ ์ˆ˜์ •/์‚ญ์ œ ์—”๋“œํฌ์ธํŠธ
        comments := v1.Group("/comments")
        comments.Use(middleware.AuthRequired())
        {
            comments.PUT("/:id", controllers.UpdateComment)
            comments.DELETE("/:id", controllers.DeleteComment)
        }
    }
    
    return r
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์™„์ „ํ•œ ๋ธ”๋กœ๊ทธ API๊ฐ€ ๊ตฌํ˜„๋˜์—ˆ์–ด์š”! ์ด API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ํšŒ์›๊ฐ€์ž…ํ•˜๊ณ , ๋กœ๊ทธ์ธํ•˜๊ณ , ๊ฒŒ์‹œ๊ธ€์„ ์ž‘์„ฑํ•˜๊ณ , ๋Œ“๊ธ€์„ ๋‹ฌ ์ˆ˜ ์žˆ์–ด์š”. ๐ŸŽ‰

๐ŸŒŸ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค!

์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ Go ์–ธ์–ด๋กœ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์› ์–ด์š”! ์ด ์ง€์‹์„ ํ™œ์šฉํ•ด ๋‹ค์–‘ํ•œ ์›น ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”. ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์—ฌ๋Ÿฌ๋ถ„์˜ API ๊ฐœ๋ฐœ ์‹ค๋ ฅ์„ ๋ฝ๋‚ด๋ณด๋Š” ๊ฒƒ์€ ์–ด๋–จ๊นŒ์š”?

9. ๋งˆ์น˜๋ฉฐ: Go๋กœ RESTful API ๊ฐœ๋ฐœ์˜ ์žฅ์  ๐Ÿ†

์ง€๊ธˆ๊นŒ์ง€ Go ์–ธ์–ด๋กœ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์–ด์š”. ๋งˆ์ง€๋ง‰์œผ๋กœ Go๋กœ API๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ์˜ ์žฅ์ ์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ์š”:

  1. ๋›ฐ์–ด๋‚œ ์„ฑ๋Šฅ - Go๋Š” ์ปดํŒŒ์ผ ์–ธ์–ด๋กœ, ์ธํ„ฐํ”„๋ฆฌํ„ฐ ์–ธ์–ด๋ณด๋‹ค ์‹คํ–‰ ์†๋„๊ฐ€ ๋น ๋ฅด๊ณ  ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ ์–ด์š”.
  2. ๊ฐ„๊ฒฐํ•œ ๋ฌธ๋ฒ• - Go์˜ ๋ฌธ๋ฒ•์€ ๋‹จ์ˆœํ•˜๊ณ  ์ง๊ด€์ ์ด์–ด์„œ ๋ฐฐ์šฐ๊ธฐ ์‰ฝ๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•ด์š”.
  3. ๊ฐ•๋ ฅํ•œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ - ๊ณ ๋ฃจํ‹ด๊ณผ ์ฑ„๋„์„ ํ†ตํ•ด ๋™์‹œ์— ๋งŽ์€ ์š”์ฒญ์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์š”.
  4. ํ’๋ถ€ํ•œ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - HTTP ์„œ๋ฒ„, JSON ์ฒ˜๋ฆฌ ๋“ฑ API ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์ด ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋˜์–ด ์žˆ์–ด์š”.
  5. ์ •์  ํƒ€์ž… ์‹œ์Šคํ…œ - ์ปดํŒŒ์ผ ์‹œ์ ์— ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์–ด ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์–ด์š”.
  6. ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์ง€์› - ๋‹ค์–‘ํ•œ ์šด์˜์ฒด์ œ์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ๋‹จ์ผ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ์ปดํŒŒ์ผ๋˜์–ด ๋ฐฐํฌ๊ฐ€ ๊ฐ„ํŽธํ•ด์š”.
  7. ํ™œ๋ฐœํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ - ์ „ ์„ธ๊ณ„์ ์œผ๋กœ ํ™œ๋ฐœํ•œ Go ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ์žˆ์–ด ๋„์›€์„ ๋ฐ›๊ธฐ ์‰ฌ์›Œ์š”.

Go ์–ธ์–ด๋Š” ํŠนํžˆ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์™€ ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ์ ํ•ฉํ•ด์š”. Docker, Kubernetes, Prometheus ๋“ฑ ๋งŽ์€ ํด๋ผ์šฐ๋“œ ๋„๊ตฌ๋“ค์ด Go๋กœ ์ž‘์„ฑ๋˜์—ˆ๋‹ค๋Š” ์‚ฌ์‹ค๋งŒ ๋ด๋„ Go์˜ ๊ฐ•์ ์„ ์•Œ ์ˆ˜ ์žˆ์ฃ !

์—ฌ๋Ÿฌ๋ถ„๋„ ์ด์ œ Go๋กœ RESTful API๋ฅผ ๊ฐœ๋ฐœํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์–ด์š”. ์ด ๊ธ€์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ž์‹ ๋งŒ์˜ API๋ฅผ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. ํ˜น์‹œ ํ”„๋ฆฌ๋žœ์„œ๋กœ ํ™œ๋™ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์—์„œ Go ๊ฐœ๋ฐœ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์—์š”! ๐Ÿ˜Š

Go ์–ธ์–ด์˜ ๋งˆ์Šค์ฝ”ํŠธ์ธ ๊ณ ํผ(Gopher)์ฒ˜๋Ÿผ ์—ด์‹ฌํžˆ ์ฝ”๋”ฉํ•˜๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ Go ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด ์žˆ์„ ๊ฑฐ์˜ˆ์š”. ํ™”์ดํŒ…! ๐Ÿ’ช

Go + REST API โ†’ โœ“ Go๋กœ RESTful API ๊ฐœ๋ฐœํ•˜๊ธฐ

์ด ๊ธ€์ด ์—ฌ๋Ÿฌ๋ถ„์˜ Go ์–ธ์–ด ํ•™์Šต๊ณผ RESTful API ๊ฐœ๋ฐœ์— ๋„์›€์ด ๋˜์—ˆ๊ธธ ๋ฐ”๋ผ์š”. ๊ถ๊ธˆํ•œ ์ ์ด๋‚˜ ๋” ์•Œ๊ณ  ์‹ถ์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ฃผ์„ธ์š”! ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€์š”. ๐Ÿ‘จโ€๐Ÿ’ป๐Ÿ‘ฉโ€๐Ÿ’ป

๊ทธ๋Ÿผ ๋‹ค์Œ์— ๋˜ ๋งŒ๋‚˜์š”! ์•ˆ๋…•~ ๐Ÿ‘‹

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.