Go(Gin) + Prismaで作るGraphQLサーバー
最終更新日: 2024年7月5日
長いの見たくない人
https://github.com/chaso-pa/graphql-server-template
環境変数は2つを指定しています。
- PRISMA_DATABASE_URL → prismaマイグレーション用URL
- DATABASE_URL → gorm接続用URL
PRISMA_DATABASE_URL="mysql://user:pass@address/database"
DATABASE_URL="user:pass@tcp(address)/database?parseTime=true"
GraphQLを使って感動した皆さん(私含む)がいざGraphQLサーバーを作る際の一助となれば幸いです。
仕様
- UserとTodoがある
- TodoはUserIdを持つ(Todo belongsTo User)
- User,Todoを取得する(Query)
- User,Todoを作成する(Mutation)
チュートリアル通りに作る
↓を参考に作る(英語)
https://gqlgen.com/getting-started
自動コードでガシガシ書いてくれるの助かるわぁ
この記事の通りに作ったときに、tools.go
でGo製のツールを管理することを知った。あまりにも短いコードなのでgqlgenで自動生成した後、ファイルごと消してしまっていたが、スキーマなど変更を行った際に再度gqlgenを実行したら派手に転けていたので注意。go mod tidy
を使えば消えるので当たり前ではあるが。。。
チュートリアルに追加でUser用のqueryとmutationを追加している
ちなみにサーバー立ち上げ後のサンドボックスは`localhost:8080`にアクセスすればPlaygroundが開いているのでそこで試せる。デフォルトのルーティングは↓
- GET / → Playground
- POST /query → GraphQLエンドポイント
チュートリアル(+α)でできる成果物の状態は↓の感じ。
- User,Todoを取得する(Query)
- User,Todoを作成する(Mutation)
- オンメモリで記録
- サーバーが素のhttp.ListenAndServeで動いている
- 実行ファイル名が
server.
go(別にこのままでも良いが後でmain.goに変更した)
今は保存がオンメモリで行うようになっており、サーバーを閉じると保存したデータは消えてしまう。
Ginサーバーで動くように変更
DBの作成前に「サーバーが素のhttp.ListenAndServeで動いている」を解決する。今回はGoのWebフレームワーク「Gin」を使った。速いとのこと。
Ginを使うことのチュートリアルであるので参考にして修正する。
https://gqlgen.com/recipes/gin
API的にフロントエンドのサーバーから呼び出す場合にはCORSの設定をするといいと思うの。
DBで動くように
まずはDBにテーブルを作る。Prismaはスキーマ駆動で動くNode製のOSS マイグレーションツールでDB周りの設計をこれ一つで行ってくれる優れもの。
Mapを使っているのはPrismaの命名規則をテーブル名は複数形に、カラム名はスネークケースにしたいためです
マイグレーションツールで使ったことあるのはActiveRecord(Rails)、Goose(Golang)だったり使っていたがどれも環境依存が多いことや単純なSQLを書かされるので単体で使うにはちょっと微妙だった。
一方でPrismaはNode環境がどのプロジェクトでも比較的導入しやすい点、スキーマの記法が好みな点がグッドポイント。
スキーマを編集してその差分をマイグレーションしていくスタイルなのでschema.prisma
が肥大化すると行数馬鹿でかくなって厄介だが、マイクロサービスにとどめておけばその辺も問題なし。
(yarnによる実行を前提としています)
$ yarn
$ yarn add -D prisma
$ yarn run prisma init
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("PRISMA_DATABASE_URL")
}
model User {
id String @id @default(cuid())
name String?
todos Todo[]
@@map("users")
}
model Todo {
id String @id @default(cuid())
text String
done Boolean
userId String @map("user_id")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("todos")
}
スキーマ定義できたらyarn run prisma migrate dev
してマイグレートする。
次に、GraphQLリゾルバがDBを使うために、middlewareを作る。
package middleware
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"os"
)
func ConnectDB() *gorm.DB {
//データベースへの接続
dsn := os.Getenv("DATABASE_URL")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err.Error())
}
return db
}
これをメイン内で呼び出し、Resolverが受け取る。ResolverがDBを受け取れるよう、構造体に追加する。
import (
"gorm.io/gorm"
)
// -------------------
type Resolver struct {
DB *gorm.DB // ← gorm.DBを追加する
package main
import (
"github/chaso-pa/gql-server/graph"
"github/chaso-pa/gql-server/middleware"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
const defaultPort = "8080"
func main() {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
db := middleware.ConnectDB() // dbの定義
router := gin.Default()
router.POST("/graphql", graphqlHandler(db)) // ← db
router.GET("/", playgroundHandler())
// ------------
}
func graphqlHandler(db *gorm.DB) gin.HandlerFunc {
h := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{
DB: db,
}}))
return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}
// -------------------
これで接続できるようになった。あとはオンメモリでCRUDしていたUserとTodoをDBを通じて取り出せるようにする。
schema.prismaにはデフォルトのIDをcuidにしているが、Gormで保存する際にはデフォルト値が入らないので、自分でcuidを作成した。
完成!
改めて完成品のテンプレートプロジェクトです。これはDBに永続的に保存できるようにしただけなのでテーブルの工夫やリゾルバの編集などはご自身でやってみてください!
https://github.com/chaso-pa/graphql-server-template
良いGraphQLライフを!!!