Open87

graphQLに入門する

tomitomi
  • クエリで参照、ミューテーションで更新
  • 型を定義していく。共通化とか変数の利用とかTSの型でできるような型操作は基本的にできるようだ。

fragmentという再利用可能なパーツに分割できる。

tomitomi

ミューテーションで更新ができる。RESTでいうPATCH的なもの。
ミューテーションのスキーマはユースケース毎になってるといいっぽい。RESTだとリソースごとだったりするところはGraphQLはユースケースって感じ。

RESTでもユースケースはできるけど。

tomitomi

削除もミューテーションでできる。

  • idを指定してリソースの削除するをするなど。
  • QA
    • deleteであることはどこで判断するんだ?実装?
tomitomi

ミューテーションの複数実行

  • クエリは並列に処理されるが、ミューテーションは直列での実行。
  • →トランザクションほど保証されていない。ロールバックされない。
    なんでー?
tomitomi

サブスクリプション

  • これはリアルタイムで更新を取得する方法とのこと。
  • 内部はサーバー側でちゃんと作って上げる必要がある。イベントなのかwebsocketなのかとか。
  • クエリ、ミューテーションよりも実装は複雑になる。
  • 頻繁かつ増分的な更新をクライアントにストリーミングする必要があるデータ取得の場合にサブスクリプションが有効
    • チャットとかはリアルタイムだね。
    • 他は?
    • RESTだと、websocketとかね。
tomitomi

バリデーション

クエリを送信するとスキーマと比較して、ただしいいか?をチェックしてエラーを返してくれる。

tomitomi

実行

  • ルートフィールド、リゾルバ
    • リゾルバ?
      • loadUserbyIdみたいな実際にDBからデータを取得する処理とかのこと。
  • トリビアルリゾルバ
    • ?
  • クエリでフィールドとかに対応したデータ取得のロジック関数を用意する必要がある。
    • RESTの代わりのところだから、実際のデータ取得のロジックは必要よね。
    • どうかいていくのかは気になるけど、
    • 各フィールド事に用意するのだろう多分。
    • 定義されていなければ取得できない。当たり前。
    • RESTだと、必要なフィールドを選ぶのはクエリパラメータで選択したりしてやれそう。この部分の仕組みがすでに用意されているイメージかなぁ。
tomitomi

レスポンス

