はじめに
GraphQL について 一通り学んだ ところで、実際のユースケースをより深く理解すべく、簡単な ToDo アプリを作りたいと思います。今回はデータストアとして DynamoDB テーブルを利用し、具体的には以下のデータを格納します。
フィールド | 説明 |
---|---|
id | ToDo エントリの ID |
title | タイトル |
description | 詳細 |
createDate | 作成日 |
これらの情報を参照/作成/更新/削除できるような Web アプリの制作を行い、GraphQL への理解をより深めることを目的とします。なお、本エントリはバックエンド、つまり GraphQL サーバー側にフォーカスしています。
プロジェクトディレクトリの作成
まずはプロジェクトディレクトリを作成します。今回は backend/
に GraphQL サーバー、frontend/
にフロントのアプリを配置するような構成を取ります。
$ mkdir todo-app
$ mkdir backend frontend
環境構築
それでは実際にバックエンドの開発に取り掛かります。
まずは必要なライブラリのインストールからです。なお aws-sdk
は DynamoDB テーブルに対する操作、uuid
については id の生成に利用します。
$ cd backend frontend
$ npm init -y
$ npm install graphql apollo-server aws-sdk uuid
次に必要なディレクトリ、ファイルを作成してきます。
$ mkdir resolver model
$ touch index.js schema.js model/todo.js resolver/{Query.js,Mutation.js}
以上で完了です。backend/
配下は以下のようなディレクトリ構造となります。
$ tree -L 2 -I node_modules
.
├── index.js
├── model
│ └── todo.js
├── package-lock.json
├── package.json
├── resolver
│ ├── Mutation.js
│ └── Query.js
└── schema.js
2 directories, 7 files
スキーマの定義
スキーマの定義を行います。今回は ToDo の CRUD 処理ができることを目的とするため、それに沿ったスキーマを書いていきます。なお、参照については LIST のみの実装とします。
const {gql} = require("apollo-server")
const typeDefs = gql`
type Query {
listTodos: [Todo!]!
}
type Mutation {
createTodo(data: CreateTodoInput!): Todo!
updateTodo(id: ID!, data: UpdateTodoInput!): Todo!
deleteTodo(id: ID!): Todo!
}
input CreateTodoInput {
title: String!
description: String!
createDate: String!
}
input UpdateTodoInput {
title: String!
description: String!
createDate: String!
}
type Todo {
id: ID!
title: String!
description: String!
createDate: String!
}
`;
module.exports = typeDefs;
モデルの作成
以下のように Todo
モデルを作成し、DynamoDB への操作はクラスのメソッドとして行うことにします。
const AWS = require("aws-sdk");
AWS.config.update({
region: "us-west-2"
});
const docClient = new AWS.DynamoDB.DocumentClient();
const tableName = "GraphQLToDoAppTable";
class Todo {
constructor(id, title, description, createDate) {
this.id = id;
this.title = title;
this.description = description;
this.createDate = createDate;
}
static async listTodos() {
let todoList = [];
let params = { TableName: tableName }
while (true) {
const result = await docClient.scan(params).promise();
if (result.Items) {
result.Items.forEach((item) => {
const {id, title, description, createDate} = item;
todoList.push(new Todo(id, title, description, createDate));
});
}
if (result.LastEvaluatedKey) {
params.ExclusiveStartKey = result.LastEvaluatedKey;
} else {
break;
}
}
return todoList;
}
static async deleteTodo(id) {
const params = {
TableName: tableName,
Key: {
"id": id
},
ReturnValues: "ALL_OLD"
};
const response = await docClient.delete(params).promise();
return response.Attributes;
}
async createTodo() {
const params = {
TableName: tableName,
Item: {
id: this.id,
title: this.title,
description: this.description,
createDate: this.createDate
}
};
await docClient.put(params).promise();
}
async updateTodo() {
const params = {
TableName: tableName,
Key: {
"id": this.id
},
UpdateExpression: "set title = :title, description = :desc, createDate = :cd",
ExpressionAttributeValues: {
":title": this.title,
":desc": this.description,
":cd": this.createDate
}
};
const response = await docClient.update(params).promise();
}
}
module.exports = Todo;
リゾルバの作成
次に以下のようにリゾルバを作成します。Query 及び Mutation のリゾルバはそれぞれ resolver/Query.js
及び resolver/Mutation.js
に配置します。
const Todo = require("../model/todo");
const Query = {
async listTodos(parent, args, info) {
const todos = Todo.listTodos();
return todos;
}
}
module.exports = Query;
const uuid = require('uuid');
const Todo = require("../model/todo");
const Mutation = {
async createTodo(parent, args, info) {
const {title, description, createDate} = args.data;
const id = uuid.v4();
const todo = new Todo(id, title, description, createDate);
await todo.createTodo();
return todo;
},
async updateTodo(parent, args, info) {
const id = args.id;
const {title, description, createDate} = args.data;
const todo = new Todo(id, title, description, createDate);
await todo.updateTodo();
return todo;
},
async deleteTodo(parent, args, info) {
const id = args.id;
const todo = await Todo.deleteTodo(id);
return new Todo(todo.id, todo.title, todo.description, todo.createDate);
}
}
module.exports = Mutation;
Apollo サーバーの起動
後は Apollo サーバーを起動するための処理を書いて完了です。
const {ApolloServer} = require("apollo-server");
const Query = require("./resolver/Query");
const Mutation = require("./resolver/Mutation");
const typeDefs = require("./schema");
const server = new ApolloServer({
typeDefs: typeDefs,
resolvers: {
Query,
Mutation
}
});
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});
動作の確認
ここまでできたらサーバーを起動し、GraphQL Playground で動作確認を行います。
$ node index.js
Server ready at http://localhost:4000/
まずは ToDo の作成から。
実際に DynamoDB テーブルにもデータが作成されていることがわかります。
次に、上記 ToDo の description を更新してみましょう。
こちらも DynamoDB テーブルから更新が確認できます。
それでは削除してみます。
削除されたかどうか、今度は listTodos
で確認してみましょう。
ちゃんと削除されていそうですね。なお、実際に ToDo が存在する場合は以下のように ToDo リストを返してくれます。
おわりに
如何でしたでしょうか。個人的には実際に IDE 環境を使って動作確認が行えるため、開発がスムーズに行えた気がしています。
次回は上記フロントエンド UI を作っていこうと思います。