LoginSignup
13
18

Next.js × Go のWebアプリをDocker上で作成してみませんか? 〜DB編〜

Last updated at Posted at 2023-08-17

はじめに

👋 こんにちは! 地方に在住し、フルリモートでWebエンジニアとして働いている @takakouと申します🏙️✨

前回の記事では、Next.js × Go のWebアプリをDocker上で環境構築する方法について解説しました。たくさんの方々から、多くのいいねやストックをいただき、本当に嬉しかったです!🚀💬

今回は、前回の記事で構築した環境をベースに、実際にAPIの開発をしていく前に、DBの設計、構築手順をお伝えします。 このステップでは、私自身が詰まったポイントや工夫した部分を共有しながら、みなさんと一緒にアプリケーションの構築を進めていきますのでよろしくお願いいたします。🌟

前回同様、記事執筆はまだまだ未熟で見づらい記事になってしまっていると思いますが、皆さんからのご意見や質問、アイデアなど、どんな些細なことでも構いませんので、お気軽にコメントしていただけると嬉しいです!💡📝

それでは、Next.jsとGoを組み合わせたWebアプリケーションの開発を楽しんでいきましょう!🎉🐳

目次

  1. 前提条件 💡
  2. 対象者 👨
  3. 動作環境 🖥️
  4. アプリ概要 📑
  5. 技術選定 🛠️
  6. 実装手順 🚀
  7. 参考文献 📚
  8. おわりに 🎉

1. 前提条件

今回の記事の前提条件は、前回の記事で既に Next.js×Goのモノレポ構成のDockerの環境を構築できていることです。そちらの記事をご覧になっていただいた後に、こちらの記事を確認いただけると、記事の内容について深く理解できると思いますのでよろしくお願いいたします。

2. 対象者

  • ある程度のWebアプリケーションに関する知識がある人
  • Dcokerを使ったことがある人
  • 必要な知識は自分自身である程度調べられる人

注意点
コードの詳しい説明は基本的に行いません
気になることがある場合は、記事を調べたり、chatGPTに聞いてみたりして自分で解決していただくようにお願いいたします。

3. 動作環境

  • PC : MacBook Air(M1,2020)

  • RAM : 8GB

  • OS : macOS Monterey(ver12.1)

注意点
Docker が使える前提で始めていきます。
Dockerが入っていない場合は、導入するようにお願いいたします。
Docker Compose V2を使っています。

4. アプリ概要

今回作成するWebアプリケーションの概要を簡単に決めておきたいと思います。

アプリ名

  • 本投稿サイト

機能

  • ユーザ認証機能(ログイン、新規登録)
  • 本投稿機能(CRUD)

設計図

API仕様書詳細設計などは次回以降の記事で作成していくので今回は省きます。

テーブル定義書

  • users
論理名 物理名 null default PK FK
ID id number ×
メールアドレス email string ×
パスワード password string ×
名前 name string ×
作成日時 created_at datetime × now
更新日時 updated_at datetime × now
  • books
論理名 物理名 null default PK FK
ID id number ×
ユーザID user_id number ×
タイトル title string ×
本文 body string ×
作成日時 created_at datetime × now
更新日時 updated_at datetime × now

5. 技術選定

フロントエンド

  • Language: TypeScript

  • Library: React

  • FW: Next.js

バックエンド

  • Language: Go

  • FW: gin

  • ORM: ent

  • DB: PostgreSQL

ポイント
ORMとは何ぞや?という方向けにORMに関する外部の記事を貼っておきます。

6. 実装手順

注意点
コンテナの終了については明記しないので適宜必要なタイミングでdownをしてください。
使わないコンテナは終了しておかないと、PCのディスク容量を圧迫してしまいます。

~ /Next_Go_App $ docker compose down

a. スキーマの作成

今回の記事ではent(ORM)を利用してスキーマの作成を行っていきます。
entを用いた実装に関しては公式リファレンスを参考にしています。
entについて、日本語での詳細な情報を知りたい場合はメルカリの記事を見ると参考になると思います。

  • コンテナを起動しshellへ入る
~ /Next_Go_App $ docker compose run backend sh
  • ユーザのスキーマを作成
# go run -mod=mod entgo.io/ent/cmd/ent new User
  • 本のスキーマを作成
