Go と prisma と lit-html と ky で作るモダンな TODO アプリ
はじめに
以前から Go の ORM (Object Relational Mapping) 選定の為に、そこそこ時間を使っています。現状は gorp を使っていますが、満足している訳ではありません。
そんな中で見つけた prisma を試すべく、バックエンドに Go と prisma を使った TODO アプリを作ってみる事にしました。
prisma とは
prisma (Next-generation ORM for Node.js and TypeScript)は簡単に言うと
- 自動生成された型付きのクライアントが付いている
- マイグレーションが出来る
- モデル定義から CRUD やインデックスを自動生成できる
- PostgreSQL, MySQL, SQLite3 等をサポート
- Prisma Studio という GUI が付いている
というモダンな ORM です。Nuxt と TypeScript 方面の産物ですが、クライアントの自動生成機能をそれぞれのプログラミング言語で実行させる事で、各々のクライアントを生成できます。Go に至っては prisma-client-go というオフィシャルのクライアントが使用できます。
構想
フロントまわりは詳しくないので、重い腰を上げたタイミングでなるべく少ない時間を使い、最新のプロダクトをキャッチアップしたいと思ったのでフロントエンドは lit-html、HTTP クライアントは ky という割と新しめの物を選ぶ事にしました。
といっても TODO アプリの部分は @ryohei さんが作られた lit-html-todo に含まれているコードをほぼほぼ使わせて頂き TypeScript から JavaScript に移植しています。TypeScript を使ってもいいのですが、モノリポになるのも微妙だし、これくらいの規模なら JavaScript のままの方が見通しが良いなと思った為です。
まずはスキーマの生成
$ npx prisma init
これを実行すると prisma に必要なファイルが生成されます。prisma/schema.prisma
をテキストエディタで開き以下の様に修正しました。
修正前
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
修正後
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "go run github.com/prisma/prisma-client-go"
}
model Task {
id Int @id @default(autoincrement())
text String?
completed Boolean? @default(false)
}
扱うデータベースを .env
ファイルに記述します。環境変数 DATABASE_URL
を設定するか .env
ファイルを編集します。prisma init
を実行された場合は既に生成されているので修正になります。
DATABASE_URL="postgresql://user:password@localhost/prisma-example"
DB にスキーマを反映する為に以下を実行します。
$ npx prisma db push --preview-feature
Go のクライアント生成
オフィシャルのドキュメントには prisma
コマンドではなく go run github.com/prisma/prisma-client-go
を使えと書いてあるのですが、モノリポにしたくないので
$ npx prisma generate
コマンドを使います。上記の通り generator client の provider に指定されているコマンドが実行されるので結局は正しく動く事になります。
REST サーバを書く
prisma は GraphQL サーバのバックエンドとして使われる事も多い様ですが、今回は REST で書く事にしました。何分、弊社はまだ GraphQL の本番投入実績が無いのでできればこの検証もあり得る形の物を作ろうと思いました。
prisma-client-go の使用感ですが、一言で言えば Facebook 社が開発している ent とほぼ一緒です。(というかそのままでは?)
e.GET("/tasks", func(c echo.Context) error {
tasks, err := client.Task.FindMany().OrderBy(
db.Task.ID.Order(db.ASC),
).Exec(context.Background())
if err != nil {
return c.String(http.StatusBadRequest, err.Error())
}
return c.JSON(http.StatusOK, tasks)
})
ent もまぁ好きなのですが1点気に入らない点があり、TaskModel から client.Task.CreateOne
を簡単に呼び出せない所があります。例えばタスクの更新では、テキストの更新もあれば実施済みフラグの更新もある訳です。1つずつの更新もあれば両方の更新もあり得ます。そういった場合 schema.prisma
の型には ?
を付与して無視可能にするのですが、そうすると prisma-client-go
は値と ok を返す task.Text()
や text.Complete()
といった関数経由でしか値を取れなくなります。つまり以下の様に1フィールドずつリクエストに値が含まれているかのチェックが必要になる訳です。
e.POST("/tasks", func(c echo.Context) error {
var task db.TaskModel
if err := c.Bind(&task); err != nil {
c.Logger().Error("Bind: ", err)
return c.String(http.StatusBadRequest, "Bind: "+err.Error())
}
var text *string
if newText, ok := task.Text(); ok {
text = &newText
}
var completed *bool
if newCompleted, ok := task.Completed(); ok {
completed = &newCompleted
}
newTask, err := client.Task.CreateOne(
db.Task.Text.SetIfPresent(text),
db.Task.Completed.SetIfPresent(completed),
).Exec(context.Background())
if err != nil {
return c.String(http.StatusBadRequest, err.Error())
}
return c.JSON(http.StatusOK, newTask)
})
現状これらをうまく扱える方法を模索している所です。
フロントエンドのコード
前述の様に @ryohei さんの lit-html-todo
のコードを使っています。ほぼほぼ同じですが JavaScript Module になっている点と done フラグが complete という名前になっているだけです。また lit-html-todo
はフロントのコードしか含まれていない為、バックエンドに繋げる為に ky
という HTTP クライアントを使いました。
(async () => {
const state = store();
const task = await ky.post('/tasks', {
json: { text: state.inputText }
}).json();
store({
tasks: [...state.tasks, task],
inputText: ""
})
})();
axios は使った事がありましたが今回は ky を選びました。
実行方法
go build
すれば実行モジュールが生成され、実行すると http://localhost:8989
で TODO アプリが使えます。
フロントエンド側のエラー処理がほぼないので「ちゃんと書いてくれ」という方、ぜひとも pull-req をお願いいたします。
おわりに
prisma
、lit-html
、ky
といった割とモダンな組み合わせで Go のアプリを書いてみました。ORM としては好き嫌いがある為、実際に業務で使えるかどうかは皆さんの目で確認してみて下さい。以下のリポジトリにソースコードを置いています。
良かったら遊んでみて下さい。
Discussion