[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

https://www.thunderclient.com

https://zenn.dev/isawa/articles/069cff08d64904

https://dev.classmethod.jp/articles/go-sample-rest-api

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

*