graphQLに入門する

を順番に読む。
その後、tutorial予定
- ただnode6でやるっぽくて、古いバージョン....

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

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

削除もミューテーションでできる。
- idを指定してリソースの削除するをするなど。
- QA
- deleteであることはどこで判断するんだ?実装?

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

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

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

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

レスポンス
[Response | GraphQL https://graphql.org/learn/response/]
- 基本はjsonで返される。
- またHTTPのステートレスでやるのが基本、サブスクリプションはサーバーイベントやwebsocketなど
レスポンスはdata,errors, extensionsの3つが用意されている。
部分的に成功の場合も、部分的に成功を返して、errorsも返したりする。

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

[Introspection | GraphQL https://graphql.org/learn/introspection/]
- クエリでスキーマの情報をいろいろみられる。

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

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系

認証、認可
基本はgraphQLに到達するまえのミドルウェア層でユーザー認証などをしておくとよいが、特定のフィールドごとに異なるとかだとどうする?って話。
下書きは著者のみが閲覧できます など
- postを投稿した本人だけが更新、削除できるというユースケースの場合。
- リゾルバで本人か?を確認したくなる。
- そうすると、この処理をいたるところで書く必要がでてきてしまう?
- 認証用のリソースをおくといいらしい。
- ビジネス ロジック レイヤーに委任したほうがいい。
- リゾルバでもできるけどね。
- リゾルバで本人か?を確認したくなる。
別の方法で、リゾルバないでディレクティブを利用する方法はこれ
type Post {
authorId: ID!
body: String @auth(rule: IS_AUTHOR)
}

スキーマ設計
[Schema Design | GraphQL https://graphql.org/learn/schema-design/]
バージョン管理について
- バージョン管理をしないようにすることがGraphQLでは大事にされていそう。
- バージョン管理するってことは、破壊的変更とかに対応するためだったりするが、そもそも互換性をもちつづけて、破壊的変更がないようにすればバージョン管理もいらないよねって考え方だ。
graphQLのnull許容
- デフォルトはnull許容してる。
- なんからの障害とかで一部のデータが取得できなくて、nullになるケースがある。だからnull許容して、常にnullの可能性を考慮しながらやるといい?

🚧グローバルオブジェクト識別
[Global Object Identification | GraphQL https://graphql.org/learn/global-object-identification/]
Nodeというインタフェースとnodeというクエリフィールドが予約語として用意されている。
なんか特別な機能をもってるフィールド。
?? いったん’skip。

キャッシング
[Caching | GraphQL https://graphql.org/learn/caching/]
RESTだとURLでリソースを識別できれるから、HTTPのキャッシュができる。
GraphQLはendpointが一つしかないからキャッシュどうする?って問題がでる。
オブジェクトにグローバルに一意なIDを振るといい?
- レコードのサロゲートキー的なもの?でいいのか?
- クエリ全体のキャッシュってことじゃないのか。。

パフォーマンス
[Performance | GraphQL https://graphql.org/learn/performance/]
いろんなところでキャッシュ戦略が適応できるらしい。
- クライアント側でのキャッシュ
- GETの問い合わせ
- クエリのハッシュをつかって、キーバリューストアとかで保存?
- N+1
- 気にせずリゾルバを作成するとDBとのやりとりは多発。
- バッチ処理技術で対処できるそう。
- dataloaderというやつがある
- 最適化されたクエリに変更する機能があるらしい。
- 圧縮
- jsonをgzipしよう
- モニタリングをしましょう。
memo
- N+1のところが気になる。
- ほかはまぁRESTと一緒でできるよって感じ。
- またCDNのキャッシュとかはクエリをハッシュにすることでできるって感じ。

セキュリティ
[Security | GraphQL https://graphql.org/learn/security/]
TCPレイヤー
- 一般的な対応をGraphQLでもする
- HTTPS、タイムアウト、とか
制限
- IP制限とか
- クエリのネストの深さの制限をもうけるとか
- クエリの幅も。
- レート制限
- クエリの複雑さのコストで制限する
- エラーメッセージは本番に出しすぎないとか。

一通り読み終えた。
- GraphQLのなんとなくの登場人物がわかった。
- クエリ、サブスクリプション、ミューテーション。
- スキーマ。型でできること
- リゾルバの存在
実際に実装してねはまだ無理なので、別のチュートリアルで動きを試す必要がある。

公式チュートリアルをやる
[Getting Started With GraphQL.js | GraphQL https://graphql.org/graphql-js/]

- graphql
- express サーバー
- graphql-http httpでgraphqlを使う。
graphiQLというツール。IDE。
- ruru でサーバーで使えるようにする。
- localhost:4000で起動する。

node は v22.11.0 で実行。

ruru
variableの書き方はこれ。

引数を渡す事ができる。
スキーマで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,
})
)

curlだとこれで渡せる。
curl -X POST -H "Content-Type: application/json" -d '{"query": "{ rollDice(numDice: 3, numSides: 6) }"}' http://localhost:4000/graphql
{"data":{"rollDice":[9]}}

type Query で複数のときは、セミコロン区切りとかない。TSとは違う。
let schema = buildSchema(`
type Query {
quoteOfTheDay: String
random: Float!
rollThreeDice: [Int]
}
`)

クエリに引数を渡す
スキーマ、リゾルバ
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)
}

[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)
}
}
これで同じメソッドも複数回よぶこともできる。別名をつけること。

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 , ~ではなく

mutationでinputとtypeのちがいは?
input
- 入力で利用する
- inputはtypeをネストするのは非推奨
- 入力専用だから計算フィールドを持たない?
type
- オブジェクトの型定義として利用する
- inputもtypeもネストok
- 計算フィールドをもっていい?
入力とそうじゃないものを分けて、可読性?安全性の変更
意図を明確にするため。

english
- as if まるで〜

mutationのクエリの書き方。
mutation {
createMessage(input: {
author: "andy",
content: "hope is a good thing",
}) {
id
}
}

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
}

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
}
}

