Ver2.0 ๐ Go ์ธ์ด๋ก RESTful API ๊ฐ๋ฐํ๊ธฐ: ์ด๋ณด์๋ ์ฝ๊ฒ ๋ฐ๋ผํ๋ ์๋ฒฝ ๊ฐ์ด๋ ๐

๐ Go ์ธ์ด๋ก RESTful API ๊ฐ๋ฐํ๊ธฐ: ์ด๋ณด์๋ ์ฝ๊ฒ ๋ฐ๋ผํ๋ ์๋ฒฝ ๊ฐ์ด๋ ๐
์๋ ํ์ธ์, ๊ฐ๋ฐ์ ์ฌ๋ฌ๋ถ! ๐ ์ค๋์ 2025๋ 3์ 24์ผ ๊ธฐ์ค์ผ๋ก ์ต์ ํธ๋ ๋๋ฅผ ๋ฐ์ํ Go ์ธ์ด๋ก RESTful API ๊ฐ๋ฐํ๊ธฐ์ ๋ํด ์์๋ณผ๊ฒ์. ์์ฆ ๋ฐฑ์๋ ๊ฐ๋ฐ์์ Go(๊ณ ์ธ์ด)๊ฐ ์ง์ง ํซํ๋ค๋ ๊ฑฐ ๋ค๋ค ์์์ฃ ? ์๋ ๋น ๋ฅด๊ณ , ๋์์ฑ ์ฒ๋ฆฌ ์ฉ๊ณ , ์ฌํํ ๋ฌธ๋ฒ๊น์ง... ์ง์ง ๊ฐ๋ฐ์๋ค ์ฌ์ด์์ ์ธ๊ธฐ ํญ๋ฐ์ด์์! ใ ใ ใ
์ด ๊ธ์ ํตํด Go๋ก RESTful API๋ฅผ ๊ฐ๋ฐํ๋ ๋ฐฉ๋ฒ์ ์ฒ์๋ถํฐ ๋๊น์ง ๋ฐฐ์๋ณผ ๊ฑด๋ฐ์, ์ฝ๋ ํ ์ค ์ ์จ๋ณธ ์ด๋ณด์๋ถ๋ค๋ ๊ฑฑ์ ๋ ธ๋ ธ! ์ฝ๊ฒ ์ค๋ช ํด๋๋ฆด๊ฒ์. ์ค๋ฌด์์ ๋ฐ๋ก ์จ๋จน์ ์ ์๋ ๊ฟํ๋ค๋ ๊ฐ๋ ์ค๋นํ๋ต๋๋ค. ๐
๐ ์ด ๊ธ์์ ๋ฐฐ์ธ ๋ด์ฉ:
- Go ์ธ์ด์ ๊ธฐ๋ณธ ๊ฐ๋ ๊ณผ RESTful API ์๊ฐ
- ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ ํ๊ธฐ
- ์ฒซ ๋ฒ์งธ Go API ์๋ฒ ๋ง๋ค๊ธฐ
- ๋ผ์ฐํ ๊ณผ ํธ๋ค๋ฌ ๊ตฌํํ๊ธฐ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋ํ๊ธฐ
- ๋ฏธ๋ค์จ์ด์ ์ธ์ฆ ๊ตฌํํ๊ธฐ
- ํ ์คํธ์ ๋ฐฐํฌ ๋ฐฉ๋ฒ
- ์ค์ ํ๋ก์ ํธ: ์์ ํ RESTful API ๊ตฌํ
1. Go ์ธ์ด์ RESTful API: ์ฐฐ๋ก๊ถํฉ์ธ ์ด์ ๐
Go ์ธ์ด(์ผ๋ช Golang)๋ 2009๋ ์ ๊ตฌ๊ธ์์ ๋ง๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ธ๋ฐ, 2025๋ ํ์ฌ ๋ฐฑ์๋ ๊ฐ๋ฐ์์ ์์ ๋์ธ๋ก ์๋ฆฌ์ก์์ด์. ํนํ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ๊ฐ ๋์ธ์ธ ์์ฆ, Go๋ ๊ทธ ๋จ์ํจ๊ณผ ๋ฐ์ด๋ ์ฑ๋ฅ์ผ๋ก ๊ฐ๋ฐ์๋ค์ ๋ง์์ ์ฌ๋ก์ก๊ณ ์์ฃ .
RESTful API๋ ์น ์๋น์ค๋ฅผ ๊ตฌ์ถํ๋ ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๋ฐฉ์ ์ค ํ๋๋ก, HTTP ํ๋กํ ์ฝ์ ๊ธฐ๋ฐ์ผ๋ก ๋ฆฌ์์ค์ ์ ๊ทผํ๊ณ ์กฐ์ํ๋ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํด์. Go ์ธ์ด๋ ์ด๋ฐ RESTful API๋ฅผ ๊ฐ๋ฐํ๊ธฐ์ ์๋ฒฝํ ์ธ์ด๋๋๋ค!
Go ์ธ์ด๊ฐ RESTful API ๊ฐ๋ฐ์ ์ฐฐ๋ก๊ถํฉ์ธ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์์:
- ๋น ๋ฅธ ์คํ ์๋ - ์ปดํ์ผ ์ธ์ด๋ผ์ ์ธํฐํ๋ฆฌํฐ ์ธ์ด๋ณด๋ค ์คํ ์๋๊ฐ ๋น ๋ฅด๊ณ , ์๋ฒ ์๋ต ์๊ฐ์ด ์งง์์.
- ๋์์ฑ ์ฒ๋ฆฌ - ๊ณ ๋ฃจํด(Goroutine)๊ณผ ์ฑ๋(Channel)์ ํตํด ๋์์ ๋ง์ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์์ด์.
- ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฐ์ - HTTP ์๋ฒ๋ฅผ ๊ตฌํํ๋ ๋ฐ ํ์ํ ๊ธฐ๋ฅ๋ค์ด ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ด๋ฏธ ํฌํจ๋์ด ์์ด์.
- ๊ฐ๊ฒฐํ ์ฝ๋ - ์ ์ ์ฝ๋๋ก ๋ง์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์ด ์ ์ง๋ณด์๊ฐ ์ฌ์์.
์ด์ 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
๊ฐ ํจํค์ง์ ์ญํ ์ ๋ค์๊ณผ ๊ฐ์์:
- gin: ๊ฐ๋ณ๊ณ ๋น ๋ฅธ HTTP ์น ํ๋ ์์ํฌ
- go-sql-driver/mysql: MySQL ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋๋ผ์ด๋ฒ
- jwt: JSON Web Token ์ธ์ฆ์ ์ํ ํจํค์ง
- 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
์ด๋ฐ ๊ตฌ์กฐ๋ก ํ๋ก์ ํธ๋ฅผ ๋๋๋ฉด ์ฝ๋ ๊ด๋ฆฌ๊ฐ ํจ์ฌ ์ฌ์์ ธ์. ๊ฐ ๋๋ ํ ๋ฆฌ์ ์ญํ ์ ๋ค์๊ณผ ๊ฐ์์:
- controllers: ์์ฒญ์ ์ฒ๋ฆฌํ๋ ํธ๋ค๋ฌ ํจ์๋ค
- models: ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ด๋ จ ์ฝ๋
- routes: API ๋ผ์ฐํธ ์ ์
- 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
์ด์ ๋ค์๊ณผ ๊ฐ์ ์๋ํฌ์ธํธ๋ฅผ ํ ์คํธํด๋ณผ ์ ์์ด์:
- GET /api/v1/users - ๋ชจ๋ ์ฌ์ฉ์ ๋ชฉ๋ก ์กฐํ
- GET /api/v1/users/1 - ID๊ฐ 1์ธ ์ฌ์ฉ์ ์กฐํ
- POST /api/v1/users - ์ ์ฌ์ฉ์ ์์ฑ
- PUT /api/v1/users/1 - ID๊ฐ 1์ธ ์ฌ์ฉ์ ์ ๋ณด ์ ๋ฐ์ดํธ
- DELETE /api/v1/users/1 - ID๊ฐ 1์ธ ์ฌ์ฉ์ ์ญ์
์ด๋ ๊ฒ ๊ธฐ๋ณธ์ ์ธ CRUD(Create, Read, Update, Delete) ๊ธฐ๋ฅ์ ๊ฐ์ถ 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์์๋ ๋ ๊ฐ์ง ์ฃผ์ ๋งค๊ฐ๋ณ์ ์ ํ์ ์ฌ์ฉํด์:
- ๊ฒฝ๋ก ๋งค๊ฐ๋ณ์(Path Parameters): URL ๊ฒฝ๋ก์ ์ผ๋ถ๋ก ์ฌ์ฉ (์: /users/:id)
- ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์(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 ํ ํฐ์ ๋ฐ๊ณ , ์ด ํ ํฐ์ ์ฌ์ฉํด ๋ณดํธ๋ ์๋ํฌ์ธํธ์ ์ ๊ทผํ ์ ์์ด์. ๐
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 ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐฐํฌํ ์ ์๋ ๋ค์ํ ํด๋ผ์ฐ๋ ์๋น์ค๊ฐ ์์ด์. ๋ช ๊ฐ์ง ์ธ๊ธฐ ์๋ ์ต์ ์ ์๊ฐํ ๊ฒ์:
- AWS Elastic Beanstalk: Docker ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํด ์ฝ๊ฒ ๋ฐฐํฌํ ์ ์์ด์.
- Google Cloud Run: ์๋ฒ๋ฆฌ์ค ์ปจํ ์ด๋ ํ๋ซํผ์ผ๋ก, ํธ๋ํฝ์ ๋ฐ๋ผ ์๋์ผ๋ก ํ์ฅ๋ผ์.
- Heroku: Go ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๊ฒ ๋ฐฐํฌํ ์ ์๋ PaaS ์๋น์ค์์.
- 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์ ์๋ํฌ์ธํธ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ค๊ณํด๋ณผ๊ฒ์:
- ์ธ์ฆ
- POST /register - ํ์๊ฐ์
- POST /login - ๋ก๊ทธ์ธ
- ์ฌ์ฉ์
- GET /api/v1/users - ๋ชจ๋ ์ฌ์ฉ์ ์กฐํ
- GET /api/v1/users/:id - ํน์ ์ฌ์ฉ์ ์กฐํ
- PUT /api/v1/users/:id - ์ฌ์ฉ์ ์ ๋ณด ์ ๋ฐ์ดํธ (์ธ์ฆ ํ์)
- DELETE /api/v1/users/:id - ์ฌ์ฉ์ ์ญ์ (์ธ์ฆ ํ์)
- ๊ฒ์๊ธ
- GET /api/v1/posts - ๋ชจ๋ ๊ฒ์๊ธ ์กฐํ
- GET /api/v1/posts/:id - ํน์ ๊ฒ์๊ธ ์กฐํ
- POST /api/v1/posts - ๊ฒ์๊ธ ์์ฑ (์ธ์ฆ ํ์)
- PUT /api/v1/posts/:id - ๊ฒ์๊ธ ์์ (์ธ์ฆ ํ์)
- DELETE /api/v1/posts/:id - ๊ฒ์๊ธ ์ญ์ (์ธ์ฆ ํ์)
- ๋๊ธ
- 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๋ฅผ ๊ฐ๋ฐํ ๋์ ์ฅ์ ์ ์ ๋ฆฌํด๋ณผ๊ฒ์:
- ๋ฐ์ด๋ ์ฑ๋ฅ - Go๋ ์ปดํ์ผ ์ธ์ด๋ก, ์ธํฐํ๋ฆฌํฐ ์ธ์ด๋ณด๋ค ์คํ ์๋๊ฐ ๋น ๋ฅด๊ณ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ์ ์ด์.
- ๊ฐ๊ฒฐํ ๋ฌธ๋ฒ - Go์ ๋ฌธ๋ฒ์ ๋จ์ํ๊ณ ์ง๊ด์ ์ด์ด์ ๋ฐฐ์ฐ๊ธฐ ์ฝ๊ณ ์ ์ง๋ณด์๊ฐ ์ฉ์ดํด์.
- ๊ฐ๋ ฅํ ๋์์ฑ ์ฒ๋ฆฌ - ๊ณ ๋ฃจํด๊ณผ ์ฑ๋์ ํตํด ๋์์ ๋ง์ ์์ฒญ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ด์.
- ํ๋ถํ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ - HTTP ์๋ฒ, JSON ์ฒ๋ฆฌ ๋ฑ API ๊ฐ๋ฐ์ ํ์ํ ๊ธฐ๋ฅ์ด ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํฌํจ๋์ด ์์ด์.
- ์ ์ ํ์ ์์คํ - ์ปดํ์ผ ์์ ์ ์ค๋ฅ๋ฅผ ๋ฐ๊ฒฌํ ์ ์์ด ๋ฐํ์ ์ค๋ฅ๋ฅผ ์ค์ผ ์ ์์ด์.
- ํฌ๋ก์ค ํ๋ซํผ ์ง์ - ๋ค์ํ ์ด์์ฒด์ ์์ ์คํ ๊ฐ๋ฅํ ๋จ์ผ ๋ฐ์ด๋๋ฆฌ๋ก ์ปดํ์ผ๋์ด ๋ฐฐํฌ๊ฐ ๊ฐํธํด์.
- ํ๋ฐํ ์ปค๋ฎค๋ํฐ - ์ ์ธ๊ณ์ ์ผ๋ก ํ๋ฐํ Go ์ปค๋ฎค๋ํฐ๊ฐ ์์ด ๋์์ ๋ฐ๊ธฐ ์ฌ์์.
Go ์ธ์ด๋ ํนํ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์ ํด๋ผ์ฐ๋ ๋ค์ดํฐ๋ธ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ ํฉํด์. Docker, Kubernetes, Prometheus ๋ฑ ๋ง์ ํด๋ผ์ฐ๋ ๋๊ตฌ๋ค์ด Go๋ก ์์ฑ๋์๋ค๋ ์ฌ์ค๋ง ๋ด๋ Go์ ๊ฐ์ ์ ์ ์ ์์ฃ !
์ฌ๋ฌ๋ถ๋ ์ด์ Go๋ก RESTful API๋ฅผ ๊ฐ๋ฐํ ์ค๋น๊ฐ ๋์์ด์. ์ด ๊ธ์์ ๋ฐฐ์ด ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์์ ๋ง์ API๋ฅผ ๋ง๋ค์ด๋ณด์ธ์. ํน์ ํ๋ฆฌ๋์๋ก ํ๋ํ๊ณ ์ถ๋ค๋ฉด, ์ฌ๋ฅ๋ท(https://www.jaenung.net)์์ Go ๊ฐ๋ฐ ์๋น์ค๋ฅผ ์ ๊ณตํด๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์์! ๐
Go ์ธ์ด์ ๋ง์ค์ฝํธ์ธ ๊ณ ํผ(Gopher)์ฒ๋ผ ์ด์ฌํ ์ฝ๋ฉํ๋ค ๋ณด๋ฉด ์ด๋์ Go ์ ๋ฌธ๊ฐ๊ฐ ๋์ด ์์ ๊ฑฐ์์. ํ์ดํ ! ๐ช
์ด ๊ธ์ด ์ฌ๋ฌ๋ถ์ Go ์ธ์ด ํ์ต๊ณผ RESTful API ๊ฐ๋ฐ์ ๋์์ด ๋์๊ธธ ๋ฐ๋ผ์. ๊ถ๊ธํ ์ ์ด๋ ๋ ์๊ณ ์ถ์ ๋ด์ฉ์ด ์๋ค๋ฉด ์ธ์ ๋ ์ง ์ง๋ฌธํด์ฃผ์ธ์! ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๋ฅผ ๋ง๋ค์ด๊ฐ์. ๐จโ๐ป๐ฉโ๐ป
๊ทธ๋ผ ๋ค์์ ๋ ๋ง๋์! ์๋ ~ ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.