[A-00232] Golang 1000本ノック(1)
go言語を使って色々作ってみるシリーズです。
初回はRestAPIサーバーを作ります。
プロジェクトの初期化を実行します。
go mod init rest-api
ディレクトリ構造は下記の通り
.
├── Makefile
├── bin
│ ├── main
│ └── main-freebsd-386
├── command.sh
├── docker-compose.yml
├── go.mod
├── go.sum
├── main.go
├── script // 不要
└── tree.txt //不要
下記のライブラリをインストールしておく
go get -u github.com/gin-gonic/gin
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
go get -u github.com/joho/godotenv
次にdocker-compose.ymlを作成し、postgreSQLを立ち上げておく
version: '3'
services:
db:
image: postgres:14
container_name: postgres
ports:
- 5432:5432
volumes:
- db-store:/var/lib/postgresql/data
- ./script:/docker-entrypoint-initdb.d
environment:
- POSTGRES_PASSWORD=password
volumes:
db-store:
下記のコマンドでpostgreSQLを立ち上げておく
docker compose up -d
デフォルトのDBは[postgres]になる。上記の情報を環境変数に設定して、プログラム実行時に読み込めるようにしておく。この処理はgodotenvを使ってます。
POSTGRES_PASSWORD=password
POSTGRES_USER=postgres
POSTGRES_DB=postgres
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
次にmakeコマンドで簡単にビルドできるようにMakefileを作成しておく
hello:
echo "Hello"
build:
go build -o bin/main main.go
run:
go run main.go
compile:
echo "Compiling for every OS and Platform"
GOOS=freebsd GOARCH=386 go build -o bin/main-freebsd-386 main.go
GOOS=linux GOARCH=386 go build -o bin/main-linux-386 main.go
GOOS=windows GOARCH=386 go build -o bin/main-windows-386 main.go
試しに下記のコマンドを実行してhelloが出力されるかを確認する。
user@usernoMacBook-Pro rest-api-server % make hello
echo "Hello"
Hello
最後にRestAPIを作成します。
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Task struct {
ID uint `gorm:"primary_key"`
Task string `gorm:"size:255"`
IsCompleted bool `gorm:"default:false"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
}
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
dbUser := os.Getenv("POSTGRES_USER")
dbPassword := os.Getenv("POSTGRES_PASSWORD")
dbName := os.Getenv("POSTGRES_DB")
dbHost := os.Getenv("POSTGRES_HOST")
dbPort := os.Getenv("POSTGRES_PORT")
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Tokyo", dbHost, dbUser, dbPassword, dbName, dbPort)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
db.AutoMigrate(&Task{})
r := gin.Default()
// endpoint: tasks GET
r.GET("/tasks", func(ctx *gin.Context) {
var tasks []Task
db.Find(&tasks)
ctx.JSON(http.StatusOK, tasks)
})
// endpoint: tasks POST
r.POST("/tasks", func(ctx *gin.Context) {
var task Task
if err := ctx.ShouldBindJSON(&task); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db.Create(&task)
ctx.JSON(http.StatusOK, task)
})
// endpoint: tasks PUT
r.PUT("/tasks/:id", func(ctx *gin.Context) {
var task Task
id := ctx.Param("id")
if err := db.First(&task, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
return
}
if err := ctx.ShouldBindJSON(&task); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db.Save(&task)
ctx.JSON(http.StatusOK, task)
})
// endpoint tasks DELETE
r.DELETE("/tasks/:id", func(ctx *gin.Context) {
var task Task
id := ctx.Param("id")
if err := db.First(&task, id).Error; err != nil {
ctx.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
return
}
db.Delete(&task)
ctx.JSON(http.StatusOK, gin.H{"message": "Task deleted"})
})
// endpoint: home GET
r.GET("/", func(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": "Hello gin.",
})
})
r.Run(":8080")
}
上記を作成後、makeコマンドで実行してみます。
user@usernoMacBook-Pro rest-api-server % make run
go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /tasks --> main.main.func1 (3 handlers)
[GIN-debug] POST /tasks --> main.main.func2 (3 handlers)
[GIN-debug] PUT /tasks/:id --> main.main.func3 (3 handlers)
[GIN-debug] DELETE /tasks/:id --> main.main.func4 (3 handlers)
[GIN-debug] GET / --> main.main.func5 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
とりあえず各エンドポイントの動作を確認します。
user@usernoMacBook-Pro rest-api-server % curl http://localhost:8080/
{"message":"Hello gin."}%
user@usernoMacBook-Pro rest-api-server % curl -X POST -d '{"id":3, "task": "task1", "isCompleted": false}' http://localhost:8080/tasks
{"ID":3,"Task":"task1","IsCompleted":false,"CreatedAt":"2025-02-09T21:03:48.694701+09:00","UpdatedAt":"2025-02-09T21:03:48.694701+09:00"}%
user@usernoMacBook-Pro rest-api-server % curl -X GET -d '{"id":3}' http://localhost:8080/tasks
[{"ID":3,"Task":"task1","IsCompleted":false,"CreatedAt":"2025-02-09T21:03:48.694701+09:00","UpdatedAt":"2025-02-09T21:03:48.694701+09:00"}]%
user@usernoMacBook-Pro rest-api-server % curl -X PUT -d '{"task":"homework,hurry","isCompleted":true}' http://localhost:8080/tasks/3
{"ID":3,"Task":"homework,hurry","IsCompleted":true,"CreatedAt":"2025-02-09T21:03:48.694701+09:00","UpdatedAt":"2025-02-09T21:08:40.073993+09:00"}%
user@usernoMacBook-Pro rest-api-server % curl -X DELETE http://localhost:8080/tasks/3
{"message":"Task deleted"}%
上記の通りに戻り値が返ってくれば動作確認OKです。
・Appendix
参考文献はこちら
https://weblabo.oscasierra.net/curl-post
https://tutorialedge.net/golang/makefiles-for-go-developers
https://www.javadrive.jp/postgresql/table/index1.html
https://qiita.com/n4a/items/12169101624d854e661c
https://qiita.com/nakamasato/items/4155ec8acbb88af81543
https://qiita.com/Asaiii12/items/4ac73445ff8d9b75e02e
https://gorm.io/ja_JP/docs/index.html
https://gin-gonic.com/ja/docs/quickstart
https://zenn.dev/tmk616/articles/383fc3fbb0ec4b
https://qiita.com/soicchi/items/2637a9195e64fdc73609
コメントを残す