Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8837

Node.js+MongoDB構成でGraphQLのお勉強

$
0
0

はじめに

現場でGraphQLを使用しているのですが、保守改修段階でプロジェクトに入ったのでスキーマ作成などの基本的なところをやったことがありませんでした。また、NoSQLデータベースも使ったことがなかったので、まとめて学んでみようということでNode.js+MongoDBの構成でGraphQLサーバをたててみることにしました。

今回作成したプロジェクトはGitHubから確認できます。

GraphQLのよさ

RESTとの比較記事がいたるところにあるので(例えばこちら)詳しく書きませんが、単一のエンドポイントであるというのがGraphQLの一番の特徴です。

GraphQLをつかうことで、RESTで必要なすべてのデータを取得しようとするときに発生する以下のような問題を解決することができます。

  • 複数のエンドポイントへリクエストを行う必要がある
  • 不要なデータも一緒に取得されてしまう

サーバ構築

適当なフォルダ(graphql-server-practice)を作成してyarn initします。そのあと、以下の構成でフォルダおよびファイルを作成します。

スクリーンショット 2021-02-24 20.50.27.png

Schema, Query, MutationをSchema.jsに記載していきます。

Schemaの作成

今回は練習のため、User, Hobby, Postの3つのSchemaをつくります。
Userはそれぞれ複数のHobbyやPostをもてるような関係になっています(One to Many relationship)。

まずはyarn add graphqlでgraphqlパッケージを導入し、schema.jsで以下を読み込みます。

schema.js
constgraphql=require('graphql');const{GraphQLObjectType,GraphQLID,GraphQLString,GraphQLInt,GraphQLSchema,GraphQLNonNull,GraphQLList,}=graphql;

UserのSchemaをUserTypeとして作成します。GraphQLObjectTypeでラッピングすることでSchemaのname, description, fieldsを定義することができます。

fieldsのid, name, age, professionをGraphQLID, GraphQLString, GraphQLInt, GraphQLStringというスカラー型で定義します。

schema.js
constUserType=newGraphQLObjectType({name:'User',description:'Documentation for user...',fields:()=>({id:{type:GraphQLID},name:{type:GraphQLString},age:{type:GraphQLInt},profession:{type:GraphQLString},}),});

HobbyTypeとPostTypeについても同様に作成します。

schema.js
constHobbyType=newGraphQLObjectType({name:'Hobby',description:'Hobby description',fields:()=>({id:{type:GraphQLID},title:{type:GraphQLString},description:{type:GraphQLString},}),});constPostType=newGraphQLObjectType({name:'Post',description:'Post description',fields:()=>({id:{type:GraphQLID},comment:{type:GraphQLString},}),});

3つのSchemaを作成しましたが、この状態ではまだUserとPost、UserとHobbyの関係が定義されていないので、これらのリレーションを考えてあげる必要があります。

UserとPostの例を考えてみます。

constUserType=newGraphQLObjectType({name:'User',description:'Documentation for user...',fields:()=>({id:{type:GraphQLID},name:{type:GraphQLString},age:{type:GraphQLInt},profession:{type:GraphQLString},posts:{type:newGraphQLList(PostType),resolve(parent,args){returnpostsData.filter((data)=>data.userId===parent.id);},},}),});constPostType=newGraphQLObjectType({name:'Post',description:'Post description',fields:()=>({id:{type:GraphQLID},comment:{type:GraphQLString},user:{type:UserType,resolve(parent,args){returnusersData.find((data)=>data.id===parent.userId);},},}),});

User1つに対してPostは複数存在します。そのため、UserTypeのfieldsに新しく作られたpostsはPostTypeの配列型となり、new GraphQLList(PostType)と定義されます。

posts:{type:newGraphQLList(PostType),resolve(parent,args){returnpostsData.filter((data)=>data.userId===parent.id);},},

また、resolveはどのUserに対するPostを表示するのかを定義するものです。parentは親のfields(ここではUserType)を指しており、以上の処理ではUserのidと等しいuserIdをもったPostのデータのみを取得するようになっています。

constusersData=[{id:'1',name:'山田勝己',age:36,profession:'SASUKE'},];constpostsData=[{id:'1',comment:'僕にはSASUKEしかないんです',userId:'1'},{id:'2',comment:'完全制覇がしたいんです',userId:'1'},];

Queryの作成

QueryはSchemaと同様に、GraphQLObjectTypeで定義を行います。試しに指定したidのUserを取得するuserクエリとすべてのUserを取得するusersクエリを作成してみます。

constRootQuery=newGraphQLObjectType({name:'RootQueryType',description:'Description',fields:{user:{type:UserType,args:{id:{type:GraphQLID}},resolve(parent,args){returnusersData.find((data)=>data.id===args.id);},},users:{type:newGraphQLList(UserType),resolve(parent,args){returnusersData;},},},});

userクエリではidを引数(args)としてとるので、fieldsでargsの型定義を行っています。resolveではargsのidと同じデータだけ取得するような処理を書いています。

一方、usersクエリでは、すべてのデータを取得するだけなのでargsは必要ありません。

ローカルサーバをたててクエリの動作確認

Mutation作成とDB接続の前に、ローカルサーバをたててQueryの挙動を確認します。

作成したRootQueryをnew GraphQLSchemaでラッピングしてエクスポートします。

schema.js
module.exports=newGraphQLSchema({query:RootQuery});

yarn add express express-graphqlで必要なパッケージを導入し、以下の設定を行います。

