Graphpack

なにこれ

「とりあえずクライアント側と同じJavaScriptで手っ取り早くGraphQLサーバー立てたい!」

このようなユースケースにGraphpackはピッタリです。 設定いらずのNode製GraphQLサーバーで 「GraphQLのスキーマとリゾルバーを定義するだけでOK」、さらに GraphQL Playground IDEが標準搭載」なのでクライアント側を自前で実装せずとも動作確認できます。
今回は、このGraphpackの使い方について以下の5ステップでご紹介します。
※ここで紹介するソースコードはGitHub(Takumon/nuxt-graphpack-sample)にもあるので参考にしてみてください。

1. Graphpackとは

Node.js製のゼロコンフィグなミニマルGraphQLサーバーでWebpackNodemonApollo Server をイイ感じにまとめたライブラリです。感触を掴むだけならCodeSandboxのお試し環境が用意されているので、そちらを触ってみるとよいでしょう。 READMEでは以下8つの特徴をうたっています。

  • 📦 設定いらずZERO-CONFIG)!
  • 🚦 ライブリロード機能組み込み済!
  • 🚨 わかりやすいエラーメッセージ!
  • 🎮 GraphQL Playground IDE 標準装備!
  • ⭐️ SDLでスキーマ定義可能(GraphQL imports
  • 💖 TypeScriptをサポート
  • 🔥 爆速ビルド
  • ⚡️ ES module importsとdynamic importをサポート

2. Graphpackを使ってみる

実装してみる

  • プロジェクト雛形を作成し、graphpack を開発環境用ライブラリとしてインストールします。
mkdir graphpack-sample
cd graphpack-sample
npm init
npm i -D graphpack

  • src/schema.graphqlsrc/resolver.jsを作成します。
プロジェクト構成
graphpack-sample
└── src
     ├── resolvers.js
     └── schema.graphql

src/scheme.graphql
type Query {
  hello: String
}

src/resolvers.graphql
const resolvers = {
  Query: {
    hello: () => 'world!',
  },
};

export default resolvers;

  • package.jsonに以下のスクリプトを追記します。
package.jsonの一部
  "scripts": {
    "dev": "graphpack",
    "build": "graphpack build"
  },

動作確認してみる

  • サーバーをnpm run devで起動して、ブラウザで http://localhost:4000/ を開くとGraphQL Playground IDEが表示されます。

hello-world

  • 試しに以下のQueryを実行してみましょう。
query {
  hello
}

  • world!がレスポンスとして返ってきます。

hello-world-result

こんな感じで、とても簡単にGraphQLサーバーが立てられます。

3. GraphpackでQueryを実装・動作確認する

ユーザー情報(ID、名前、メール、年齢)を扱う処理を例に実装方法を説明します。

実装

  • ユーザー情報のスキーマ定義します。
src/schema.graphql
type Query {
  users: [User!]!
  user(id: ID!): User!
}

type User {
  id: ID!
  name: String!
  email: String!
  age: Int
}

  • 仮のユーザー情報を用意しましょう。
src/db.js
export let users = [
  {
    id: 1,
    name: 'gatsby taro',
    email: 'gatsby.taro@gmail.com',
    age: 32
  },
  {
    id: 2,
    name: 'gridsome taro',
    email: 'gridsom.taro@gmail.com',
    age: 55
  },
];

  • 最後にリゾルバーを定義します。データは前手順で作成したものを使います。
src/resolvers.js
import { users } from './db';

const resolvers = {
  Query: {
    // 1件検索
    user: (parent, { id }, context, info) => users.find(user => user.id == id),
    // 複数件検索(簡単のため全件検索としている)
    users: (parent, args, context, info) => users
  },
};

動作確認

  • 実装できたらnpm run devでサーバー起動して http://localhost:4000/ を開いて 以下のクエリを発行します。するとユーザー一覧が取得できます。
ユーザー一覧取得用Query
query {
  users {
    id
    name
    email
    age
  }
}

get-users

  • 1件検索の場合は以下のようなクエリを発行しましょう。指定したIDのユーザー情報が取得できます。
ユーザー情報1件検索用Query
query {
  user(id: 1) {
    id
    name
    email
    age
  }
}

get-user

4. GraphpackでMutationを実装・動作確認する

ユーザー情報が取得できたので、次はユーザー情報の登録・更新・削除を実装します。

実装

スキーマ定義にMutationを追記します。

src/schema.graphqlの一部
type Mutation {
  // 登録
  createUser(
    name: String!,
    email: String!,
    age: Int
  ): User!
  
  // 更新
  updateUser(
    id: ID!,
    name: String!,
    email: String,
    age: Int
  ): User!
  
  // 削除
  deleteUser(
    id: ID!
  ): User!
}

これはGraphQLとは関係ありませんが、ユーザー情報登録時のID採番用ロジックをdb.jsに追記します。 初期状態でユーザー情報が2件なので、採番用IDは3から始まるようにします。

src/db.js
export let users = [
  {
    id: 1,
    name: 'gatsby taro',
    email: 'gatsby.taro@gmail.com',
    age: 32
  },
  {
    id: 2,
    name: 'gridsome taro',
    email: 'gridsome.taro@gmail.com',
    age: 55
  },
];

// 採番用ID(3から始まるようにする)
let idSequence = 2;
// 採番処理
export const generateId = () => ++idSequence;

リゾルバーにMutationを追記します。

src/resolvers.jsの一部
import { users, generateId } from './db';

const resolvers = {
  Query: {
    // ・・・
  },
  Mutation: {
    createUser: (parent, { name, email, age }, context, info) => {
      const newUser = { id: generateId(), name, email, age};
      users.push(newUser);
      return newUser;
    },
    updateUser: (parent, { id, name, email, age }, context, info) => {
      const updatedUser = users.find(user => user.id == id);
      updatedUser.name = name;
      updatedUser.email = email;
      updatedUser.age = age;
      return updatedUser;
    },
    deleteUser: (parent, { id }, context, info) => {
      const userIndex = users.findIndex(user => user.id == id);
      if (userIndex === -1) throw new Error('User not found');
      const [deletedUser] = users.splice(userIndex, 1);
      return deletedUser;
    }
  },
  // ・・・
};

動作確認

実装できたらnpm run devでサーバー起動して http://localhost:4000/ を開きます。

登録の動作確認

以下のようなMutationを発行すると、登録したユーザー情報が返ってきます。
※登録後にユーザー一覧を取得すると、nuxt taroが取得できます。

登録用Muatation
mutation {
  createUser(
    name: "nuxt taro"
    email: "nuxt.taro@gmail.com"
    age: 43
  ) {
    id
    name
    email
    age
  }
}

create-user

更新の動作確認

今度はnuxt taroを更新してみましょう。 以下のようなMutationを発行すると、更新されたユーザー情報が返ってきます。
※更新後にユーザー一覧を取得するとnuxt taronuxt updatedtaroになっていることを確認できます。

更新用Muatation
mutation {
  updateUser(
    id: 3
    name: "nuxt updatedtaro"
    email: "nuxt.updatedtaro@gmail.com"
    age: 44
  ) {
    id
    name
    email
    age
  }
}

update-user

削除の動作確認

最後にnuxt updatedtaroを削除してみましょう。 以下のようなMutationを発行すると削除されたユーザー情報が返ってきます。
※削除後にユーザー一覧を取得するとnuxt updatedtaroがなくなっていることが確認できます。

削除用Mutation
mutation {
  deleteUser(
    id: 3
  ) {
    id
    name
    email
    age
  }
}

delete-user

5. GraphpackでSubscriptionを実装・動作確認する

実装

GraphpackではSubscriptionはデフォルトoffになっています。 そのためココだけは設定ファイルを作成する必要があります。 プロジェクトのルートフォルダ直下にgraphpack.config.jsを作成しましょう。

graphpack.config.js
module.exports = {
  server: {
    subscriptions: {
      // Subscriptionのエンドポイントとして
      // QueryとMutationと同じパスを設定します。
      path: `/graphql`
    }
  },
};

スキーマ定義にSubscriptionを追記します。

src/schema.graphqlの一部
type Subscription {
    userCreated: User!
    userUpdated: User!
    userDeleted: User!
}

リゾルバーでSubscriptionを定義し、Mutationも修正します。 Mutationも修正するのは、登録・更新・削除完了後にSubscriptionを発行できるようにするためです。

src/resolvers.jsの一部
// Subscriptionのやりとりには`apollo-server`のPubSubを使る
// `graphpack`の場合、`apollo-server`は入っている
import { PubSub } from 'apollo-server';
import { users, generateId } from './db';
const pubsub = new PubSub();

const EVENT = {
    USER_CRAETED: 'userCreated',
    USER_UPDATED: 'userUpdated',
    USER_DELETED: 'userDeleted',
};

const resolvers = {    
  // ・・・
  Mutation: {
    createUser: (parent, { name, email, age }, context, info) => {
      const newUser = { id: generateId(), name, email, age};
      users.push(newUser);
            
      pubsub.publish(EVENT.USER_CRAETED, {[EVENT.USER_CRAETED]: newUser});
      return newUser;
    },
    updateUser: (parent, { id, name, email, age }, context, info) => {
      const updatedUser = users.find(user => user.id == id);
      updatedUser.name = name;
      updatedUser.email = email;
      updatedUser.age = age;

      pubsub.publish(EVENT.USER_UPDATED, {[EVENT.USER_UPDATED]: updatedUser});
      return updatedUser;
    },
    deleteUser: (parent, { id }, context, info) => {
      const userIndex = users.findIndex(user => user.id == id);
      
      if (userIndex === -1) throw new Error('User not found');
      const [deletedUser] = users.splice(userIndex, 1);
      
      pubsub.publish(EVENT.USER_DELETED, {[EVENT.USER_DELETED]: deletedUser});
      return deletedUser;
    }
  },
  Subscription: {
    [EVENT.USER_CRAETED]: {
      subscribe: () => pubsub.asyncIterator([EVENT.USER_CRAETED])
    },
    [EVENT.USER_UPDATED]: {
      subscribe: () => pubsub.asyncIterator([EVENT.USER_UPDATED])
    },
    [EVENT.USER_DELETED]: {
      subscribe: () => pubsub.asyncIterator([EVENT.USER_DELETED])
    },
  },
  // ・・・
};

動作確認

実装できたらnpm run devでサーバー起動して http://localhost:4000/ を開きます。

userCreatedの動作確認をしましょう。 以下のようなSubscriptionを発行します。

subscription {
  userCreated {
    id
    name
    email
    age    
  }
}

すると実行結果(画面右半分)にローディングアイコンが表示され、監視状態となります。

subscription-1

この状態で別タブを開き...

subscription-2

登録用のMutaitonを発行してみましょう。

subscription-3

これで、元のタブに戻ると、登録された情報がレスポンスで返ってきていれば成功です。

subscription-4


以上でSubscriptionの動作確認は終了です。 今回は紹介しませんが、userUpdateduserDeletedも同様の方法で動作確認できるのでやってみてください。

まとめ

今回はNode製GraphQLサーバー「Graphpack」の使い方について紹介しました。 上記で紹介した通り非常に簡単ですので、「とりあえずGraphQLサーバーを立ててみたい!」という方はGraphpackを検討してみてはいかがでしょうか🍅

参考