Go(Gin) + Prismaで作るGraphQLサーバー

最終更新日: 2024年7月5日

Go
GraphQL
Prisma

長いの見たくない人

https://github.com/chaso-pa/graphql-server-template

環境変数は2つを指定しています。

  • PRISMA_DATABASE_URL → prismaマイグレーション用URL
  • DATABASE_URL → gorm接続用URL
.env
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による実行を前提としています)

Zsh
$ yarn
$ yarn add -D prisma
$ yarn run prisma init
schema.prisma
// 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を作る。

middleware/connect_db.go
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を受け取れるよう、構造体に追加する。

graph/resolver.go
import (
  "gorm.io/gorm"
)
// -------------------
type Resolver struct {
	DB    *gorm.DB // ← gorm.DBを追加する
main.go
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ライフを!!!