💬

sqlcを使って簡単なウェブアプリケーションを作る

2022/05/15に公開

mattnさんの書かれている記事のentの部分をsqlcに置き換えてみました。
https://zenn.dev/mattn/articles/c08072b42f7a5cdcd749
ソースコードはgithubに公開しています。
https://github.com/nnabeyang/sqlc-example
sqlc以外の部分は、ほぼmattnさんのコードの流用になります。

sqlcはSQLファイルを読み取って、goコードを生成するツールです。sqlc.devに説明がありますが、entのようにsqlc自身でマイグレーションをしてくれるわけではないようです。サポートしているツールはいくつかあるようですが、dbmateを使いました。

前準備

次のコマンドでCLIツールをインストールします。

$ go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
$ go install github.com/amacneil/dbmate@latest

プロジェクトを作ります。

$ mkdir path/to/sqlc-example
$ cd path/to/sqlc-example
$ go mod init github.com/nnabeyang/sqlc-example

データベースのテーブルを作成

データベースはsample_dbという名前でmysql上に作っているとします。dbmateでこれにentriesテーブルを追加しますが、接続情報を.envからDATABASE_URLで参照するようなので、まず次のように.envを作成します。

$ echo 'DATABASE_URL="mysql://user:password@127.0.0.1:3306/sample_db"' > .env

マイグレーションファイルを作成します。

$ dbmate new create_entries_table
Creating migration: db/migrations/20220515125749_create_entries_table.sql

ファイルが作成されたので、それを編集します。

db/migrations/20220515125749_create_entries_table.sql
-- migrate:up
CREATE TABLE `entries` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `content` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
);
-- migrate:down
DROP TABLE `entries`;

マイグレーションを実行します。

$ dbmate up

テーブルは作成され、db/schema.sqlに現在のデータベースのスキーマが書き出されます。

DBスキーマに対応するgoコードを生成

sqlc initでsqlc.yamlを生成して、編集します。

sqlc.yaml
version: "1"
project:
    id: ""
packages:
  - path: "database"
    name: "database"
    engine: "mysql"
    schema: "db/schema.sql"
    queries: "db/query.sql"

まだquery.sqlがないので、ファイルを作成します。このquery.sqlを元にGoコードを生成します。

db/query.sql
-- name: GetEntries :many
SELECT *
FROM entries
ORDER BY created_at DESC
LIMIT ?;
-- name: CreateEntry :execresult
INSERT INTO entries (content)
VALUES (?);

このようにコメントに関数名や、返り値の型などを書きます。では生成しましょう。

$ sqlc generate

databaseディレクトリが作成されます。ここまでで、省略した部分もありますが、以下のような感じになります。databasedb/schemaはツールが生成したものです。

.
├── LICENSE
├── README.md
├── database
│   ├── db.go
│   ├── models.go
│   └── query.sql.go
├── db
│   ├── migrations
│   │   └── 20220515125749_create_entries_table.sql
│   ├── query.sql
│   └── schema.sql
├── go.mod
├── go.sum
├── main.go
├── .env
├── sqlc.yaml
└── template
    └── index.slim

アプリケーションを実装

mattnさんのコードをコピペした後、entのコードをsqlcに置き換えました。

main.go
package main

import (
	"context"
	"database/sql"
	"log"
	"net/http"
	"os"

	_ "github.com/go-sql-driver/mysql"
	"github.com/labstack/echo/v4"
	"github.com/mattn/go-slim"
	"github.com/nnabeyang/sqlc-example/database"
)

func main() {
	db, err := sql.Open("mysql", os.Getenv("DSN")+"?tls=false&parseTime=true")
	if err != nil {
		log.Fatalf("failed opening connection to sqlite: %v", err)
	}

	t, err := slim.ParseFile("template/index.slim")
	if err != nil {
		log.Fatal(err)
	}
	client := database.New(db)
	e := echo.New()

	e.GET("/", func(c echo.Context) error {
		entries, err := client.GetEntries(context.Background(), 10)
		if err != nil {
			return err
		}
		c.Request().Header.Set("content-type", "text/html")
		return t.Execute(c.Response(), map[string]interface{}{
			"entries": entries,
		})
	})

	e.POST("/add", func(c echo.Context) error {
		if _, err := client.CreateEntry(context.Background(), c.FormValue("content")); 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"))
}

おわりに

いかがだったでしょうか。sqlcはentのように細かくgoコードでSQLを組み立てたりすることはできませんが、スキーマ変えたり、追加するたびに決まりきって書くようなコードの自動生成は十分にやってくれるようです。

Discussion