# go run -mod=mod entgo.io/ent/cmd/ent new Book
  • 作成されたent/schema/user.goent/schema/book.goに変更を加える
ent/schema/user.go
package schema

- import "entgo.io/ent"
+ import (
+	 "entgo.io/ent"
+	 "entgo.io/ent/schema/edge"
+	 "entgo.io/ent/schema/field"
+	 "time"
+ )

// User holds the schema definition for the User entity.
type User struct {
	ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
-   return nil
+	return []ent.Field{
+		field.String("email").
+			Unique().
+			NotEmpty(),
+		field.String("password").
+			Sensitive().
+			NotEmpty(),
+		field.String("name").
+			NotEmpty(),
+		field.Time("created_at").
+			Default(time.Now),
+		field.Time("updated_at").
+			Default(time.Now).
+			UpdateDefault(time.Now),
+	}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
-	return nil
+	return []ent.Edge{
+		edge.To("books", Book.Type),
+	}
}
ent/schema/book.go
package schema

- import "entgo.io/ent"
+ import (
+	"entgo.io/ent"
+	"entgo.io/ent/schema/edge"
+	"entgo.io/ent/schema/field"
+	"time"
+ )

// Book holds the schema definition for the Book entity.
type Book struct {
	ent.Schema
}

// Fields of the Book.
func (Book) Fields() []ent.Field {
-   return nil
+	return []ent.Field{
+		field.Int("user_id").
+           Positive(),
+		field.String("title").
+			NotEmpty(),
+		field.String("body").
+			NotEmpty(),
+		field.Time("created_at").
+			Default(time.Now),
+		field.Time("updated_at").
+			Default(time.Now).
+			UpdateDefault(time.Now),
+	}
}

// Edges of the Book.
func (Book) Edges() []ent.Edge {
-   return nil
+	return []ent.Edge{
+		edge.From("user", User.Type).
+				Ref("books").
+				Field("user_id").
+				Unique().
+				Required(),
+	}
}

fieldの設定、edgeの設定については下記の公式リファレンスを参照してみると良いと思います。

  • 再度コンテナのshellに入る
~ /Next_Go_App/backend $ docker compose run backend sh
  • go generateコマンドを実行
# go generate ./ent
  • backendのフォルダ構成
    コマンド実行後、下記のフォルダ構成になっていればスキーマ作成の準備は完了しています。

注意点
ファイルが新規で作成されない場合はエラーで止まっている可能性が高いのでshellのログを確認してみてください。

backend
├── Dockerfile
├── ent
│   ├── book
│   │   ├── book.go
│   │   └── where.go
│   ├── book.go
│   ├── book_create.go
│   ├── book_delete.go
│   ├── book_query.go
│   ├── book_update.go
│   ├── client.go
│   ├── ent.go
│   ├── enttest
│   │   └── enttest.go
│   ├── generate.go
│   ├── hook
│   │   └── hook.go
│   ├── migrate
│   │   ├── migrate.go
│   │   └── schema.go
│   ├── mutation.go
│   ├── predicate
│   │   └── predicate.go
│   ├── runtime
│   │   └── runtime.go
│   ├── runtime.go
│   ├── schema
│   │   ├── book.go
│   │   └── user.go
│   ├── tx.go
│   ├── user
│   │   ├── user.go
│   │   └── where.go
│   ├── user.go
│   ├── user_create.go
│   ├── user_delete.go
│   ├── user_query.go
│   └── user_update.go
├── go.mod
├── go.sum
└── main.go

b. 必要なパッケージを追加

package main

import (
	"github.com/gin-gonic/gin"
+	"context"
+   "log"
+	"Next_Go_App/ent"
+	_ "github.com/lib/pq"
)
func main() {
	//Ginフレームワークのデフォルトの設定を使用してルータを作成
	router := gin.Default()
	
	// ルートハンドラの定義
	router.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello, World!",
		})
	})

	// サーバー起動
	router.Run(":8080")
}
  • shellに入って追記されたパッケージをinstallする
~ /Next_Go_App/backend $ docker compose run backend sh

# go mod tidy

インストールが無事に完了したら問題ないです。

c. PostgreSQLとの接続設定

package main

