LoginSignup
0
0

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

Posted at

この記事の概要

この記事の概要

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

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

$ mkdir sample_go
$ cd sample_go
$ go mod init sample_go

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

go.mod
module sample_go

go 1.21.0

Hello World を出力する

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)
}
 $ go run main.go

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

http://localhost:8080/

スクリーンショット 2023-08-30 181544.png

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

ポイント

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

HandleFuncの解説はこちらを読むと理解が深まります。
【Go】net/httpパッケージを読んでhttp.HandleFuncが実行される仕組み

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

http://localhost:8080/about

スクリーンショット 2023-08-30 182957.png

ポイント

  • Home関数とAbout関数を記述した。
  • HandlFunc関数の引数にHome関数とAbout関数を渡した

templateを作成して呼び出す

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

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

$ mkdir templates
$ touch templates/home.page.tmpl
$ touch templates/about.page.tmpl

このようなディレクトリ構成になります。

.
├── 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)
}

ポイント

  • "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を実行すると下記のエラーが発生します。

$ 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も同時に実行しなくてはいけません。

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

$ go run main.go handlers.go render.go 

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

$ go run *.go

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

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

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

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

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

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

.
├── 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を実行してみます。

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

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

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

templateの共通化をする

home.page.tmplabout.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.tmplabout.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部分に埋め込んでいます。

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

さいごに

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

0
0
0

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
0
0