Yuya-Furusawa / sns-merng

0 stars 0 forks source link

調べたことのメモ(主にApollo + MongoDBのこと) #1

Open Yuya-Furusawa opened 3 years ago

Yuya-Furusawa commented 3 years ago

MERNGスタックでアプリを作る際に調べたことをメモ

Apollo

Apollo is a platform for building a data graph, a communication layer that seamlessly connects your application clients (such as React and iOS apps) to your back-end services.

ApolloはGraphQLを扱うためのプラットフォーム。クライアント側からGraphQLを扱うApollo Client、サーバー側のApollo Serverなどがある。

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. Use it to fetch, cache, and modify application data, all while automatically updating your UI.

GraphQLはただの仕様に過ぎないのでネットワークリクエストやデータのキャッシュなどをしてくれるクライアントが多く開発されている、Apolloクライアントはそのうちの1つ。

Apollo Server is an open-source, spec-compliant GraphQL server that's compatible with any GraphQL client, including Apollo Client. It's the best way to build a production-ready, self-documenting GraphQL API that can use data from any source.

Apollo Serverを使えばGraphQLサーバーの構築を簡単にできる。加えて、Expressなどの各種フレームワークにも対応することができる。

MongoDB NoSQLデータベース、JavaScriptやPythonなどさまざまな言語でサポートされる

mongoose https://mongoosejs.com/docs/index.html Node.js用のMongoDBライブラリ、MongoDBの操作などをサポートしてくれる 事前にNode.jsとMongoDBを用意する必要あり

Yuya-Furusawa commented 3 years ago

GraphQLの初歩

使い方

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

参照

Yuya-Furusawa commented 3 years ago

mongooseの使い方

スキーマ・モデル・ドキュメント

// 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' }]
});

参照

Yuya-Furusawa commented 3 years ago

GraphQLでのAuthentication(認証)

//この関数は、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'];
}
Yuya-Furusawa commented 3 years ago

Subscriptionについて

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']);

参照

Yuya-Furusawa commented 3 years ago

Apollo Clientの設定

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

参照

Yuya-Furusawa commented 3 years ago

useMutationのやり方(useQueryも大体同じ)

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

参照

Yuya-Furusawa commented 3 years ago

cache(キャッシュ)とは

Apolloでのmutation

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

参照

Yuya-Furusawa commented 3 years ago

ポート使用されてるエラー

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
Yuya-Furusawa commented 3 years ago

HTTPヘッダとは?

Yuya-Furusawa commented 3 years ago

追記:mongoDBへの接続

// スキーマの定義
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' });

参照

Yuya-Furusawa commented 3 years ago

ApolloのQueryでのcache

参照

Yuya-Furusawa commented 3 years ago

ApolloでMutationしたあとにlocal dataをupdateする

「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/writeQueryreadFragment/writeFragmentcache.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.

参照