app.js
constexpress=require('express');const{graphqlHTTP}=require('express-graphql');constschema=require('./schema/schema');constapp=express();app.use('/graphql',graphqlHTTP({graphiql:true,schema,}));app.listen(4000,()=>{console.log('Listening for requests on my awesome port 4000');});

node appで4000ポートにサーバが立ち上がるのですが、ソースコードの修正をリアルタイムで反映させるためにyarn global add nodemonnodemonを導入します。

nodemon appでサーバを立ち上げ、http://localhost:4000/graphql を開くと以下の画面が現れます。

スクリーンショット 2021-02-25 8.04.42.png

userクエリを試しに実行すると右側に取得データが表示されます。postsのデータも問題なく表示されています。
スクリーンショット 2021-02-25 8.10.45.png

MongoDBとの接続

DBと接続してMutationを実装します。

MongoDB Atlasの設定

MongoDB Atlasのアカウントを作成します。Googleアカウントがあれば大丈夫です。
スクリーンショット 2021-02-25 21.26.37.png

適当な名前でProjectsを新規で作成します。
スクリーンショット 2021-02-25 21.29.32.png

Projects内でClusterを作成します。今回、クラウドにはAWSを使用し、DB性能に関わるCluster Tierには無料のMO Sandboxを使用します。
スクリーンショット 2021-02-25 21.31.25.png

作成したClusterのCONNECTボタンを押して、"Connect using MongoDB Compass"を選択します。CompassはMongoDB用のGUIツールです。
スクリーンショット 2021-02-25 21.41.01.png

Compassをダウンロードし、DB接続用のコードをコピーします。
スクリーンショット 2021-02-25 21.43.06.png

Node.jsの設定

GraphQLとMongoDBと連携するために、Node.jsのmongooseというパッケージを使用します(yarn add mongoose)。
app.jsファイルを以下のようにします。mongoose.connectでMongoDBとの接続、mongoose.connection.onceで接続が成功したことを確認するためのコンソールログを行っています。

app.js
constexpress=require('express');const{graphqlHTTP}=require('express-graphql');constmongoose=require('mongoose');constschema=require('./schema/schema');constapp=express();mongoose.connect('mongodb+srv://dbUser:<password>@cluster0.gjo5x.mongodb.net/test',// <password>には自分で設定したものを入力{useNewUrlParser:true});mongoose.connection.once('open',()=>{console.log('we are connected.');});app.use('/graphql',graphqlHTTP({graphiql:true,schema,}));app.listen(4000,()=>{console.log('Listening for requests on my awesome port 4000');});

Modelの作成

DBのSchemaにあたるModelを作成していきます。GraphQLのSchemaとModelを関連付けることで、DBからデータを取得(Query)したり、登録・削除(Mutation)などを行うことができます。
modelフォルダ以下に新しいファイルを作成します。
スクリーンショット 2021-02-25 22.03.45.png

UserのModelは以下のようになります。userSchemaは後ほどschema.jsで読み込むので、最後にエクスポートします。

user.js
constmongoose=require('mongoose');constMSchema=mongoose.Schema;constuserSchema=newMSchema({name:String,age:Number,profession:String,});module.exports=mongoose.model('User',userSchema);

Mutationの作成

Userデータの作成(CreateUser)、更新(UpdateUser)、削除(RemoveUser)のMutationsを作成します。

schema.js
constgraphql=require('graphql');constUser=require('../model/user');~中略~constMutation=newGraphQLObjectType({name:'Mutation',fields:{CreateUser:{type:UserType,args:{name:{type:newGraphQLNonNull(GraphQLString)},age:{type:newGraphQLNonNull(GraphQLInt)},profession:{type:GraphQLString},},resolve(parent,args){letuser=newUser({name:args.name,age:args.age,profession:args.profession,});returnuser.save();},},UpdateUser:{type:UserType,args:{id:{type:newGraphQLNonNull(GraphQLString)},name:{type:newGraphQLNonNull(GraphQLString)},age:{type:GraphQLInt},profession:{type:GraphQLString},},resolve(parent,args){return(updatedUser=User.findByIdAndUpdate(args.id,{$set:{name:args.name,age:args.age,profession:args.profession,},},{new:true}));},},RemoveUser:{type:UserType,args:{id:{type:newGraphQLNonNull(GraphQLString)},},resolve(parent,args){letremovedUser=User.findByIdAndRemove(args.id).exec();if(!removedUser){thrownew'Error'();}returnremovedUser;},},}~中略~module.exports=newGraphQLSchema({query:RootQuery,mutation:Mutation,});

resolve内でUserのModelを読み込み、データ登録用のメソッド(save)や更新用のメソッド(findByIdAndUpdate)を使用します。これらのメソッドについては、mongooseの公式Docsに使い方の詳細な説明が記載されています。

また、更新や削除では処理を行うデータを指定するため、argsのidはNon-nullとなります。Non-nullにしたいカラムについては、GraphQLNonNullをラッピングします。

最後にMutationのエクスポートも忘れずに行います。

Mutationの実行

localhost:4000/graphqlを開き、CreateUserを試しに実行してみます。
スクリーンショット 2021-02-25 22.47.16.png

MongoDB Compassでデータが登録されたことを確認することができました。
スクリーンショット 2021-02-25 22.50.31.png

おわりに

今回のサーバ構築作業には結構時間がかかったのですが、AWS AppSyncとAmplifyをつかったら一瞬で構築できました。なかなか衝撃的な体験だったので、別記事で書こうと思います。
また、PostやHobbyのModelやMutationなど、記載を省略した部分についてご興味がありましたら、GitHubをご確認いただければと思います。


Viewing all articles
Browse latest Browse all 8837

Trending Articles