GraphQL Subscriptionsのお手軽お試しコード

GraphQL SubscriptionsというGraphQLでSocket.ioのようにリアルタイム通知を受ける仕組みがあります。リンク先はApollo社の記事だとサンプルコード全体がないので、どのような実装になるかサンプルを作ってみました。

サンプルコードの内容

先日投稿したサンプルコードの拡張版になっています。会社と社員のデータモデルがあって、サンプルでは会社のIDが3の社員データをサブスクライブしています。動作させるとID3の会社に社員を作成した時のみ下記コード中のcallbackが呼び出されているのがわかると思います。

subscriptionManager.subscribe({
  query: `
subscription employees($company_id: ID!) {
  employeeAdded(company_id: $company_id) {
    id
    company_id
    name
  }
}
  `,
  variables: {
    company_id: 3,
  },
  context: {},
  callback: (err, data) => console.log('SUBSCRIPTION CALLBACK', JSON.stringify(data, null, 2)),
});

サンプルコード

事前に下記のNPMのインストールが必要です。

$ npm install graphql graphql-tools graphql-subscriptions

node hoge.jsとすれば動くと思います。

const graphql = require('graphql');
const tools = require('graphql-tools');
const {PubSub, SubscriptionManager} = require('graphql-subscriptions');

const companies = [
  {
    id: 1,
    name: 'A company'
  },
  {
    id: 2,
    name: "B company"
  }
];

const employees = [
  {
    id: 1,
    company_id: 1,
    name: 'Mr. A',
  },
  {
    id: 2,
    company_id: 1,
    name: 'Mr. B',
  },
  {
    id: 3,
    company_id: 2,
    name: 'Mr. C',
  }
];

const pubsub = new PubSub();

const typeDefs = `
  type Company {
    id: ID!
    name: String
    employees: [Employee]
    sales: Int
  }
  
  type Employee {
    id: ID!
    company_id: ID!
    name: String
    company: Company
  }
  
  type Query {
    getCompanies: [Company]
    getCompany (id: ID!): Company
    getEmployees (company_id: ID!): [Employee]
  }
  
  input CompanyInput {
    name: String!
  }
  
  input EmployeeInput {
    company_id: ID!
    name: String!
  }
  
  type Mutation {
    addCompany (company: CompanyInput): Company
    addEmployee (employee: EmployeeInput): Employee
  }
  type Subscription {
    employeeAdded(company_id: ID!): Employee
  }
`;


const resolvers = {
  Company: {
    sales: (company, args, context) => {
      return Math.random() * 100
    },
    employees: (company, args, context) => {
      return employees.filter(e => +e.company_id === +company.id);
    }
  },
  Employee: {
    company: (employee, args, context) => {
      return companies.find(c => +c.id == +employee.company_id);
    }
  },
  Query: {
    getCompanies(container, args, ctx, info) {
      if (args && args.id) {
        return companies.filter(c => +args.id === +c.id);
      }
      return companies;
    },
    getCompany(container, args, ctx, info) {
      return companies.find(c => +args.id === +c.id);
    },
    getEmployees(container, args, ctx, info) {
      return employees.filter(e => +e.company_id === +args.company_id);
    }
  },
  Mutation: {
    addCompany (mutation, args) {
      const id = companies.length + 1;
      companies.push({
        id,
        name: args.company.name,
      });
      return companies.find(c => +c.id === +id);
    },
    addEmployee (mutation, args) {
      const id = employees.length + 1;
      const employee = {
        id,
        company_id: args.employee.company_id,
        name: args.employee.name,
      };
      employees.push(employee);
      pubsub.publish('newEmployeesChannel', employee);
      return employees.find(e => +e.id === +id);
    }
  },
  Subscription: {
    employeeAdded(employee) {
      return employee;
    },
  },
};

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

const subscriptionManager = new SubscriptionManager({
  schema,
  pubsub,
  setupFunctions: {
    employeeAdded: (options, args) => {
      return {
        newEmployeesChannel: {
          filter: (employee) => {
            return +employee.company_id === +args.company_id;
          }
        },
      };
    },
  },
});


subscriptionManager.subscribe({
  query: `
subscription employees($company_id: ID!) {
  employeeAdded(company_id: $company_id) {
    id
    company_id
    name
  }
}
  `,
  variables: {
    company_id: 3,
  },
  context: {},
  callback: (err, data) => console.log('SUBSCRIPTION CALLBACK', JSON.stringify(data, null, 2)),
});

const gql = `
mutation example($employee: EmployeeInput!) {
  employee: addEmployee(employee: $employee) {
    id
    name
  }
}
`;


let i = 1;
setInterval(() => {
  graphql.graphql(schema, gql, null, null, {employee: {company_id: 3, name: `new Employee (${i++})`}}).then(result => {
    // console.log('1, MUTATION', JSON.stringify(result, null, 2));
  });
}, 1000);

setInterval(() => {
  graphql.graphql(schema, gql, null, null, {employee: {company_id: 1, name: `new Employee (${i++})`}}).then(result => {
    // console.log('1, MUTATION', JSON.stringify(result, null, 2));
  });
}, 1000);

その他

情報が断片的だったのでやや苦労したのですが、下記のvue-apolloのサンプルが参考になりました。