[Response | GraphQL https://graphql.org/learn/response/]

  • 基本はjsonで返される。
  • またHTTPのステートレスでやるのが基本、サブスクリプションはサーバーイベントやwebsocketなど

レスポンスはdata,errors, extensionsの3つが用意されている。

部分的に成功の場合も、部分的に成功を返して、errorsも返したりする。

tomitomi

エラー

  • リクエストのエラー 4xx的なもの?
tomitomi

ベストプラクティス

[GraphQL Best Practices \| GraphQL https://graphql.org/learn/best-practices/]
  • 認証とかHTTPにおける利用、キャッシュ、スキーマ設計、ページネーションどうする?セキュリティなどのことが書かれている。
tomitomi

HTTP

  • RESTはリソースに基づく、GraphQLはエンティティグラフに基づく。
  • GraphQLはURLによって識別されない。だから、1つのendpointのみをもつ。通常は/graphql

リクエストヘッダーのacceptはapplication/graphql-response+jsonを指定する。

GETの場合
http://myapi/graphql?query={me{name}}こんな感じになる。

GETとPOSTだけつかう?
どう使い分ける?

レスポンス

  • dataに入ってれば、200になる。errorsがあっても200。
  • エラーのみなら400または500系
tomitomi

認証、認可

基本はgraphQLに到達するまえのミドルウェア層でユーザー認証などをしておくとよいが、特定のフィールドごとに異なるとかだとどうする?って話。

下書きは著者のみが閲覧できます など

  • postを投稿した本人だけが更新、削除できるというユースケースの場合。
    • リゾルバで本人か?を確認したくなる。
      • そうすると、この処理をいたるところで書く必要がでてきてしまう?
      • 認証用のリソースをおくといいらしい。
      • ビジネス ロジック レイヤーに委任したほうがいい。
      • リゾルバでもできるけどね。

別の方法で、リゾルバないでディレクティブを利用する方法はこれ

type Post {
  authorId: ID!
  body: String @auth(rule: IS_AUTHOR)
}
tomitomi

スキーマ設計

[Schema Design | GraphQL https://graphql.org/learn/schema-design/]

バージョン管理について

  • バージョン管理をしないようにすることがGraphQLでは大事にされていそう。
  • バージョン管理するってことは、破壊的変更とかに対応するためだったりするが、そもそも互換性をもちつづけて、破壊的変更がないようにすればバージョン管理もいらないよねって考え方だ。

graphQLのnull許容

  • デフォルトはnull許容してる。
  • なんからの障害とかで一部のデータが取得できなくて、nullになるケースがある。だからnull許容して、常にnullの可能性を考慮しながらやるといい?
tomitomi

🚧グローバルオブジェクト識別

[Global Object Identification | GraphQL https://graphql.org/learn/global-object-identification/]

Nodeというインタフェースとnodeというクエリフィールドが予約語として用意されている。
なんか特別な機能をもってるフィールド。

?? いったん’skip。
tomitomi

キャッシング

[Caching | GraphQL https://graphql.org/learn/caching/]

RESTだとURLでリソースを識別できれるから、HTTPのキャッシュができる。
GraphQLはendpointが一つしかないからキャッシュどうする?って問題がでる。

オブジェクトにグローバルに一意なIDを振るといい?

  • レコードのサロゲートキー的なもの?でいいのか?
  • クエリ全体のキャッシュってことじゃないのか。。
tomitomi

パフォーマンス

[Performance | GraphQL https://graphql.org/learn/performance/]

いろんなところでキャッシュ戦略が適応できるらしい。

  • クライアント側でのキャッシュ
  • GETの問い合わせ
    • クエリのハッシュをつかって、キーバリューストアとかで保存?
  • N+1
    • 気にせずリゾルバを作成するとDBとのやりとりは多発。
    • バッチ処理技術で対処できるそう。
      • dataloaderというやつがある
      • 最適化されたクエリに変更する機能があるらしい。
  • 圧縮
    • jsonをgzipしよう
  • モニタリングをしましょう。

memo

  • N+1のところが気になる。
  • ほかはまぁRESTと一緒でできるよって感じ。
  • またCDNのキャッシュとかはクエリをハッシュにすることでできるって感じ。
tomitomi

セキュリティ

[Security | GraphQL https://graphql.org/learn/security/]

TCPレイヤー

  • 一般的な対応をGraphQLでもする
  • HTTPS、タイムアウト、とか

制限

  • IP制限とか
  • クエリのネストの深さの制限をもうけるとか
  • クエリの幅も。
  • レート制限
  • クエリの複雑さのコストで制限する
  • エラーメッセージは本番に出しすぎないとか。
tomitomi

一通り読み終えた。

  • GraphQLのなんとなくの登場人物がわかった。
  • クエリ、サブスクリプション、ミューテーション。
  • スキーマ。型でできること
  • リゾルバの存在

実際に実装してねはまだ無理なので、別のチュートリアルで動きを試す必要がある。

tomitomi
  • graphql
  • express サーバー
  • graphql-http httpでgraphqlを使う。

graphiQLというツール。IDE。

  • ruru でサーバーで使えるようにする。
  • localhost:4000で起動する。
tomitomi

ruru
variableの書き方はこれ。

tomitomi

引数を渡す事ができる。

スキーマでrollDiceというフィールド?で引数がある。

  • numDiceはIntでnot null, numSidesはnull許可のIntで、配列のIntを返す。
  • これに対応するリゾルバをrootとして定義する。
  • 引数は名前付き引数として定義する。
let schema = buildSchema(
  `type Query {
    rollDice(numDice: Int!, numSides: Int): [Int]
  }`
)
// これがリゾルバぽい
let root = {
  hello() {
    return "Hello world!"
  },
  rollDice({numDice, numSides}) {
    console.log("🚀 ~ rollDice ~ numDice:", numDice)
    return [numDice+numSides]
  }
}

var app = express()

app.all(
  "/graphql",
  createHandler({
    schema: schema,
    rootValue: root,
  })
)

tomitomi

curlだとこれで渡せる。

curl -X POST -H "Content-Type: application/json" -d '{"query": "{ rollDice(numDice: 3, numSides: 6) }"}' http://localhost:4000/graphql

{"data":{"rollDice":[9]}}
tomitomi

type Query で複数のときは、セミコロン区切りとかない。TSとは違う。

let schema = buildSchema(`
  type Query {
    quoteOfTheDay: String
    random: Float!
    rollThreeDice: [Int]
  }
`)
tomitomi

クエリに引数を渡す

スキーマ、リゾルバ

let schema = buildSchema(`
  type Query {
    rollThreeDice(numDice: Int!, numSides: Int): [Int]
  }
`)

let root = {
  rollThreeDice({numDice, numSides}) {
    return [1, 2, 3].map(_ => 1 + Math.floor(Math.random() * 6))
  },
}

クエリ
引数のワタシかたはjsの名前付きとは違う?

{
  rollDice(numDice: 3, numSides: 6)
}

jsなら{}でかこって渡す。

変数にしてクエリを書くと?

RollDiceは関数名のようなもの。

`query RollDice($dice: Int!, $sides: Int) {
  rollDice(numDice: $dice, numSides: $sides)
}`

query ってして、名前をつけると複数かけない?

  • ちがいがよくわかっていない。

以下のようにすると複数いける。
queryはあくまで一つで、その中のフィールドに引数を渡してあげる。引数はqueryのトップレベルのところでうけとるのか。

query RollDice($dice: Int!, $sides: Int) {
  quoteOfTheDay
  random
  rollThreeDice(numDice: $dice, numSides: $sides)
}

tomitomi

[Object Types | GraphQL https://graphql.org/graphql-js/object-types/]

let express = require("express")
var { createHandler } = require("graphql-http/lib/use/express")
let { buildSchema } = require("graphql")

let schema = buildSchema(`

  type RandomDie {
    numSides: Int!
    rollOnce: Int!
    roll(numRolls: Int!): [Int]
  }

  type Query {
    getDie(numSides: Int): RandomDie # これを追加する
  }
`)

class RandomDie {
  constructor(numSides) {
    this.numSides = numSides
  }

  rollOnce() {
    return 1 + Math.floor(Math.random() * this.numSides)
  }

  roll({ numRolls }) {
    var output = []
    for (var i = 0; i < numRolls; i++) {
      output.push(this.rollOnce())
    }
    return output
  }
}

// これがリゾルバ
let root = {
  getDie({ numSides }) {
    return new RandomDie(numSides || 6)
  },
}
...

これを呼ぶクエリ

{
  getDie(numSides: 6) {
    rollOnce
    roll(numRolls: 3)
  }
}

getDieでスキーマのtype QueryのgetDie→リゾルバがRandomDieのインスタンスを返す。→ RandomDieのインスタンスメソッドのrollOnceをフィールドとして呼べる。

フィールドでメソッドも呼べるのか。

{
  getDie(numSides: 6) {
    first: roll(numRolls: 2)
    second: roll(numRolls: 3)
  }
}

これで同じメソッドも複数回よぶこともできる。別名をつけること。

tomitomi

mutation

[Mutations and Input Types | GraphQL https://graphql.org/graphql-js/mutations-and-input-types/]

更新処理

  • input とtype Mutation
  • inputはtypeでもかける?けどinputであることを明示できるから?
  • type MutationはsetMessageとかupdateMessageとか
input MessageInput {
  content: String
  author: String
}
 
type Message {
  id: ID!
  content: String
  author: String
}
 
type Query {
  getMessage(id: ID!): Message
}
 
type Mutation {
  createMessage(input: MessageInput): Message
  updateMessage(id: ID!, input: MessageInput): Message
}

english

  • rather than , ~ではなく
tomitomi

mutationでinputとtypeのちがいは?

input

  • 入力で利用する
  • inputはtypeをネストするのは非推奨
  • 入力専用だから計算フィールドを持たない?

type

  • オブジェクトの型定義として利用する
  • inputもtypeもネストok
  • 計算フィールドをもっていい?

入力とそうじゃないものを分けて、可読性?安全性の変更
意図を明確にするため。

tomitomi

mutationのクエリの書き方。

mutation {
  createMessage(input: {
    author: "andy",
    content: "hope is a good thing",
  }) {
    id
  }
}
tomitomi

inputを使わないで更新処理

let schema = buildSchema(`

  type Mutation {
    setMessage(message: String): String
  }

  type Query {
    getMessage: String
  }
`)

var fakeDatabase = {}
var root = {
  setMessage({ message }) {
    fakeDatabase.message = message
    console.log("🚀 ~ setMessage ~ fakeDatabase.message:", fakeDatabase.message)
    return message
  },
  getMessage() {
    return fakeDatabase.message
  },
}

これでもmutationはできる。

これでセットできる。

mutation {
  setMessage(message: "sassss")
}

これでgetできる。

query {
  getMessage
}
tomitomi

inputを利用する。

messageはクラスに。


let schema = buildSchema(`

  input MessageInput {
    content: String
    author: String
  }

  type Message {
    id: ID!
    content: String
    author: String
  }

  type Mutation {
    createMessage(input2: MessageInput): Message
    updateMessage(id: ID!, input: MessageInput): Message
  }

  type Query {
    getMessage(id: ID!): Message
  }
`)

class Message {
  constructor(id, { content, author }) {
    this.id = id
    this.content = content
    this.author = author
  }
}


var fakeDatabase = {}
var root = {
  createMessage({ input2 }) {
    var id = require("crypto").randomBytes(10).toString("hex")
    fakeDatabase[id] = input2
    return new Message(id, input2)
  },
  updateMessage({ id, input }) {
    if (!fakeDatabase[id]) {
      throw new Error("no message exists with id " + id)
    }
    // This replaces all old data, but some apps might want partial update.
    fakeDatabase[id] = input
    return new Message(id, input)
  },
  getMessage({id}) {
    if (!fakeDatabase[id]) {
      throw new Error("no message exists with id " + id)
    }
    return new Message(id, fakeDatabase[id])
  },
}

ミューテーションの実行

引数は以下の様に渡せる。inputの名前はなんでも良さそう。
フィールドの指定で結果を求める必要がある?

mutation {
	createMessage(input2: {
    content: "コンテンツ",
    author: "me"
  }) {
    id
  }
}

tomitomi

js-tutorial完了

  • graphqlをjsで利用する方法を学習
  • expressサーバーでhttpリクエストを利用したgraphqlの利用方法を学んだ
  • typeを利用してqueryやmutationなどのスキーマ定義をして、queryを実行する方法を学んだ。
  • 変数でのクエリ実行もok
  • DBとの紐づけはまだやっていない。
  • ruruを用いて、IDEをつかったクエリの記述を知った。

TODOアプリを作るなら?

  • graphqlをimportして、expressでhttpでのgraphql実行環境を用意
  • スキーマとして、class Todoをつくったり、createTodo,updateTodo, deleteTodoを作る。getTodolistやgetTodoなどをqueryを作成する。
  • 各種クエリ、mutationに対応するリゾルバを作成する。
  • これでTODOの挙動はできる?
  • クエリを呼ぶ側がいないので、APIサーバーができるだけか。
  • フロント側はreactとかでつくったとして、APIを飛ばせるようにして、graphqlのクエリをフロント側に実装が必要。fetchとかでもできる。

  • apollo とかつかって実践的な利用方法を学ぶ必要がある。
  • type ORM, type-graphqlなどでのスキーマの定義方法、利用方法を知る必要がある。
tomitomi

graphql-tag

  • Tagged Template Literals
  • タグ付きテンプレートリテラル

jsの機能

function myTag(string, values){}
myTag`aaaaaaaa, ${aaaaa}`
とかね。
tomitomi

import { addMocksToSchema } from "@graphql-tools/mock";
import { makeExecutableSchema } from "@graphql-tools/schema";

これで自分の定義したスキーマに対してmockを導入できる。

tomitomi

@apollo/client の ApolloClient, ApolloProvider, InMemoryCache を使って、フロントのreactで利用できるようにする。

  • ApolloClientはクライアント本体
  • graphqlサーバーのURIとキャッシュの設定とともに、インスタンス化する
  • apolloProviderでreactにわたす。
tomitomi

@graphql-codegen/cli

  • graphqlスキーマをフロントで使うにために、型を生成してくれるツール

    npm install -D @graphql-codegen/cli @graphql-codegen/client-preset @graphql-codegen/typescript @graphql-codegen/typescript-operations
    
tomitomi

useQueryを利用して、react上でクエリ発行できた。

1つ目完了!

tomitomi

リゾルバーの学習

datasoruceがREST APIの場合?
N+1とかの問題をどうするのか?とかの話?

@apollo/datasource-rest

tomitomi

リゾルバー

  • 引数
    • parent
    • args
    • conextValue
    • info
tomitomi

resolver.tsを作成

dataSourceはcontextValueとして。datasoruce-restをつかってつくったrest api経由のdatasoruceのクラスを使う。

export const resolvers = {
  Query: {
    // returns an array of Tracks that will be used to populate
    // the homepage grid of our web client
    tracksForHome: (_, __, { dataSources }) => {
      return dataSources.TrackAPI.getTracksForHome()
    },
  },
  Track: {
    author: ({authorId}, _, { dataSources }) => {
      return dataSources.TrackAPI.getAuthor(authorId)
    }
  }
};
tomitomi

apollo stadioで動作確認できる。
graphqlはrest apiをdata sourceにもできるから、既存のrest api をwrapしたgraphqlもつくれるのか。
移行とかするまではこれ。って感じでできるのかもなぁ。

resoloverの型もgeneratorでtsの型生成をしてあげるといい。

tomitomi

Lift-off 2終了!

  • rest のdata sourceで実装
  • リゾルバーの利用周りを実践

次は、Lift-off 3。

  • args, parent周りをもうすこしやる
tomitomi

Resolver chains

  • ネストされたオブジェクトを取得する仕組み?
  • N+1みたいなケース
tomitomi

全体の流れわすれるなぁ
新規のクエリを追加するなら、、

  • backend
    • schemaを定義する
    • resolver作る
    • あわせてts型まわり整理
  • frontend
    • GET_XXXみたいなgqlでクエリを定義する
    • useQueryで実行する。引数はここで渡せる。
    • あとは、描画する。
tomitomi

[スキーマ定義から見たgraphql|概念から理解するgraphql https://zenn.dev/urotea/books/ece9493100b5be/viewer/53920c]

なんとなくよりみち。
キーワードとかはわかったので、これでもうすこしイメージっを強化。

IDの意味がスキーマ全体での一意なのは気づいてなかった。

tomitomi

複数のdatasourceから取得するケースで便利なのか。BFFとして。

  • 以前のPJでの、AシステムからCシステムへユーザー情報とか取得してたやつの問題を吸収できたりしそうだ。
tomitomi
  • type Mutationでスキーマの定義
  • mutationのresponseは?以下がよいらしい?

code (a non-nullable Int)
success (a non-nullable Boolean)
and message (a non-nullable String)

tomitomi

mutation

  • backend
    • define type Mutation
    • implement resolver of the Mutation we defined
    • if as needed, we add rest api datasorucce
  • frontend
    • set mutation with gql
    • use useMutation
tomitomi

apollo Client cache のキャッシュの機能が強い?

  • キャッシュの挙動はちゃんと理解しておかないとだ。
  • page遷移時のclick時にmutation操作をした場合に、ページ遷移して最初はキャッシュでの表示、その後更新されて、その更新された値で変更されて表示される。というのもできてる。
    うまい具合にキャッシュと更新とでやってくれてる。

いい感じにしてくれてるがゆえに....の問題はあるのでちゃんと理解しておこ

tomitomi

無事完了。

ただ、実務的には、N+1の対応が必要そうだ。
dataloaderの理解が必要層。

tomitomi

Next

  • start tutorial dataloader
  • create TODO app
  • with nextjs, apollo server /client , database(mysql)
tomitomi

apollo studioはmock機能があるのか。

tomitomi

dataloader

  • 似たリクエストをまとめてバッチリクエストにすることでN+1を解消する機能
  • 最初のリスト取得のリクエスト内のサブリソースのIDをまとめて、リクエストを送るようにする。

FYI

  • apollo Federation
  • 複数のgraphqlをまとめたレイヤーをついかする機能
tomitomi

todo app を作ってみる。

  • フロントエンドはnext, tsで。
  • サーバー側は?
    • apollo server でgraphqlを使えるようにしたい¥
    • expressは 必要なのか?
tomitomi

expressは軽量なwebフレームワーク
- rubyでいうとsinatra的なかんじ。

tomitomi
  • 基本は認証処理とかいろいろやりたいからexpressとapollo serverを使うはず。
  • とりあえずgraphqlをうごかしたいのでapollo serverだけで動かしてみる。
tomitomi

nextjs14

  • 14からrouter がapp routerになった。13まではpage router。
  • page routerはpages配下において、nuxtぽいうごき
  • app routerはpage.tsx などファイル名はpageが必要
    • またapp routerはサーバーコンポーネントであり、サーバーでレンダリングされる
      [Next.jsでのレンダリングを理解してSSRを効果的に活用する - i3DESIGN Tech Blog https://tech.i3design.jp/nextjs-rendering-csr-ssr/]
tomitomi

とりあえず、pageの中にいれこむ。

import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:4000/',
  cache: new InMemoryCache(),
});


export default function TodoPage() {

  client.query({
    query: gql`
      query Todos {
        todos {
          content
          status
        }
      }
    `
  }).then((result) => console.log(result));

...

tomitomi
  • うごいたっちゃ、うごいたけど
  • TSの型がない。
  • クライアントを使うたびにインスンタンス化が必要な書き方になってる。
tomitomi

ApolloProviderつかえばよいのか。
clientを全体で使えるようにする。

import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:4000/',
  cache: new InMemoryCache(),
});




export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <ApolloProvider client={client}>
      <html lang="ja">
        <body
          className={`${geistSans.variable} ${geistMono.variable} antialiased`}
          >
          {children}
        </body>
      </html>
    </ApolloProvider>
  );
}

tomitomi

useQuery を使う。


const GET_TODOS = gql`
  query GetTodos {
    todos {
      content
      status
    }
  }
`;

export default function TodoPage() {

  const  {loading, error, data } = useQuery(GET_TODOS)

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error : {error.message}</p>;

  return (
    <Layout>
      <div className="max-w-md mx-auto mt-8">
        <h1 className="text-2xl font-bold mb-4"> todo list ({ data.todos.length })</h1>

        <ul className='border p-4'>
          {
            data.todos.map((todo, index) => {
              return <li key={index}>{ todo.content }</li>
            })
           }
        </ul>
        <div className="">
          <Link href={"/todo/new"}>新規作成</Link>
        </div>
      </div>
    </Layout>
  )
}
tomitomi
  • これで一応grpahqlを実行することができる状態になって、データ表示もできてる。
tomitomi
  • 新規作成、編集、削除をできるようにする。
tomitomi
  • mutationを追加
  • inputとresponseをつくる。
    • responseの型は公式を参考に。code, success, message+ 作成物
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
const typeDefs = `#graphql
  # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

  # This "Book" type defines the queryable fields for every book in our data source.
  type Todo {
    id: ID!
    content: String
    status: Status!
  }

  enum Status {
    "未着手"
    TODO
    "着手中"
    IN_PROGRESS
    "完了"
    DONE
  }

  # The "Query" type is special: it lists all of the available queries that
  # clients can execute, along with the return type for each. In this
  # case, the "books" query returns an array of zero or more Books (defined above).
  type Query {
    todos: [Todo]
  }

  type Mutation {
    addTodo(input: NewTodoInput!): NewTodoResponse!
  }

  input NewTodoInput {
    content: String!
  }

  type NewTodoResponse {
    code: Int!
    success: Boolean!
    message: String!
    todo: Todo
  }
`;

const todos = [
  {
    content: "御飯食べる",
    status: "TODO"
  },
  {
    content: "布団をたたむ",
    status: "IN_PROGRESS"
  },
  {
    content: "ご飯をつくる",
    status: "IN_PROGRESS"
  },
  {
    content: "起きる",
    status: "DONE"
  },
];

const resolvers = {
  Query: {
    todos: () => todos,
  },
  Mutation: {
    addTodo: (_parent, args, _context, _info) => {
      console.log("🚀 ~ test:", args)

      return {
        code: 200,
        success: true,
        message: "new todo created!",
        todo: {
          id: "xxx",
          content: args.input.content,
          status: "TODO"
        }
      }
    }
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);
tomitomi

pages routerに変更しておく。

  • app routerはあとで。
  • pagesフォルダ作る
    • layout.tsx → _app.tsx
    • app.tsxを編集
tomitomi

add mutation that create new todo.

import { gql, useMutation } from "@apollo/client"
import { useState } from "react"


const CREATE_TODO = gql(`
    mutation Mutation($input: NewTodoInput!) {
      addTodo(input: $input) {
        code
        success
        message
        todo {
          id
          content
          status
        }
      }
    }
  `)

export default function TodoNewPage() {
  // ページ繊維がおそいのはなんでだろうか? -> SSRだからだ

  const [input, setInput] = useState('')

  const [createTodo] = useMutation(CREATE_TODO, {
    variables: {
      input: {
        content: input
      }
    },

    onError: (error) => {
      console.log("🚀 ~ TodoNewPage ~ error:", error)
    },
    onCompleted: (data) => {
      console.log("🚀 ~ TodoNewPage ~ data:", data)
    }
  })


  return <>
    <div className="max-w-md mx-auto mt-8">
      <h1 className="text-2xl font-bold mb-4"> todo add </h1>

      <input
        type="text"
        value={input}
        placeholder="新しいタスクを入力"
        className="border font-black"
        onChange={(e) => setInput(e.target.value)}
      />

      <p>{input}</p>

      <button type="button" onClick={()=> createTodo()}>保存</button>
      </div>
  </>
}
tomitomi
  • ここまででCRUD操作ができるようになった。
  • データはメモリ上でも更新されているわけじゃない。
  • メモリ上にのこるようにした。fakeDB = [] を使う。
tomitomi

react の関数の中の実行ライフサイクルはどうなっているのか?

  • inputのonChangeでsetInputのたびに、再度実行されている。
  • vuejsのCAPIのような挙動とは違う?
  • レンダリングのたびに全部実行されているの?
tomitomi

React Strict Modeによって開発環境で意図的に2回レンダリングが行われることが主な原因です。
なんじゃこりゃぁ。だから二回よばれているのか。

tomitomi

問題

  • todoを一つ削除したあとに、list表示ページにもどると、削除したはずのtodoが表示されてしまう。

原因

  • apolo clientのキャッシュ
  • キャッシュされたデータは明示的にクリアされるまで残るようだ。
  • ブラウザリロードで消えはする。sessionにある? → NO
    • ブラウザのメモリ内であり、web storageではない。javascript heap memory
  • タブ閉じる、リロードなどで消える。

メモ

  • client.resetStore()したら、またエラーでした。