【Go言語】WebフレームワークEchoとent.を連携する方法(PostgreSQL使用)

今回は、Webフレームワーク①`Echo`と②`Ent.`を使って簡単なWebアプリを作ってみます。

こちらの記事を参考にしました↓

https://zenn.dev/mattn/articles/c08072b42f7a5cdcd749

`Echo公式サイト`

https://echo.labstack.com/docs/quick-start

`ent.公式サイト`

https://entgo.io/ja/docs/getting-started/

今回の記事では少し変更を加えています。

# ディレクトリ構成

```
MYECHO
├─ ent
│  ├─ entry
│  ├─ enttest
│  ├─ hook
│  ├─ migrate
│  ├─ prodicate
│  ├─ runtime
│  ├─ schema
│  │  └─ entry.go
│  ├─ client.go
│  ├─ ent.go
│  ├─ entry_create.go
│  ├─ entry_delete.go
│  ├─ entry_query.go
│  ├─ entry_update.go
│  ├─ entry.go
│  ├─ ganerate.go
│  ├─ mutation.go
│  ├─ runtime.go
│  └─ tx.go
├─ templates
│  └─ index.htm
├─ go.mod
├─ go.sum
└─ main.go
```

# サンプル
`http://localhost:8989/`にアクセスしてタスク投稿画面を起動します。
![ent画面起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/30843a18-46ce-4644-bea3-4f7cd4d1d520.png)

タスクを入力します。
![ent投稿内容入力.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/6e873aee-add6-489c-912b-231701527078.png)

タスクが無事登録されると、下記のキャプチャのように登録内容が表示されます。
![ent投稿内容結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/b5c802ac-60f7-4b02-aab8-7e628fb57f10.png)

## 変更点
■CSSファイルを読み込む設定を追加
■CSSフレームワークBootstrapを使用
■PostgreSQLを使用

# `Echoプロジェクト`を作成する
#### 【手順】
#### ■デスクトップにプロジェクトフォルダを作る
```
mkdir <プロジェクト名>

(例)
mkdir myEcho
```
#### ■Echo公式サイトの通りモジュールを初期化する。
```
go mod init <モジュール名>

(例)
go mod init myapp
```

#### ■Echo公式さとでEchoプロジェクトに必要なソースをインストールする。
```
go get github.com/labstack/echo/v4
```

#### ■Echoプロジェクト直下に「main.go」を追加する

#### ■プロジェクトフォルダ(例：myEchoフォルダ)に移動して、`entスキーマ`を作成する。
```
go run -mod=mod entgo.io/ent/cmd/ent new <スキーマ名>
(例)
go run -mod=mod entgo.io/ent/cmd/ent new Entry
```

#### ■スキーマを定義する。
```ent/schema/entry.go
package schema

import (
	"time"

	"entgo.io/ent"
	"entgo.io/ent/schema/field"
)

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

// Fields of the Entry.
func (Entry) Fields() []ent.Field { //←の関数を修正する
	return []ent.Field{
		field.String("content").Default(""),
		field.Time("created_at").Default(func() time.Time { return time.Now() }),
	}
}

// Edges of the Entry.
func (Entry) Edges() []ent.Edge {
	return nil
}

```

#### ■修正したスキーマをもとにクラスを自動酢生成する。
```
go generate ./ent
```

すると、下記のようにフォルダが自動で生成される。

![スキーマ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/f333d3c9-e645-4c71-9a42-d7f0722f8656.png)

#### ■`PostgreSQLドライバ`をインストールする。
```
go get github.com/lib/pq
```

https://github.com/lib/pq


#### ■モジュールの依存関係を解消する。

```
go mod tidy
```

#### ■`Echo`の最新モジュールをインストールする。
```
go get github.com/labstack/echo/v4
```

■`main.go`を修正する。

```main.go
package main

import (
	"context"
	"html/template"
	"io"
	"log"
	"myapp/ent"
	"myapp/ent/entry"
	"net/http"

	"github.com/labstack/echo/v4"
	_ "github.com/lib/pq"
)

// CustomRenderer is a custom renderer for HTML templates.
type CustomRenderer struct {
	templates *template.Template
}

// NewCustomRenderer creates a new CustomRenderer.
func NewCustomRenderer() *CustomRenderer {
	// Load all templates from the templates directory
	tmpl, err := template.ParseFiles("templates/index.html")
	if err != nil {
		log.Fatal("Error loading template:", err)
	}
	return &CustomRenderer{templates: tmpl}
}

// Render renders the template with the given name and data.
func (cr *CustomRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	// Render the template to the provided io.Writer (which is http.ResponseWriter)
	return cr.templates.ExecuteTemplate(w, name, data)
}

func main() {
	client, err := ent.Open("postgres", "host=<ホスト名> port=5432 user=<ユーザ名> dbname=<データベース名> password=<パスワード> sslmode=disable")
	if err != nil {
		log.Fatalf("failed opening connection to postgres: %v", err)
	}
	defer client.Close()

	// Run the auto migration tool.
	if err := client.Schema.Create(context.Background()); err != nil {
		log.Fatalf("failed creating schema resources: %v", err)
	}

	e := echo.New()

	// Set the custom renderer for rendering HTML templates
	e.Renderer = NewCustomRenderer()
	//スタイリングシートCSS読み込み設定
	e.Static("/css", "src/css")
	//Bootstrap読み込み設定
	e.Static("/bootstrap/css", "src/lib/bootstrap/css")

	e.GET("/", func(c echo.Context) error {
		// Fetch the 10 most recent entries
		eq := client.Entry.Query().Order(ent.Desc(entry.FieldCreatedAt)).Limit(10)
		entries, err := eq.All(context.Background())
		if err != nil {
			log.Println("Error fetching entries:", err)
			return c.String(http.StatusInternalServerError, "Failed to load entries")
		}
		// Render the index.html template with the entries
		return c.Render(http.StatusOK, "index.html", map[string]interface{}{
			"entries": entries,
		})
	})

	e.POST("/add", func(c echo.Context) error {
		e := client.Entry.Create()
		e.SetContent(c.FormValue("content"))
		if _, err := e.Save(context.Background()); err != nil {
			log.Println(err.Error())
			return c.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
		return c.Redirect(http.StatusFound, "/")
	})

	e.Logger.Fatal(e.Start(":8989"))
}

```

:::note info
#### エラー「failed creating schema resources: querying server version pq: SSL is not enabled on the server」が出た場合の対処方法

このエラーは、PostgreSQLサーバーがSSL接続を要求しているにもかかわらず、entライブラリでSSLが無効の設定で接続しようとしているため発生しています。
エラーメッセージによれば、「SSLが有効でないサーバーに接続しようとしている」とあります。

#### 解決方法
SSLを無効にする: PostgreSQLサーバーがSSLを要求していない場合でも、接続文字列の中にSSLを無効にする設定を追加することで解決できます。

SSLを有効にする: PostgreSQLサーバーがSSLを要求している場合、entライブラリで接続時にSSLを有効にする必要があります。

#### 1. SSL無効にする場合
接続文字列に`sslmode=disable`を追加して、SSLを無効にすることができます。以下のように修正します。
```main.go
client, err := ent.Open("postgres", "host=<ホスト名> port=5432 user=<ユーザ名> dbname=<データベース名> password=<パスワード> sslmode=disable")

```

#### 2. SSL有効にする場合
PostgreSQLサーバーがSSL接続を要求している場合、接続文字列に適切なSSLオプションを追加する必要があります。例えば、`sslmode=require`を指定します。
```maio.go
client, err := ent.Open("postgres", "host=<ホスト名> port=5432 user=<ユーザ名> dbname=<データベース名> password=<パスワード> sslmode=require")
```

:::

:::note info
#### テンプレートエンジンhtmlを使用する場合
GoのEchoフレームワークでHTMLテンプレートをレンダリングするためには、echo.Rendererインターフェースを実装する必要があります。

以下の手順で修正できます：

テンプレートレンダラーの作成: Echoフレームワークでは、HTMLテンプレートをレンダリングするために、`echo.Renderer`インターフェースを実装したカスタムレンダラーを作成する必要があります。

テンプレートファイルのレンダリング: Echoフレームワークは`html/template`を使ってテンプレートをレンダリングできます。t.Executeの代わりに、`c.Render`メソッドを使ってレンダリングします。

使用する関数としては`Render(w io.Writer, name string, data interface{}, c echo.Context)`を使用してレンダリングを行います。

`c.Render`で渡すテンプレート名は、ファイル名（拡張子なし）で指定します。templates/index.htmlを指定している部分を、ファイル名`index.html`を渡すようにします。
:::

#### ■Echoプロジェクトを起動する。

```
go run main.go
```

そうすると、PostgreSQLのテーブルにスキーマが反映されます。

# スタイリングシートの読み込み
CSSやBootstrap,Javascriptなどのファイルを読み込んでみましょう。

```main.go
//スタイリングシートCSS読み込み設定
e.Static("/css", "src/css")

//Bootstrap読み込み設定
e.Static("/bootstrap/css", "src/lib/bootstrap/css")
```

第一引数に省略した形のパスを、第二引数に相対パスを記載します。

つぎにHTMLファイルでは下記のように記載します。
`href`の部分は、前述の関数(`e.Static("第一引数","第二引数")`)の第一引数と読み込むファイル名を記載します。
```templates/index.html
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
<link rel="stylesheet" href="css/index.css"/>
```


全体のコードはこちら↓
```templates/index.html
<!DOCTYPE html>
<html>
    <head>
        <title>タイトル</title>
        <!--共通-->
        <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
        <!--個別ファイル-->
        <link rel="stylesheet" href="css/index.css"/>
    </head>
    <body>
        <h1>投稿内容</h1>
        <form method="post" action="/add">
            <label for="exampleInputEmail1" class="form-label">登録するタスク</label>
            <input type="text" name="content" class="form-control border border-primary" required/>
            <input type="submit" value="送信" class="btn btn-primary"/>
        </form>
        <ul>
            {{range .entries}}
                <li>{{.Content}}</li>
            {{else}}
                <li>まだ投稿はありません。</li>
            {{end}}
        </ul>
    </body>
</html>

```

以上です。






# 参考にしたサイト


https://zenn.dev/mattn/articles/c08072b42f7a5cdcd749

https://www.techpit.jp/courses/29/curriculums/30/sections/256/parts/878