Open Yuya-Furusawa opened 3 years ago
使い方
const { ApolloServer, gql } = require('apollo-server');
//スキーマの定義
const schema = gql`
type Query{
sayHi: String
}
`;
//リゾルバ関数の定義
const resolvers = {
Query: {
sayHi: () => "Hello World!";
}
};
//ApolloServerインスタンスの作成
const server = new ApolloServer({
schema,
resolvers
})
//サーバー立ち上げ
server.listen({port: 5000});
参照
スキーマ・モデル・ドキュメント
// mongooseではスキーマからすべてが始まる
// スキーマの定義
const kittySchema = new mongoose.Schema({
name: String
});
//スキーマをモデルにコンパイル
//Kittenという名前で参照する
const Kitten = mongoose.model('Kitten', kittySchema);
//モデルを使ってドキュメントを作成
//ドキュメントは、スキーマで定義したプロパティや動作を持つ
const silence = new Kitten({ name: 'Silence' });
console.log(silence.name); // 'Silence'
クエリ
//クエリはモデルを介して行われる
//nameがGhostのKittenを1つ取ってくる、そしてそれをqueryに格納
const query = Kitten.findOne({ 'name': 'Ghost' });
//データ保存はドキュメントを介して行われる
const fluffy = new Kitten({ name: 'fluffy' });
fluffy.save();
スキーマの定義の仕方について補足(リレーションについて)
//以下は同じ
const kittySchema = new mongoose.Schema({
name: String
});
const kittySchema = new mongoose.Schema({
name: {type: String}
});
//ref属性について
//refを使うとリレーションを表現することができる
const personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
const storySchema = Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
参照
context
オブジェクトを作成しそこに、認証情報などを格納する//この関数は、userフィールドを持ったcontextオブジェクトを作成している
//これはコンストラクタで定義しておく
context: ({ req }) => {
// get the user token from the headers
const token = req.headers.authentication || '';
// try to retrieve a user with the token
const user = getUser(token);
// optionally block the user
// we could also check user roles/permissions here
if (!user) throw new AuthorizationError('you must be logged in');
// add the user to the context
return { user };
}
//こんな感じで取り出して操作できる
users: (root, args, context) => {
// In this case, we'll pretend there is no data when
// we're not logged in. Another option would be to
// throw an error.
if (!context.user) return [];
return ['bob', 'jake'];
}
Apollo-ServerでのSubscriptionのやり方
// スキーマの定義は同じ
type Subscription {
postCreated: Post
}
// リゾルバ関数の定義
// リゾルバ関数はsubscribe関数でなくてはならない
// そしてsubscribe関数はAsyncIteratorタイプを返さなくてはならない
const resolvers = {
Subscription: {
subscribe: () => pubsub.asyncIterator('POST_CREATED')
}
}
// PubSubクラス
// Apollo Serverはpub/subモデルを使用してサブスクリプションを更新するイベントを追跡する
const { PubSub } = require('apollo-server');
const pubsub = new PubSub();
// publishメソッドでイベントを追加
// 第1引数でどのイベントに追加するか
// 第2引数はデータ
pubsub.publish('POST_CREATED', {
postCreated: {
author: 'Ali Baba',
comment: 'Open sesame'
}
});
// イベントをlistenする
pubsub.asyncIterator(['POST_CREATED']);
参照
//@apollo/clientはセットアップに必要なものが全て含まれてる
$ npm install @apollo/client graphql
//クライアントを作成
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://hogehoge.com',
cache: new InMemoryCache()
});
//これで準備完了
import React from 'react';
import { render } from 'react-dom';
import { ApolloProvider } from '@apollo/client';
//Apollo Providerでアプリをwrapする
//clientとreactを接続する役割
function App() {
return (
<ApolloProvider client={client}>
<div>
<h2>My first Apollo app 🚀</h2>
</div>
</ApolloProvider>
);
}
render(<App />, document.getElementById('root'));
//useQueryを使ってデータのリクエストを行う
import { useQuery, gql } from '@apollo/client';
//GraphQL Queryの定義
const EXCHANGE_RATES = gql`
query GetExchangeRates {
rates(currency: "USD") {
currency
rate
}
}
`;
function ExchangeRates() {
//データが無事返ってきたらdata変数に格納される
const { loading, error, data } = useQuery(EXCHANGE_RATES);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.rates.map(({ currency, rate }) => (
<div key={currency}>
<p>
{currency}: {rate}
</p>
</div>
));
}
参照
import { gql, useMutation } from '@apollo/client';
//GraphQLのクエリ文を書く
//このクエリはスキーマ通りに書かないといけない
//例えばStringはダメ、定義通りString!にする
const ADD_TODO = gql`
mutation AddTodo($type: String!) {
addTodo(type: $type) {
id
type
}
}
`;
function AddTodo() {
let input;
//useMutationの返り値はタプル
//1つ目はmutationを実行する関数
//2つ目は「結果」オブジェクト、その中にはloading、errorの状態、value(mutationの返り)とかが含まれる
const [addTodo, { data }] = useMutation(ADD_TODO);
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
//variablesはmutationに渡す変数
addTodo({ variables: { type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
const [addUser, { loading }] = useMutation(REGISTER_USER, {
//updateはmutationを実行したあとにcacheを更新する関数
update(_, result){
props.history.push('/');
},
//onErrorでエラー処理
onError(err){
setErrors(err.graphQLErrors[0].extensions.exception.errors);
},
//ここで変数代入するの???
variables: values
});
参照
Apolloでのmutation
updateのときは明示的にcacheを更新する必要ない
If a mutation updates a single existing entity, Apollo Client can automatically update that entity's value in its cache when the mutation returns. To do so, the mutation must return the id of the modified entity, along with the values of the fields that were modified.
const UPDATE_TODO = gql`
mutation UpdateTodo($id: String!, $type: String!) {
updateTodo(id: $id, type: $type) {
id
type
}
}
`;
function Todos() {
const [updateTodo] = useMutation(UPDATE_TODO);
return data.todos.map(({ id, type }) => {
let input;
return (
<div key={id}>
<p>{type}</p>
<form
onSubmit={e => {
e.preventDefault();
updateTodo({ variables: { id, type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Update Todo</button>
</form>
</div>
);
});
}
参照
Error: listen EADDRINUSE: address already in use :::5000
ポート5000がすでに使われているというエラー
まず何で使われているのかをチェックlsof -i
コマンド
% lsof -i :5000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 90236 yuyafurusawa 32u IPv6 0x67d6ac7c1270551d 0t0 TCP *:commplex-main (LISTEN)
このプロセスを削除する
kill -9 90236
// スキーマの定義
const kittySchema = new mongoose.Schema({
name: String
}, {
// ここで接続するcollectionを指定する
collection: 'kitties'
});
// モデルを作成
const Kitten = mongoose.model('Kitten', kittySchema);
//モデルを使ってドキュメントを作成
const silence = new Kitten({ name: 'Silence' });
// 'Ghost'という名前のKittenを
// kittiesというcollectionから探してくる!!!
const query = Kitten.findOne({ 'name': 'Ghost' });
参照
cache-first
のfetch policyであるno-cache
とするとcacheの保存はしない(なので毎回ネットワークにリクエストする)cache-and-network
とするとcacheとネットワークリクエストからの結果を比較する参照
「mutationを行う=back-endデータを更新する」ということなので、localにキャッシュされたdataを更新したいはず。 例えばtodoリストにitemを追加したら、そのitemもリストに更新したい。
方法①:Queryをrefetchする シンプルな方法だが、この方法ではネットワークリクエストを発生させてしまう
// mutationの後にrefetchを行う
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
'GetComments' // 以前行ったクエリの名前
],
});
方法②:cacheを直接updateする
ネットワークリクエストを発生させないが、非常に複雑な方法(mutationが複雑化するに従い複雑性が増す)
readQuery
/writeQuery
、readFragment
/writeFragment
、cache.modify
などのAPIを使うことによってcacheを直接操作することができる
//cache.modifyとwriteFragmentを使ってcacheを直接update
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
cache.modify({
fields: {
todos(existingTodos = []) {
const newTodoRef = cache.writeFragment({
data: addTodo,
fragment: gql`
fragment NewTodo on Todo {
id
type
}
`
});
return [...existingTodos, newTodoRef];
}
}
});
}
});
cacheをupdateメソッドの中で更新すると、UIも更新される
Any changes you make to cached data inside of an
update
function are automatically broadcast to queries that are listening for changes to that data. Consequently, your application's UI will update to reflect these updated cached values.
参照
MERNGスタックでアプリを作る際に調べたことをメモ
Apollo
ApolloはGraphQLを扱うためのプラットフォーム。クライアント側からGraphQLを扱うApollo Client、サーバー側のApollo Serverなどがある。
GraphQLはただの仕様に過ぎないのでネットワークリクエストやデータのキャッシュなどをしてくれるクライアントが多く開発されている、Apolloクライアントはそのうちの1つ。
Apollo Serverを使えばGraphQLサーバーの構築を簡単にできる。加えて、Expressなどの各種フレームワークにも対応することができる。
MongoDB NoSQLデータベース、JavaScriptやPythonなどさまざまな言語でサポートされる
mongoose https://mongoosejs.com/docs/index.html Node.js用のMongoDBライブラリ、MongoDBの操作などをサポートしてくれる 事前にNode.jsとMongoDBを用意する必要あり