Goを使ったWebアプリケーション開発


## この記事の概要

:::note info
この記事の概要
- Goプロジェクトの作成
- `net/http`を使ってlocalhostに画面をHelloWorldを出力する
- Home画面とAbout画面の２画面を作成する
- Go標準パッケージの`html/template`を使って画面描画する
- `main.go`の関数を別ファイルに移動して呼び出す
- 切り出したファイルをフォルダ格納してmain.goを呼び出す
- さいごに
:::


 

## Goのプロジェクトを作成する。

```bash
$ mkdir sample_go
$ cd sample_go
$ go mod init sample_go
```

modファイルが作成される。

```go.mod
module sample_go

go 1.21.0

```

### Hello World を出力する

```go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		_, err := fmt.Fprintf(w, "Hello, world!")
		if err != nil {
			fmt.Println(err)

		}
	})

	_ = http.ListenAndServe(":8080", nil)
}
```

```bash
 $ go run main.go
```

ブラウザで次のURLを入力する

```
http://localhost:8080/
```

![スクリーンショット 2023-08-30 181544.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518204/58b0c2d8-a019-04cb-f514-44edd46dd4ee.png)

Hello, world!がブラウザに出力されました。

:::note info
ポイント
- "net/http"の標準ライブラリをインポートしている
- http.HandleFunc関数を使って、Webブラウザに描画している
:::