import (
	"github.com/gin-gonic/gin"
	"context"
    "log"
	"github.com/j19015/Next_Go_Bookers2/ent"
	_ "github.com/lib/pq"
)
func main() {
	//Ginフレームワークのデフォルトの設定を使用してルータを作成
	router := gin.Default()

+	//PostgreSQLに接続
+   client, err := ent.Open("postgres", "host=db port=5432 user=postgres dbname=db password=password sslmode=disable")
+
+	if err != nil {
+		log.Fatalf("failed opening connection to postgres: %v", err)
+	}
+	// Run the auto migration tool.
+	if err := client.Schema.Create(context.Background()); err != nil {
+		log.Fatalf("failed creating schema resources: %v", err)
+	}
	
	// ルートハンドラの定義
	router.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello, World!",
		})
	})

	// サーバー起動
	router.Run(":8080")
}

backendのコンテナが起動した際に、dbのコンテナへ通信し、スキーマの作成を行っています。
hostやportの設定はcompose.ymlの記述で変わりますので気を付けてください。
Auto migrationについては「ent」の公式リファレンスなどを参照することをお勧めします。

d.テーブルの作成確認

  • コンテナを起動
~ /Next_Go_App $ docker compose up backend -d

注意点
ほかの記事等の再現や、個人開発で、Docker Composeを使用してデータベースを含むコンテナを永続化させた場合、コンテナ終了後にその永続化されたデータボリュームが残っていると、次回コンテナを起動する際に競合やエラーが発生する可能性があります。その際は下記コマンドにて永続化されているデータボリュームをリセットしてから再度コンテナの起動を試してください。

~ /Next_Go_App $ docker compose down -v
  • 起動しているdbのbashに入る
~ /Next_Go_App $ docker compose exec db bash
  • dbに接続
dbコンテナID:/# psql -U postgres -d db
  • テーブル確認コマンド
db= # \dt

         List of relations
 Schema | Name  | Type  |  Owner   
--------+-------+-------+----------
 public | books | table | postgres
 public | users | table | postgres
(2 rows)
  • Userテーブルの確認
db=# \d users

   Column   |           Type           | Collation | Nullable |             Default              
------------+--------------------------+-----------+----------+----------------------------------
 id         | bigint                   |           | not null | generated by default as identity
 email      | character varying        |           | not null | 
 password   | character varying        |           | not null | 
 name       | character varying        |           | not null | 
 created_at | timestamp with time zone |           | not null | 
 updated_at | timestamp with time zone |           | not null | 
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "users_email_key" UNIQUE, btree (email)
Referenced by:
    TABLE "books" CONSTRAINT "books_users_books" FOREIGN KEY (user_id) REFERENCES users(id)
  • Bookテーブルの確認
db=# \d books

   Column   |           Type           | Collation | Nullable |             Default              
------------+--------------------------+-----------+----------+----------------------------------
 id         | bigint                   |           | not null | generated by default as identity
 title      | character varying        |           | not null | 
 body       | character varying        |           | not null | 
 created_at | timestamp with time zone |           | not null | 
 updated_at | timestamp with time zone |           | not null | 
 user_id    | bigint                   |           | not null | 
Indexes:
    "books_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "books_users_books" FOREIGN KEY (user_id) REFERENCES users(id)

それぞれ正常にスキーマが作成されていることが確認できました。
default値やindex、外部キーの説明はこの記事では、省かせていただきますので気になる方は調べてみてください。

7. 参考文献

8. おわりに

今回の記事では、DBの中で自分が作成したいスキーマが正常に構築できていることを確認しました💾

正直自分が一人でやるときは、リファレンスが少なくかなり苦労しました💦

次回の記事では、APIの作成方法について今回同様ハンズオン形式で説明していきたいと思っています 📝🔗

もし興味を持っていただけましたら、ぜひ続きも読んでいただけるとうれしいです! 📖🌟(次回の公開は来月中を予定しています)

(最後に、気づいた方もいらっしゃると思うのですが、絵文字を入れるのは苦手なので、文章に沿った絵文字を挿入するのに、ChatGPTを利用させていただきましたが、いかがでしたでしょうか。不快に思われた方がいたら申し訳ございません 🙇‍♂️)

(10/10追記)
続編となる記事を公開いたしました!
バックエンドの開発へと進まれる方はぜひ見ていただけますと嬉しいです!

13
18
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
18