プロジェクトのセットアップ
環境準備
今回の演習は、(第一部)サーバーからデータベースを扱う の状態から開始します。
もしファイルを削除してしまった場合は、以下の手順でセットアップしましょう。
データベースを扱う準備 からプロジェクトをセットアップしましょう。
.env
ファイルを作成し、以下のように編集しましょう。
DB_USERNAME="root"
DB_PASSWORD="password"
DB_HOSTNAME="localhost"
DB_PORT="3306"
DB_DATABASE="world"
go mod tidy
を実行しましょう。
以上でセットアップはできているはずです。
ファイルの分割
このまま演習を始めてしまうとファイルが長くなりすぎてしまうので、ファイルを別のパッケージとして分割します。
TIP
パッケージとは、関連する複数のファイルをまとめる単位のことです。
ディレクトリとパッケージは一対一に対応しています。原則的に、ディレクトリ名とパッケージ名は同じにします。
パッケージによって、機能を分離でき、変数や関数の公開範囲を最低限にできる等沢山の恩恵が得られます。
パッケージの外部に公開する変数や関数などのシンボルは、先頭を大文字にする必要があります。
逆に言えば、先頭が大文字でないシンボルは、パッケージの外部からはアクセスできません。
詳しくは以下を参照してください。 A Tour of Go - Packages
Effective Go - package-names
各エンドポイントでの処理はハンドラーと呼ばれますが、それを handler/handler.go
に移動してみましょう。手順は以下の通りです。
handler.go の作成
handler
というディレクトリを新しく作成し、その中にhandler.go
というファイルを作成する。handler.go
を以下のように記述する。
package handler
import (
"database/sql"
"errors"
"github.com/jmoiron/sqlx"
"github.com/labstack/echo/v4"
"log"
"net/http"
)
type Handler struct {
db *sqlx.DB
}
func NewHandler(db *sqlx.DB) *Handler {
return &Handler{db: db}
}
type City struct {
ID int `json:"id,omitempty" db:"ID"`
Name sql.NullString `json:"name,omitempty" db:"Name"`
CountryCode sql.NullString `json:"countryCode,omitempty" db:"CountryCode"`
District sql.NullString `json:"district,omitempty" db:"District"`
Population sql.NullInt64 `json:"population,omitempty" db:"Population"`
}
func (h *Handler) GetCityInfoHandler(c echo.Context) error {
cityName := c.Param("cityName")
var city City
err := h.db.Get(&city, "SELECT * FROM city WHERE Name=?", cityName)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return c.NoContent(http.StatusNotFound)
}
log.Printf("failed to get city data: %s\n", err)
return c.NoContent(http.StatusInternalServerError)
}
return c.JSON(http.StatusOK, city)
}
func (h *Handler) PostCityHandler(c echo.Context) error {
var city City
err := c.Bind(&city)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "bad request body")
}
result, err := h.db.Exec("INSERT INTO city (Name, CountryCode, District, Population) VALUES (?, ?, ?, ?)", city.Name, city.CountryCode, city.District, city.Population)
if err != nil {
log.Printf("failed to insert city data: %s\n", err)
return c.NoContent(http.StatusInternalServerError)
}
id, err := result.LastInsertId()
if err != nil {
log.Printf("failed to get last insert id: %s\n", err)
return c.NoContent(http.StatusInternalServerError)
}
city.ID = int(id)
return c.JSON(http.StatusCreated, city)
}
ファイルを編集したら、go mod tidy
を実行しましょう。
main.go の編集
main.go
を以下のように編集しましょう。
package main
import (
"github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"github.com/joho/godotenv"
"github.com/labstack/echo/v4"
"github.com/traPtitech/naro-template-backend/handler"
"log"
"os"
"time"
)
func main() {
// .envファイルから環境変数を読み込み
err := godotenv.Load(".env")
if err != nil {
log.Fatal(err)
}
// データーベースの設定
jst, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
log.Fatal(err)
}
conf := mysql.Config{
User: os.Getenv("DB_USERNAME"),
Passwd: os.Getenv("DB_PASSWORD"),
Net: "tcp",
Addr: os.Getenv("DB_HOSTNAME") + ":" + os.Getenv("DB_PORT"),
DBName: os.Getenv("DB_DATABASE"),
ParseTime: true,
Collation: "utf8mb4_unicode_ci",
Loc: jst,
}
// データベースに接続
db, err := sqlx.Open("mysql", conf.FormatDSN())
if err != nil {
log.Fatal(err)
}
h := handler.NewHandler(db)
e := echo.New()
e.GET("/cities/:cityName", h.GetCityInfoHandler)
e.POST("/cities", h.PostCityHandler)
err = e.Start(":8080")
if err != nil {
log.Fatal(err)
}
}
ファイルを編集したら、go mod tidy
を実行しましょう。
ここまで出来たら、画像のようになっているはずです。
準備完了
ファイルの分割で変更したのは、以下の 3 点です。
handler
パッケージを作成し、コードを分割した。handler
というdb
をフィールドに持つ構造体を作成し、その構造体のメソッドとしてGetCityInfoHandler
やPostCityHandler
を定義した。.env
ファイルの環境変数を、プログラムで読むようにした。
それでは、go run main.go
で実行してみましょう。
TIP
main package
を複数ファイルに分割した場合、go run main.go
だとmain.go
のみがビルドされるため、go run .
やgo run main1.go main2.go
のようにして複数ファイルを読み込む必要があります。
詳しくはgo help run
を参照してください。
無事起動が出来たら、ターミナルでtask up
を実行してデーターベースを起動し、localhost:8080/cities/Tokyoにアクセスして実際に動いていることを確認しましょう。
上手く動いていることを確認できたら、 Ctrl+C
で一旦止めましょう。