変数を使う書き方。

認証とミドルウェア
[Authentication and Express Middleware | GraphQL https://graphql.org/graphql-js/authentication-and-express-middleware/]
english
- like you would [use middleware] with a normal Express app.
- []内の動詞が省略されている。
- あきらかな動詞は省略されるようだ。

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などでのスキーマの定義方法、利用方法を知る必要がある。

ほかにも学習リソースある。
- [How to GraphQL - The Fullstack Tutorial for GraphQL https://www.howtographql.com/]
- アポロオデッセイを見る。
- [Learn with our GraphQL Tutorials, Examples, and Training - GraphQL Tutorials https://www.apollographql.com/tutorials/]
- 以下からやると良さそう。
- [GraphQL Tutorial, Lift-off I: Basics - GraphQL Tutorials https://www.apollographql.com/tutorials/lift-off-part1]

https://www.apollographql.com/tutorials/lift-off-part1]
[GraphQL Tutorial, Lift-off I: Basics - GraphQL Tutorials- スキーマファーストでやる
- バックエンド
- フロントエンド
スキーマファーストはスキーマあれば、フロントエンド、バックエンドの両方の開発を開始できる。

graphql-tag
- Tagged Template Literals
- タグ付きテンプレートリテラル
jsの機能
function myTag(string, values){}
myTag`aaaaaaaa, ${aaaaa}`
とかね。

import { addMocksToSchema } from "@graphql-tools/mock";
import { makeExecutableSchema } from "@graphql-tools/schema";
これで自分の定義したスキーマに対してmockを導入できる。

@apollo/client の ApolloClient, ApolloProvider, InMemoryCache を使って、フロントのreactで利用できるようにする。
- ApolloClientはクライアント本体
- graphqlサーバーのURIとキャッシュの設定とともに、インスタンス化する
- apolloProviderでreactにわたす。

@graphql-codegen/cli
-
graphqlスキーマをフロントで使うにために、型を生成してくれるツール
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset @graphql-codegen/typescript @graphql-codegen/typescript-operations

useQueryを利用して、react上でクエリ発行できた。
1つ目完了!

second mission
[Exploring data for our GraphQL API - GraphQL Tutorials https://www.apollographql.com/tutorials/lift-off-part2/02-exploring-our-data]
- いろんなdatasourceから取得できる。graphql

リゾルバーの学習
datasoruceがREST APIの場合?
N+1とかの問題をどうするのか?とかの話?
@apollo/datasource-rest

リゾルバー
- 引数
- parent
- args
- conextValue
- info

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)
}
}
};

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

Lift-off 2終了!
- rest のdata sourceで実装
- リゾルバーの利用周りを実践
次は、Lift-off 3。
- args, parent周りをもうすこしやる

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

全体の流れわすれるなぁ
新規のクエリを追加するなら、、
- backend
- schemaを定義する
- resolver作る
- あわせてts型まわり整理
- frontend
- GET_XXXみたいなgqlでクエリを定義する
- useQueryで実行する。引数はここで渡せる。
- あとは、描画する。

3個目も完了した。

次はmutationとかぽい。

[スキーマ定義から見たgraphql|概念から理解するgraphql https://zenn.dev/urotea/books/ece9493100b5be/viewer/53920c]
なんとなくよりみち。
キーワードとかはわかったので、これでもうすこしイメージっを強化。
IDの意味がスキーマ全体での一意なのは気づいてなかった。

複数のdatasourceから取得するケースで便利なのか。BFFとして。
- 以前のPJでの、AシステムからCシステムへユーザー情報とか取得してたやつの問題を吸収できたりしそうだ。

- type Mutationでスキーマの定義
- mutationのresponseは?以下がよいらしい?
code (a non-nullable Int)
success (a non-nullable Boolean)
and message (a non-nullable String)

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

apollo Client cache のキャッシュの機能が強い?
- キャッシュの挙動はちゃんと理解しておかないとだ。
- page遷移時のclick時にmutation操作をした場合に、ページ遷移して最初はキャッシュでの表示、その後更新されて、その更新された値で変更されて表示される。というのもできてる。
うまい具合にキャッシュと更新とでやってくれてる。
いい感じにしてくれてるがゆえに....の問題はあるのでちゃんと理解しておこ

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

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

https://www.apollographql.com/tutorials/intro-typescript/01-course-overview-and-setup]
[Course overview and setup - GraphQL Tutorials- dataloaderやるまえにこれもやっておく。

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

dataloader
- 似たリクエストをまとめてバッチリクエストにすることでN+1を解消する機能
- 最初のリスト取得のリクエスト内のサブリソースのIDをまとめて、リクエストを送るようにする。
FYI
- apollo Federation
- 複数のgraphqlをまとめたレイヤーをついかする機能

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

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

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

[Get Started with Apollo Server - Apollo GraphQL Docs https://www.apollographql.com/docs/apollo-server/getting-started]
- npx でnextのpjを作成
- backend
- npm init
- @apollo/server, typescript, type/nodeのinstall

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/]
- またapp routerはサーバーコンポーネントであり、サーバーでレンダリングされる

nextjsでapollo client を使う
todoのlistを取得したい。dataはserver側のハードコードされたtodoデータでいい。
- [Next.js + Apollo でシュッとGraphQLを始める https://zenn.dev/yuta4j1/articles/nextjs-apollo-starter]
- [Get started with Apollo Client - Apollo GraphQL Docs https://www.apollographql.com/docs/react/get-started]
npm install @apollo/client graphql

とりあえず、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));
...

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

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>
);
}

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>
)
}

