🚀

React + Go + GraphQL + urqlを最小構成で試す

2022/08/09に公開

概要

手元にGraphQLが動かせる環境が欲しかったので最小構成で作ってみました。
せっかくだからとApollo Clientではなくurqlを使ってみようとしたところgraphql-codegen周りでちょっと手が止まったのでメモがてら記事にします。
完成版はこちら

環境

  • Node.js 16.16.0
  • yarn 1.22.10
  • Go 1.17

server

基本的にgqlgenの公式ドキュメント通りに実行していきます。

go mod init

go mod init github.com/${myname}/xxx-app

tools.goを用意

gqlgenはcode generateの時だけに使用するのでこうしないとgo mod tidyで消されてしまいます。

tools.go
//go:build tools
// +build tools

package tools

import (
	_ "github.com/99designs/gqlgen"
)

雛形を生成

gqlgenのinitをします。これにより雛形が自動生成されます。

go run github.com/99designs/gqlgen init

繋ぎこみの準備

フロントと繋ぎ込みが行えるように生成されたschema.resolvers.goのpanic部分を適当に変更します。
必要に応じて、ここにDBからの取得処理などを書いていきますが、最小構成としたいのでここではstructをハードコーディングしています。

schema.resolvers.go
// Todos is the resolver for the todos field.
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
	return []*model.Todo{
		{ID: "1", Text: "first todo", Done: false},
		{ID: "2", Text: "second todo", Done: true},
	}, nil
}

さらにこのままだとフロント(localhost)からリクエストを送るとCORSエラーになるのでserver.goに追加でcors対応を実装します。ここでは細かい設定は省略します。

server.go
package main

import (
	"log"
	"net/http"
	"os"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"github.com/purp1eeeee/graphql-app/graph"
	"github.com/purp1eeeee/graphql-app/graph/generated"
	"github.com/rs/cors"
)

const defaultPort = "8080"

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = defaultPort
	}
	mux := http.NewServeMux()
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
	handler := cors.Default().Handler(mux)
	mux.Handle("/", playground.Handler("GraphQL playground", "/query"))
	mux.Handle("/query", srv)

	log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
	log.Fatal(http.ListenAndServe(":"+port, handler))
}

go run server.go でサーバーを立ち上げてgraphiQLからtodos queryが動くことが確認できると思います。
サーバーはここまでで準備完了です。

front

雛形を作成

今回はviteのテンプレートを使用しますが、Next.jsでもcreate-react-appでもなんでも大丈夫です。
あとyarnじゃなくても大丈夫です。

yarn create vite client --template react-ts

graphQLのスキーマからTypeScriptの型情報を生成

ここではgraphql-code-generatorを使用します。

yarn add graphql
yarn add -D @graphql-codegen/cli

graphql-codegen initを実行し、configのymlを生成

gqlgenで生成したschemaのパスを生成先として答えていますが、必要に応じてこのschemaを配置する場所を変更してください。今回はdefaultのままとします。
コマンド実行後にpacage.jsonに使用するpluginが追加されるので再度yarnします。

yarn graphql-codegen init
...
? What type of application are you building? Application built with React
? Where is your schema?: (path or url) ../server/graph/schema.graphqls
? Where are your operations and fragments?: src/**/*.graphql
? Pick plugins: TypeScript (required by other typescript plugins), TypeScript Operations (operations and fragments), Urql 
Introspection (for Urql Client)
? Where to write the output: src/generated/graphql.ts
? Do you want to generate an introspection file? Yes
? How to name the config file? codegen.yml
? What script in package.json should run the codegen? codegen
Fetching latest versions of selected plugins...

    Config file generated at codegen.yml
    
      $ npm install

    To install the plugins.

      $ npm run codegen

    To run GraphQL Code Generator.
  
✨  Done in 137.94s.

yarn

CLIで選択肢がなかったため手動でurql用のpluginを用意

yarn add -D @graphql-codegen/typescript-urql

pluginを追加した最終的なymlはこちら

codegen.yml
overwrite: true
schema: "../server/graph/schema.graphqls"
documents: "src/**/*.graphql"
generates:
  src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-urql"
  ./graphql.schema.json:
    plugins:
      - "introspection"

適当な.graphqlを用意してgenerateしてみる

findTodos.graphql
query findTodos {
  todos {
    text
    done
  }
}
yarn codegen

src/generated/graphql.tsが生成されていたら準備は完了です。
ここまできたらようやくgraphQL clientの準備をします。

urqlのセットアップ

インストール

yarn add urql

Providerを用意

main.tsx
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import "./index.css"
import * as Urql from "urql"

const client = Urql.createClient({
  url: "http://localhost:8080/query",
})

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <Urql.Provider value={client}>
      <App />
    </Urql.Provider>
  </React.StrictMode>
)

繋ぎこみ

App.tsxで生成されたhookを使ってfetchできることを確認できたら完成です。

App.tsx
import * as React from "react"
import "./App.css"
import { useFindTodosQuery } from "./generated/graphql"

function App() {
  const [result, reexecuteQuery] = useFindTodosQuery()
  const { data, fetching, error } = result
  if (fetching) {
    return <p>loading...</p>
  }

  if (error) {
    return <p>error</p>
  }

  return (
    <div>
      {data?.todos.map((v, i) => (
        <p key={i}>{v.text}</p>
      ))}
    </div>
  )
}

export default App

おわり

queryだけですがReact + Go + GraphQL + urqlの最小構成を作ることができました。

Discussion