**HandleFuncの解説はこちらを読むと理解が深まります。**
[【Go】net/httpパッケージを読んでhttp.HandleFuncが実行される仕組み](https://qiita.com/immrshc/items/1d1c64d05f7e72e31a98)


## HomeページとAboutページを追加する

次に、HomeページとAboutページを追加していきます。

```main.go
package main

import (
	"fmt"
	"net/http"
)

const portNumber = ":8080"

// HomeページをHandlFunc関数の第二引数で呼出
func Home(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "ホームページです。")
}

// AboutページをHandlFunc関数の第二引数で呼出
func About(w http.ResponseWriter, r *http.Request) {
	sum := addValues(2, 2)
	_, _ = fmt.Fprintf(w, fmt.Sprintf("Aboutページで関数実行 2 + 2 は %d\n", sum))
	fmt.Fprintf(w, "アバウトページです。")
}

// x + y の結果を返却する関数
func addValues(x, y int) int {
	return x + y
}

func main() {
	http.HandleFunc("/", Home)
	http.HandleFunc("/about", About)

	fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
	_ = http.ListenAndServe(portNumber, nil)
}

```

パスごとに表示内容を変更出来ています。
ブラウザで次のURLを入力する

```
http://localhost:8080/
```
![スクリーンショット 2023-08-30 182852.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518204/3841e6d5-4003-27bf-1078-782e5d78b587.png)


```
http://localhost:8080/about
```
![スクリーンショット 2023-08-30 182957.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/518204/b496a3fb-f50f-319e-e82b-287372d3eab9.png)

:::note info
ポイント
- Home関数とAbout関数を記述した。
- HandlFunc関数の引数にHome関数とAbout関数を渡した
:::


## templateを作成して呼び出す

Goの標準パッケージの`"html/template"`パッケージをを使用してtemplateを使いながら画面描画をします。

`templates`フォルダを作成して、2つファイルを用意します。

```bash
$ mkdir templates
$ touch templates/home.page.tmpl
$ touch templates/about.page.tmpl
```
このようなディレクトリ構成になります。

```bash 
.
├── go.mod
├── main.go
├── readme.md
└── templates
    ├── about.page.tmpl
    └── home.page.tmpl
```

テンプレートは次のように記述していきます。

```home.page.tmpl
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<h1>ホームページです。</h1>
<p>テキストです。</p>
</body>
</html>
```

```about.page.tmpl
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<h1>アバウトページです。</h1>
</body>
</html>

```
main.goを編集していきます。

```main.go
package main

import (
	"fmt"
	"html/template"
	"net/http"
)

const portNumber = ":8080"

// HomeページをHandlFunc関数の第二引数で呼出
func Home(w http.ResponseWriter, r *http.Request) {
	renderTemplate(w, "home.page.tmpl")
}

// AboutページをHandlFunc関数の第二引数で呼出
func About(w http.ResponseWriter, r *http.Request) {
	renderTemplate(w, "about.page.tmpl")
}

func renderTemplate(w http.ResponseWriter, tmpl string) {
	parsedTemplate, _ := template.ParseFiles("./templates/" + tmpl)
	err := parsedTemplate.Execute(w, nil)
	if err != nil {
		fmt.Println("error parsing template:", err)
	}
}

// main is the main function
func main() {
	http.HandleFunc("/", Home)
	http.HandleFunc("/about", About)

	fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
	_ = http.ListenAndServe(portNumber, nil)
}

```

:::note info
ポイント
- "html/template"をGoの標準からインポートして使っている。
- template.ParseFilesでパスを解析している
- template.Executeで解析したテンプレートを実行している。
:::

## handler.go,render.goを作成して関数を切り出す

Home関数、About関数、renderTemplate関数をmain.goから別ファイルに書き出していきます。

```main.go
package main

import (
	"fmt"
	"net/http"
)

const portNumber = ":8080"

func main() {
	http.HandleFunc("/", Home)
	http.HandleFunc("/about", About)

	fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
	_ = http.ListenAndServe(portNumber, nil)
}

```

handlers.goを作成して、main.goからの記述をペーストしていきます。

```handlers.go
package main

import (
	"net/http"
)

// Home is the handler for the home page
func Home(w http.ResponseWriter, r *http.Request) {
	renderTemplate(w, "home.page.tmpl")
}

// About is the handler for the about page
func About(w http.ResponseWriter, r *http.Request) {
	renderTemplate(w, "about.page.tmpl")
}

```
render.goを作成して、main.goから記述をペーストしていきます。

```render.go
package main

import (
	"fmt"
	"html/template"
	"net/http"
)

func renderTemplate(w http.ResponseWriter, tmpl string) {
	parsedTemplate, _ := template.ParseFiles("./templates/" + tmpl)
	err := parsedTemplate.Execute(w, nil)
	if err != nil {
		fmt.Println("error parsing template:", err)
	}
}

```
この状態でmain.goを実行すると下記のエラーが発生します。

```bash
$ go run main.go
# command-line-arguments
./main.go:12:23: undefined: Home
./main.go:13:28: undefined: About
```

ファイルを切り出したことによって、Home関数とAbout関数が定義されていないという扱いになってしまいました。
これを回避するために、handlers.goとrender.goも同時に実行しなくてはいけません。

同時に実行するために、全てのファイル名を記述する方法もあります。

```bash
$ go run main.go handlers.go render.go 
```

しかし、それでは面倒臭いので、次のような呼び出し方もできます。

```bash
$ go run *.go
```

このようにファイルを切り出して実行してローカルホストにアクセスしてみても、問題なく画面を描画できていることがわかります。

```bash
# 下記にアクセスする
http://localhost:8080/
```

## フォルダを作成して、切り出したファイルを格納していく

先ほどの切り出したファイルをフォルダに移動していきます。
元々のファイル構成がこちらです。

```bash 
.
├── go.mod
├── handlers.go
├── main.go
├── readme.md
├── render.go
└── templates
    ├── about.page.tmpl
    └── home.page.tmpl

```

下のような構成に変更します。

```bash
.
├── cmd
│   └── web
│       └── main.go
├── go.mod
├── pkg
│   ├── handlers
│   │   └── handlers.go
│   └── render
│       └── render.go
├── readme.md
└── templates
    ├── about.page.tmpl
    └── home.page.tmpl

```

### cmd/web/main.goの編集

変わった場所は、**HandleFuncの第二引数**が変わっています。
元々Homeという関数を渡していましたが、handlers.Homeという渡し方に変わっています。
前回は同じ階層にmainパッケージを定義してHome関数を使っていましたが、ディレクトリ構成を変えたことで、importしてから使うように変更しています。

```main.go
package main

import (
	"fmt"
	"net/http"
	"sample_go/pkg/handlers"
)

const portNumber = ":8080"

// main is the main function
func main() {
	http.HandleFunc("/", handlers.Home)
	http.HandleFunc("/about", handlers.About)

	fmt.Println(fmt.Sprintf("Staring application on port %s", portNumber))
	_ = http.ListenAndServe(portNumber, nil)
}

```

## pkg/handlers/handlers.goの編集

ディレクトリ構成を変えて、pkg/handlersという箇所にhandlers.goを移動しました。
handlers.goの中身で変わっているのは、package名を`package main`から`package handlers`に変更しているところです。
さらに、処理の中で`RenderTemplate`関数の呼び出し方が変わっています。
元々はmainパッケージの中に定義されてた関数を呼び出していましたが、`pkg/render`に移動したので、インポートしてから使っています。


```handlers.go
package handlers

import (
	"net/http"
	"sample_go/pkg/render"
)

// Homeページ
func Home(w http.ResponseWriter, r *http.Request) {
	render.RenderTemplate(w, "home.page.tmpl")
}

// Aboutページ
func About(w http.ResponseWriter, r *http.Request) {
	render.RenderTemplate(w, "about.page.tmpl")
}
```
### pkg/render/render.goの編集

こちらのrender.goで変わった箇所は、2箇所あります。
1. package名が`main`から`render`に変わっている点。
2. `renderTemplate`の関数名が`RenderTemplate`に変わっている点です。

関数名は、頭文字が小文字の場合はパッケージ内でしか使えない関数になります。
頭文字が大文字の場合は、別のパッケージからも呼び出せる関数になります。
今回の`RenderTemplate`関数は、`handlers.go`から呼び出されています。

```render.go
package render

import (
	"fmt"
	"html/template"
	"net/http"
)

// RenderTemplate renders a template
func RenderTemplate(w http.ResponseWriter, tmpl string) {
	parsedTemplate, _ := template.ParseFiles("./templates/" + tmpl)
	err := parsedTemplate.Execute(w, nil)
	if err != nil {
		fmt.Println("error parsing template:", err)
	}
}

```

## プログラムを実行してみる

main.goを実行してみます。
ディレクトリ構成が変わったとき、どのような呼び出し方をすれば良いでしょうか。
試しに、`main.go`の場所まで行ってからプログラムを実行してみます。

```
$ cd cmd/web    
$ go run main.go
```

このプログラムは失敗します。実行するカレントディレクトリの場所にgo.modがないとプログラムはうまく動きません。
では、go.modの場所まで戻って、`main.go`を実行してみます。

```bash 
$ cd ../../
$ go run cmd/web/main.go
```

これで描画が出来ています。

```
# 下記にアクセスする
http://localhost:8080/
```

## templateの共通化をする

`home.page.tmpl`と`about.page.tmpl`には共通で使われている記述があります。
そのため、その共通部分を`base.layout.tmpl`として切り出し、共通化する処理をしていきます。
まずは、base.layout.tmplを作成します。

```base.layout.tmpl
{{define "base"}}
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
            content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
            integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">

        {{block "css" .}}

        {{end}}

    </head>
    <body>

    {{block "content" .}}

    {{end}}

    {{block "js" .}}

    {{end}}

    </body>
    </html>
{{end}}
```

`{{define "base"}}`から`{{end}}`までの部分で共通化したtemplateになります。
そして、中身を見ると、`{{block "css" .}}{{end}}`や`{{block "content" .}}{{end}}`のように記述して、共通部分を切り出して実装できようにしています。
今回は、`{{block "content" .}}{{end}}`の部分に`home.page.tmpl`と`about.page.tmpl`を埋め込んでみます。

```home.page.tmpl
{{template "base" .}}

{{define "content"}}
    <div class="container">
        <div class="row">
            <div class="col">
                <h1>This is the home page</h1>
                <p>This is some text</p>
            </div>
        </div>
    </div>
{{end}}
```
```about.page.tmpl
{{template "base" .}}

{{define "content"}}
    <div class="container">
        <div class="row">
            <div class="col">
                <h1>This is the about page</h1>
            </div>
        </div>
    </div>
{{end}}
```
先ほど`base.layout.tmpl`で定義していた{{define "base"}}を`home.page.tmpl`では、
`{{template "base" .}}`として呼び出して使っています。
その中身で`{{define "content"}}`から`{{end}}`までをcontent部分に埋め込んでいます。

このように記述することで、共通部分を切り出して使うことが出来ます。

## さいごに

今回の記事はここまでです。
最後まで読んでいただき、ありがとうございます。