- これで一応grpahqlを実行することができる状態になって、データ表示もできてる。

- 新規作成、編集、削除をできるようにする。

- 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}`);

pages routerに変更しておく。
- app routerはあとで。
- pagesフォルダ作る
- layout.tsx → _app.tsx
- app.tsxを編集

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>
</>
}

- ここまででCRUD操作ができるようになった。
- データはメモリ上でも更新されているわけじゃない。
- メモリ上にのこるようにした。fakeDB = [] を使う。

react の関数の中の実行ライフサイクルはどうなっているのか?
- inputのonChangeでsetInputのたびに、再度実行されている。
- vuejsのCAPIのような挙動とは違う?
- レンダリングのたびに全部実行されているの?

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

問題
- todoを一つ削除したあとに、list表示ページにもどると、削除したはずのtodoが表示されてしまう。
原因
- apolo clientのキャッシュ
- キャッシュされたデータは明示的にクリアされるまで残るようだ。
- ブラウザリロードで消えはする。sessionにある? → NO
- ブラウザのメモリ内であり、web storageではない。javascript heap memory
- タブ閉じる、リロードなどで消える。
メモ
- client.resetStore()したら、またエラーでした。

TODO:

codegenで型生成をする
[the-guild.dev/graphql/codegen/docs/getting-started https://the-guild.dev/graphql/codegen/docs/getting-started]
backend
- graphqlのschemaから各種objectなどの型を生成する
frontend
- bakcendのgraphql schemaからフロントで使えるtsの型を生成できる。