Blogs

Apollo サーバーと GraphQL サブスクリプションによるリアルタイム通知 API の構築

April 3, 2024

ページを更新せずに、新しいメッセージ、友達リクエスト、アクティビティ通知に関する最新情報をすぐに受け取ることを想像してみてください。リアルタイム通知は、今日のペースの速いデジタル環境においてユーザーの関心を維持し、情報を伝えるための鍵です。リアルタイム通知は、エンゲージメント、満足度、定着率を高めます。印象に残るインタラクティブでダイナミックな体験にユーザーを夢中にさせましょう。

graphql サブスクリプションを使用して、graphql API にリアルタイムの更新を追加できます。この記事では、実装に焦点を当てます。 サブスクリプション リアルタイム通知の作成に役立つタイプ。

Graphql サブスクリプションとは何ですか?

サブスクリプションは、特定の場合にサーバーがクライアントにデータを送信できるようにする GraphQL の機能です。 出来事 起こる。サブスクリプションは GraphQL 契約の一部に過ぎず、以下を指します。 イベント。これらを送信できるようにするには イベント リアルタイムでは、それをサポートするトランスポートを選択する必要があります。
サーバーは通常 (クライアントによるポーリングの代わりに) サブスクリプションの更新をプッシュするため、通常は HTTP の代わりに WebSocket プロトコルを使用します。

graphql サーバーのセットアップ

サブスクリプション用にgraphqlサーバーを設定するには、Webソケットサーバーとgraphqlサーバーを実行するため、Expressミドルウェアを使用する必要があります。スタンドアロンサーバー設定を使用して graphql サーバーをセットアップする方法はすでにわかっています (こちらの記事へのリンクはこちら)。 graphql サーバーのセットアップ)。そのため、このセクションでは、Express Compatibility に必要な変更点についてのみ説明します。変更する必要があるのは、私たちの 1 つだけです。 server.ts ファイル。

GitHub のリンクは次のとおりです。 https://github.com/icon-gaurav/mastering-graphql-with-nodejs/tree/graphql-subscriptions

これを複製して、私の説明に従うことができます。

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { createServer } from 'http';
import express from 'express';
import { makeExecutableSchema } from '@graphql-tools/schema';
import cors from 'cors';
import resolvers from './graphql/resolvers'
import typeDefs from "./graphql/schema";
import mongoose from "mongoose";

async function startServer(){
    const app = express();

    const httpServer = createServer(app);

    const schema = makeExecutableSchema({
        resolvers,
        typeDefs,
    });

    const apolloServer = new ApolloServer({
        schema,
        introspection:true,
    });

    await apolloServer.start();

    app.use('/graphql', cors<cors.CorsRequest>(), express.json(), expressMiddleware(apolloServer));

    const DATABASE_URL: string = `mongodb://localhost:27017/test`;
    mongoose.connect(DATABASE_URL)
        .then(() => {
            console.log('Database connected successfully')
        })
        .catch((e: any) => {
            console.log('Error connecting : ', e?.message)
        })

    const PORT = 4000;
// Now that our HTTP server is fully set up, we can listen to it.
    httpServer.listen(PORT, () => {
        console.log(`Server is now running on http://localhost:${PORT}/graphql`);
    });
}

startServer();

サーバーを起動するには、次のコマンドを実行します。 npm ランスタート:開発

サーバーは、エクスプレスミドルウェアを使用してポート4000で起動します。

スキーマでサブスクリプションを定義

スキーマのサブスクリプションタイプは、ユーザー/クライアントがサブスクライブできる最上位フィールドを定義します。サブスクリプションタイプの定義は、Query and Mutation タイプのスキーマを定義するのと同じくらい簡単です。

# Subscription type definition in GraphQL Schema
type Subscription {
        postCreated: Post
    }

# Return type of the Subscription
type Post {
        _id: ID!
        title: String!
        content: String!
    }

ザの 投稿が作成されました フィールドは、バックエンドで新しい投稿が作成されるたびに更新され、このイベントを購読している新しく作成された投稿を持つすべてのクライアントに通知されます。

購読を有効にする

サブスクリプションを有効にするには、WebSocketサーバーを起動する必要があります。GraphQL WebSocketサーバーを起動する簡単な手順は次のとおりです

1。インストール グラフ-WSWS、および @graphql-ツール/スキーマ

 npm install --save graphql-ws ws @graphql-tools/schema

2。サブスクリプションサーバーとして使用する WebSocketServer インスタンスを作成する

 import { WebSocketServer } from 'ws';
 import { useServer } from 'graphql-ws/lib/use/ws';

 // Creating the WebSocket server
     const wsServer = new WebSocketServer({
         // This is the `httpServer` we created in a previous step.
         server: httpServer,
         // Pass a different path here if app.use
         // serves expressMiddleware at a different path
         path: '/subscriptions',
     });

3。WebSocketServer と HTTP サーバーをクリーンアップするプラグインを ApolloServer コンストラクターに追加すると、マシン上で稼働しているすべてのサーバーを適切にシャットダウンするのに役立ちます。


     const serverCleanup = useServer({ schema }, wsServer);

     const apolloServer = new ApolloServer({
         schema,
         introspection:true,
         plugins: [
             // Proper shutdown for the HTTP server.
             ApolloServerPluginDrainHttpServer({ httpServer }),

             // Proper shutdown for the WebSocket server.
             {
                 async serverWillStart() {
                     return {
                         async drainServer() {
                             await serverCleanup.dispose();
                         },
                     };
                 },
             },
         ],
     });

これが完全に構成されています server.ts ファイル。

