はじめに
この記事では、プログラミング言語のGoとWebフレームワークであるFiber v3を使って、リレーショナルデータベースのPostgreSQLをバックエンドに利用したCRUD (Create, Read, Update, Delete) 操作ができるWeb APIを作成する方法を説明します。
本来であれば、Fiber v3の新機能や変更点を活用したかったのですが、十分な調査を行う前に雰囲気で実装を進めてしまったため、本記事ではそれらを採用できておりません。深夜に検証を行っていたこともあり、ベストプラクティスとは言えない部分があることを認識しています。
エンジニアとして、新しいバージョンのフレームワークを使う際には、事前に十分な調査を行い、新機能や変更点を理解した上で実装を進めるべきでした。GitHub Copilot とLanguage Server で適当に書いてしまいました。また、コードの品質を保つためには、適切な時間帯に集中して作業を行うことが重要です(これはガチ)。
今回の実装では、これらの点が不十分であったことを反省しています。業務では、技術選定や実装方法について、より慎重に検討を行い、品質の高いコードを書くことを心がけたいと思います。
読者の皆様におかれましては、本記事の内容を参考にする際には、上記の点にご留意いただければ幸いです。
プロジェクトの初期化
まず、新しいGoプロジェクトを作成します。
mkdir fiber-crud-api cd fiber-crud-api go mod init github.com/yourusername/fiber-crud-api
必要なパッケージのインストール
次に、必要なパッケージをインストールします。
go get github.com/gofiber/fiber/v3 go get github.com/lib/pq
データベースの設定とモデルの定義
main.go
ファイルを作成し、以下のようにデータベースの設定とモデルを定義します。
package main import ( "database/sql" "encoding/json" "fmt" "log" "time" "github.com/gofiber/fiber/v3" _ "github.com/lib/pq" ) const ( host = "db" port = 5432 user = "postgres" password = "password" dbname = "mydb" ) // Connect to the database func Connect() (*sql.DB, error) { psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) db, err := sql.Open("postgres", psqlInfo) if err != nil { return nil, err } return db, nil } // User model type User struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` Password string `json:"password"` CreatedAt time.Time `json:"created_at"` } // Post model type Post struct { ID int `json:"id"` UserID int `json:"user_id"` Title string `json:"title"` Content string `json:"content"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` }
APIエンドポイントの実装
続けて、main.go
ファイルにAPIエンドポイントを実装します。v2 の時には存在していたctx のbodyparserがv3ではなくなっていたので手癖でJSONで返したのですがおそらくBind周りで実装するとよいみたいです(あとから気付きました...)。v2からv3の変更点については以下を参考にしてください。
func main() { app := fiber.New() // データベース接続 db, err := Connect() if err != nil { log.Fatal(err) } defer db.Close() // Create User app.Post("/users", func(c fiber.Ctx) error { user := new(User) if err := json.Unmarshal(c.Body(), user); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request body", }) } // パスワードのハッシュ化やバリデーションを行うことが推奨される // 簡易実装のため、ここでは省略 // データベースにユーザーを作成 _, err := db.Exec("INSERT INTO users (name, email, password) VALUES ($1, $2, $3)", user.Name, user.Email, user.Password) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Failed to create user", }) } return c.Status(fiber.StatusCreated).JSON(fiber.Map{ "message": "User created", }) }) // Get User app.Get("/users/:id", func(c fiber.Ctx) error { id := c.Params("id") // データベースからユーザーを取得 row := db.QueryRow("SELECT * FROM users WHERE id = $1", id) user := new(User) if err := row.Scan(&user.ID, &user.Name, &user.Email, &user.Password, &user.CreatedAt); err != nil { if err == sql.ErrNoRows { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "error": "User not found", }) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Failed to get user", }) } return c.JSON(user) }) // Create Post app.Post("/posts", func(c fiber.Ctx) error { post := new(Post) if err := json.Unmarshal(c.Body(), post); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request body", }) } // データベースに記事を作成 _, err := db.Exec("INSERT INTO posts (user_id, title, content) VALUES ($1, $2, $3)", post.UserID, post.Title, post.Content) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Failed to create post", }) } return c.Status(fiber.StatusCreated).JSON(fiber.Map{ "message": "Post created", }) }) // Get Post app.Get("/posts/:id", func(c fiber.Ctx) error { id := c.Params("id") // データベースから記事を取得 row := db.QueryRow("SELECT * FROM posts WHERE id = $1", id) post := new(Post) if err := row.Scan(&post.ID, &post.UserID, &post.Title, &post.Content, &post.CreatedAt, &post.UpdatedAt); err != nil { if err == sql.ErrNoRows { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "error": "Post not found", }) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Failed to get post", }) } return c.JSON(post) }) log.Fatal(app.Listen(":3000")) }
このコードでは、以下のエンドポイントを実装しています。
POST /users
: 新しいユーザーを作成します。GET /users/:id
: 指定されたIDのユーザーを取得します。POST /posts
: 新しい記事を作成します。GET /posts/:id
: 指定されたIDの記事を取得します。
Dockerfileとdocker-compose.ymlの作成
開発環境用のDockerfile
とdocker-compose.yml
を作成します。これも簡易的に用意した適当なファイルなので十分に吟味してください。
FROM golang:1.22-alpine WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o main . CMD ["./main"]
version: '3' services: app: build: . ports: - "3000:3000" depends_on: - db db: image: postgres environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: mydb volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql
データベースの初期化スクリプト
init.sql
ファイルを作成し、データベースの初期化スクリプトを記述します。
CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE posts ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id), title VARCHAR(255) NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
アプリケーションの実行
以下のコマンドを実行してアプリケーションを起動します。
docker-compose up --build
これで、GoとFiberを使ってCRUDができるAPIが作成され、PostgreSQLをバックエンドに利用する環境が整いました。APIエンドポイントをテストするために、cURLやPostmanなどのツールを使用してリクエストを送信できます。
APIのテストとデータベースの確認
アプリケーションを起動した後、CURLを使ってAPIエンドポイントをテストし、PostgreSQLの中身を確認してみましょう。
ユーザーの作成と確認
まず、新しいユーザーを作成します。
curl -X POST -H "Content-Type: application/json" -d '{"name":"John Doe","email":"john@example.com","password":"secret"}' http://localhost:3000/users
レスポンスとして、"User created"が返ってくるはずです。
次に、作成したユーザーを確認します。
curl http://localhost:3000/users/1
レスポンスとして、作成したユーザーの情報がJSON形式で返ってきます。
{"id":1,"name":"John Doe","email":"john@example.com","password":"secret","created_at":"2023-04-24T12:34:56Z"}
PostgreSQLの中身を確認するために、データベースにログインします。
docker-compose exec db psql -U postgres mydb
ユーザーテーブルの中身を確認します。
SELECT * FROM users;
作成したユーザーがテーブルに存在することを確認できます。
id | name | email | password | created_at ----+---------+----------------+----------+---------------------------- 1 | John Doe| john@example.com| secret | 2024-05-30 01:34:56.789012 (1 row)
記事の作成と確認
次に、新しい記事を作成します。
curl -X POST -H "Content-Type: application/json" -d '{"user_id":1,"title":"My First Post","content":"Hello, World!"}' http://localhost:3000/posts
レスポンスとして、"Post created"が返ってくるはずです。
作成した記事を確認します。
curl http://localhost:3000/posts/1
レスポンスとして、作成した記事の情報がJSON形式で返ってきます。
{"id":1,"user_id":1,"title":"My First Post","content":"Hello, World!","created_at":"2023-04-24T12:45:67Z","updated_at":"2023-04-24T12:45:67Z"}
再度、PostgreSQLの中身を確認します。
SELECT * FROM posts;
作成した記事がテーブルに存在することを確認できます。
id | user_id | title | content | created_at | updated_at ----+---------+--------------+---------------+----------------------------+---------------------------- 1 | 1 | My First Post| Hello, World! | 2024-05-30 01:45:67.890123 | 2024-05-30 01:45:67.890123 (1 row)
以上で、APIのテストとデータベースの確認が完了しました。
さいごに
以上が、GoとFiberを使ってCRUDができるAPIを作成し、PostgreSQLをバックエンドに利用する方法です。実際のアプリケーションでは、エラーハンドリングやバリデーションなどを追加し、より堅牢なAPIを作成することが重要です。また、認証や認可、ページネーション、フィルタリングなどの機能も必要になるでしょう。データベースのマイグレーションツールを使用して、テーブル構造の変更を管理することも忘れずに。本当に手癖でやってみただけです。楽しかったです。この記事を書いている時にはaikoを聴いていたのでプレイリストも共有です。