import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { createServer } from 'http';
import express from 'express';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { WebSocketServer } from 'ws';
import cors from 'cors';
import { useServer } from 'graphql-ws/lib/use/ws';
import resolvers from './graphql/resolvers'
import typeDefs from "./graphql/schema";
import mongoose from "mongoose";

async function startServer(){
    const app = express();

    const httpServer = createServer(app);

    const schema = makeExecutableSchema({
        resolvers,
        typeDefs,
    });

    // Creating the WebSocket server
    const wsServer = new WebSocketServer({
        // This is the `httpServer` we created in a previous step.
        server: httpServer,
        // Pass a different path here if app.use
        // serves expressMiddleware at a different path
        path: '/subscriptions',
    });

// Hand in the schema we just created and have the
// WebSocketServer start listening.
    const serverCleanup = useServer({ schema }, wsServer);

    const apolloServer = new ApolloServer({
        schema,
        introspection:true,
        plugins: [
            // Proper shutdown for the HTTP server.
            ApolloServerPluginDrainHttpServer({ httpServer }),

            // Proper shutdown for the WebSocket server.
            {
                async serverWillStart() {
                    return {
                        async drainServer() {
                            await serverCleanup.dispose();
                        },
                    };
                },
            },
        ],
    });

    await apolloServer.start();

    app.use('/graphql', cors<cors.CorsRequest>(), express.json(), expressMiddleware(apolloServer));

    const DATABASE_URL: string = `mongodb://localhost:27017/test`
    mongoose.connect(DATABASE_URL)
        .then(() => {
            console.log('Database connected successfully')
        })
        .catch((e: any) => {
            console.log('Error connecting : ', e?.message)
        })

    const PORT = 4000;
// Now that our HTTP server is fully set up, we can listen to it.
    httpServer.listen(PORT, () => {
        console.log(`Server is now running on http://localhost:${PORT}/graphql`);
    });
}

startServer();

サブスクリプション用のリゾルバーを実装する

サブスクリプションフィールドリゾルバーは、以下を定義するオブジェクトであるため、サブスクリプションのリゾルバーはミューテーションタイプやクエリタイプとは異なります。 申し込む 関数。リゾルバーを実装しましょう 投稿が作成されました サブスクリプション。サブスクリプションタイプのリゾルバーを定義する方法は次のとおりです。

Subscription:{
        postCreated: {
            subscribe: () => pubsub.asyncIterator(['POST_CREATED']),
        },
    }
  1. ザの 申し込む 関数 しなければならない 型のオブジェクトを返す 非同期イテレーター、非同期結果を反復処理するための標準インターフェースです。
  2. 非同期イテレーター によって生成されます PubSub.Async イテレーター
  3. PubSub.Async イテレーター オブジェクトはラベルに関連付けられているイベントを監視し、それらを処理するキューに追加します

イベントを公開

このステップでは、リアルタイム通知が必要なイベントを公開します。これは通常、新しいデータベースエントリや管理者側からの更新があった場合に発生します。

この例では、公開します 「投稿作成済み」 を使用して新しい投稿が作成されるたびに発生するイベント 投稿を作成 突然変異。

createPost: async (_parent: any, args: any, _context: any) => {
            // create new post document
            const post = new Post(args);
            //save post document and return the saved document
            await post.save();
            await pubsub.publish('POST_CREATED', {postCreated: post});
            return post;
        }

pubsub.publish 関数は、このイベントのペイロード (この場合は投稿データ) と共にイベントの公開を行います。

必要なすべてのリゾルバーの完全な例を次に示します。

import {posts, users, users_posts} from "../utils/data";
import Post from "../models/Post";
import User from "../models/User";
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();


const resolvers = {
    Query: {
        posts: async (_parent: any, _args: any, context: any) => {
            return Post.find();
        },
    },
    Mutation: {
        // Mutation that will create a new post and save in DB
        createPost: async (_parent: any, args: any, _context: any) => {
            // create new post document
            const post = new Post(args);
            //save post document and return the saved document
            await post.save();
            // publish the "POST_CREATED" event
            await pubsub.publish('POST_CREATED', {postCreated: post});
            return post;
        }
    },
    Subscription:{
        postCreated: {
            // resolving subscribe function
            subscribe: () => pubsub.asyncIterator(['POST_CREATED']),
        },
    }
}

export default resolvers;

これでサーバーの準備が整いました。サブスクリプションをテストできます。

当社の API をテストしてください

を使用してサーバーを実行する npm ランスタート:開発 コマンド

これがこのサーバーの出力です。あるウィンドウでは投稿の作成方法を定義し、別のウィンドウではサブスクリプションを聞いて、そこで更新されたデータを確認します。同様に、このシステムを使用して、認証、承認、フィルタリングを含む本格的な通知システムを構築できます。

GraphQL サブスクリプションは、Web アプリケーションのリアルタイムデータ更新のための強力なソリューションを提供します。サブスクリプションを活用することで、開発者は絶え間ないポーリングを必要とせずに即時に更新を提供できるため、ユーザーエクスペリエンスを向上させることができます。GraphQL サブスクリプションの実装はとても楽しく、私たちも楽しかったと思います。

GitHub のリンクは次のとおりです。 https://github.com/icon-gaurav/mastering-graphql-with-nodejs/tree/graphql-subscriptions

注記: PubSub クラスはインメモリイベントパブリッシングシステムを使用するため、本番環境での使用はお勧めしません。代わりに、ユースケースに応じて外部データストアベースのライブラリを使用できます。

ライブラリリストと詳細情報はこちらで確認できます。 https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries