├── .gitignore ├── services ├── PostService │ ├── .env │ ├── .babelrc │ ├── src │ │ ├── Post │ │ │ ├── mutationresolvers │ │ │ │ ├── index.js │ │ │ │ ├── updatePost.js │ │ │ │ └── createPost.js │ │ │ ├── queryResolvers │ │ │ │ ├── index.js │ │ │ │ ├── fetchPostById.js │ │ │ │ └── fetchPostByUserId.js │ │ │ ├── index.js │ │ │ ├── Types.js │ │ │ └── Model.js │ │ └── index.js │ └── package.json ├── UserService │ ├── .env │ ├── .babelrc │ ├── src │ │ ├── User │ │ │ ├── mutationResolvers │ │ │ │ ├── index.js │ │ │ │ └── createUser.js │ │ │ ├── queryResolvers │ │ │ │ ├── index.js │ │ │ │ ├── fetchAllUser.js │ │ │ │ └── fetchUserById.js │ │ │ ├── index.js │ │ │ ├── Types.js │ │ │ └── Model.js │ │ └── index.js │ └── package.json └── CommentService │ ├── .env │ ├── .babelrc │ ├── src │ ├── Comment │ │ ├── queryResolvers │ │ │ ├── index.js │ │ │ └── fetchCommentByPost.js │ │ ├── mutationResolvers │ │ │ ├── index.js │ │ │ ├── postComment.js │ │ │ └── updateComment.js │ │ ├── index.js │ │ ├── Types.js │ │ └── Model.js │ └── index.js │ └── package.json ├── federation_demo.PNG ├── apollo_federation.png ├── package.json ├── gateway.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | services/*/package-lock.json -------------------------------------------------------------------------------- /services/PostService/.env: -------------------------------------------------------------------------------- 1 | PORT=4002 2 | MONGO_DB=federation_post -------------------------------------------------------------------------------- /services/UserService/.env: -------------------------------------------------------------------------------- 1 | PORT=4001 2 | MONGO_DB=federation_user -------------------------------------------------------------------------------- /services/CommentService/.env: -------------------------------------------------------------------------------- 1 | PORT=4003 2 | MONGO_DB=federation_comment -------------------------------------------------------------------------------- /services/CommentService/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /services/PostService/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /services/UserService/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /federation_demo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshmani/apollo-federation-app/HEAD/federation_demo.PNG -------------------------------------------------------------------------------- /apollo_federation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ganeshmani/apollo-federation-app/HEAD/apollo_federation.png -------------------------------------------------------------------------------- /services/UserService/src/User/mutationResolvers/index.js: -------------------------------------------------------------------------------- 1 | import createUser from './createUser'; 2 | 3 | export default { 4 | createUser 5 | } -------------------------------------------------------------------------------- /services/CommentService/src/Comment/queryResolvers/index.js: -------------------------------------------------------------------------------- 1 | import fetchCommentByPost from './fetchCommentByPost'; 2 | 3 | export default { 4 | fetchCommentByPost 5 | } -------------------------------------------------------------------------------- /services/PostService/src/Post/mutationresolvers/index.js: -------------------------------------------------------------------------------- 1 | import createPost from './createPost'; 2 | import updatePost from './updatePost'; 3 | 4 | export default { 5 | createPost, 6 | updatePost 7 | } -------------------------------------------------------------------------------- /services/CommentService/src/Comment/mutationResolvers/index.js: -------------------------------------------------------------------------------- 1 | import postComment from './postComment'; 2 | import updateComment from './updateComment'; 3 | 4 | export default { 5 | postComment, 6 | updateComment 7 | } -------------------------------------------------------------------------------- /services/UserService/src/User/queryResolvers/index.js: -------------------------------------------------------------------------------- 1 | import fetchUserById from './fetchUserById'; 2 | import fetchAllUser from './fetchAllUser'; 3 | 4 | 5 | export default { 6 | fetchUserById, 7 | fetchAllUser 8 | } -------------------------------------------------------------------------------- /services/PostService/src/Post/queryResolvers/index.js: -------------------------------------------------------------------------------- 1 | import fetchPostById from './fetchPostById'; 2 | import fetchPostByUserId from './fetchPostByUserId'; 3 | export default { 4 | fetchPostById, 5 | fetchPostByUserId 6 | } -------------------------------------------------------------------------------- /services/UserService/src/User/queryResolvers/fetchAllUser.js: -------------------------------------------------------------------------------- 1 | 2 | import UserModel from '../Model'; 3 | export default async(parent,args,context) => { 4 | 5 | try { 6 | 7 | const userCollection = await UserModel.getAllUser(); 8 | 9 | return { 10 | success : true, 11 | data : userCollection, 12 | error : null 13 | } 14 | 15 | } 16 | catch(e){ 17 | console.log(e); 18 | 19 | return { 20 | success : false, 21 | error : { 22 | status : 500, 23 | message : e 24 | } 25 | } 26 | } 27 | 28 | 29 | } -------------------------------------------------------------------------------- /services/CommentService/src/Comment/mutationResolvers/postComment.js: -------------------------------------------------------------------------------- 1 | 2 | import CommentModel from '../Model'; 3 | export default async(parent,args,context) => { 4 | 5 | try{ 6 | const commentInfo = args.request.data; 7 | 8 | await CommentModel.insertComment(commentInfo); 9 | 10 | return { 11 | success : true, 12 | error : null 13 | } 14 | 15 | } 16 | catch(e){ 17 | console.log(e); 18 | 19 | return { 20 | success : false, 21 | error : { 22 | status : 500, 23 | message : e 24 | } 25 | } 26 | 27 | } 28 | 29 | 30 | } -------------------------------------------------------------------------------- /services/UserService/src/User/index.js: -------------------------------------------------------------------------------- 1 | import userTypes from './Types'; 2 | 3 | import mutationResolvers from './mutationResolvers'; 4 | import queryResolvers from './queryResolvers'; 5 | 6 | export const userTypeDefs =` 7 | ${userTypes} 8 | 9 | type Query{ 10 | fetchUserById(request: fetchUserByIdInput) : fetchUserByIdResponse 11 | fetchAllUser : fetchAllUserResponse 12 | } 13 | 14 | type Mutation{ 15 | createUser(request: createUserInput) : createUserResponse 16 | } 17 | ` 18 | 19 | export const userResolvers = { 20 | Query : { 21 | ...queryResolvers 22 | }, 23 | Mutation : { 24 | ...mutationResolvers 25 | } 26 | } -------------------------------------------------------------------------------- /services/PostService/src/Post/queryResolvers/fetchPostById.js: -------------------------------------------------------------------------------- 1 | 2 | import PostModel from '../Model'; 3 | 4 | export default async(parent,args,context) => { 5 | 6 | try { 7 | const postId = args.request.data.postId; 8 | 9 | const postCollection = await PostModel.getPostById(postId); 10 | 11 | return { 12 | success : true, 13 | data : postCollection, 14 | error : null 15 | } 16 | 17 | } 18 | catch(e){ 19 | console.log(e); 20 | 21 | return { 22 | success : false, 23 | error : { 24 | status : 500, 25 | message : e 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /services/PostService/src/Post/queryResolvers/fetchPostByUserId.js: -------------------------------------------------------------------------------- 1 | import PostModel from '../Model'; 2 | export default async(parent,args,context) => { 3 | 4 | try { 5 | const userId = args.request.data.userId; 6 | 7 | const postCollection = await PostModel.getPostByUserId(userId); 8 | 9 | return { 10 | success : true, 11 | data : postCollection, 12 | error : null 13 | } 14 | 15 | } 16 | catch(e){ 17 | console.log(e); 18 | 19 | return { 20 | success : false, 21 | error : { 22 | status : 500, 23 | message : e 24 | } 25 | } 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /services/UserService/src/User/queryResolvers/fetchUserById.js: -------------------------------------------------------------------------------- 1 | 2 | import UserModel from '../Model'; 3 | export default async(parent,args,context) => { 4 | 5 | 6 | try { 7 | const userId = args.request.data.userId; 8 | 9 | const userCollection = await UserModel.getUserById(userId); 10 | 11 | return { 12 | success : true, 13 | data : userCollection, 14 | error : null 15 | } 16 | 17 | } 18 | catch(e){ 19 | console.log(e); 20 | 21 | return { 22 | success : false, 23 | error : { 24 | status : 500, 25 | message : e 26 | } 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /services/PostService/src/Post/mutationresolvers/updatePost.js: -------------------------------------------------------------------------------- 1 | 2 | import PostModel from '../Model'; 3 | 4 | export default async(parent,args,context) => { 5 | 6 | try { 7 | const postId = args.request.data.postId; 8 | 9 | const postInfo = args.request.data; 10 | 11 | await PostModel.updatePost(postId,postInfo); 12 | 13 | return { 14 | success : true, 15 | error : null 16 | } 17 | 18 | } 19 | catch(e){ 20 | console.log(e); 21 | 22 | return { 23 | success : false, 24 | error : { 25 | status : 500, 26 | message : e 27 | } 28 | } 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /services/CommentService/src/Comment/queryResolvers/fetchCommentByPost.js: -------------------------------------------------------------------------------- 1 | 2 | import CommentModel from '../Model'; 3 | export default async(parent,args,context) => { 4 | 5 | try{ 6 | const postId = args.request.data.postId; 7 | 8 | const commentCollection = await CommentModel.getCommentByPost(postId); 9 | 10 | return { 11 | success : true, 12 | data : commentCollection, 13 | error : null 14 | } 15 | 16 | } 17 | catch(e){ 18 | console.log(e); 19 | 20 | return { 21 | success: false, 22 | error : { 23 | status : 500, 24 | message : e 25 | } 26 | } 27 | 28 | } 29 | 30 | 31 | } -------------------------------------------------------------------------------- /services/UserService/src/User/Types.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | input fetchUserByIdInputData { 4 | userId : String! 5 | } 6 | 7 | input fetchUserByIdInput { 8 | data : fetchUserByIdInputData! 9 | } 10 | 11 | input createUserInputData { 12 | name : String 13 | email : String 14 | } 15 | 16 | input createUserInput{ 17 | data : createUserInputData 18 | } 19 | 20 | type fetchAllUserResponse{ 21 | success : Boolean 22 | data : [User!] 23 | error : Error 24 | } 25 | 26 | type fetchUserByIdResponse { 27 | success : Boolean 28 | data : User 29 | error : Error 30 | } 31 | 32 | type createUserResponse { 33 | success : Boolean 34 | error : Error 35 | } 36 | ` -------------------------------------------------------------------------------- /services/CommentService/src/Comment/mutationResolvers/updateComment.js: -------------------------------------------------------------------------------- 1 | 2 | import CommentModel from '../Model'; 3 | export default async(parent,args,context) => { 4 | 5 | try { 6 | 7 | const commentId = args.request.data.commentId; 8 | const description = args.request.data.description; 9 | 10 | await CommentModel.updateComment(commentId,description); 11 | 12 | return { 13 | success : true, 14 | error : null 15 | } 16 | 17 | 18 | } 19 | catch(e){ 20 | console.log(e); 21 | 22 | return { 23 | success: false, 24 | error :{ 25 | status : 500, 26 | message : e 27 | } 28 | } 29 | 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-federation-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start-gateway": "nodemon --exec babel-node gateway.js", 8 | "postinstall": "lerna bootstrap", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@apollo/gateway": "^0.10.4", 16 | "apollo-server": "^2.9.3", 17 | "graphql": "^14.5.4", 18 | "redis": "^2.8.0" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.6.0", 22 | "@babel/node": "^7.6.1", 23 | "@babel/preset-env": "^7.6.0", 24 | "lerna": "^3.16.4", 25 | "nodemon": "^1.19.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gateway.js: -------------------------------------------------------------------------------- 1 | const { ApolloServer } = require('apollo-server'); 2 | 3 | const { ApolloGateway } = require('@apollo/gateway'); 4 | 5 | const gateway = new ApolloGateway({ 6 | serviceList: [ 7 | { name: "users", url: "http://localhost:4001/graphql" }, 8 | { name: "posts", url: "http://localhost:4002/graphql" }, 9 | { name: "comments", url: "http://localhost:4003/graphql" }, 10 | // { name: "inventory", url: "http://localhost:4004/graphql" } 11 | ] 12 | }); 13 | 14 | (async () => { 15 | const { schema, executor } = await gateway.load(); 16 | 17 | const server = new ApolloServer({ schema, executor }); 18 | 19 | server.listen().then(({ url }) => { 20 | console.log(`🚀 Server ready at ${url}`); 21 | }); 22 | })(); 23 | -------------------------------------------------------------------------------- /services/CommentService/src/Comment/index.js: -------------------------------------------------------------------------------- 1 | 2 | import CommentTypes from './Types'; 3 | import queryResolvers from './queryResolvers'; 4 | import mutationResolvers from './mutationResolvers'; 5 | 6 | 7 | export const commentTypeDefs = ` 8 | 9 | ${CommentTypes} 10 | 11 | extend type Query { 12 | fetchCommentByPost(request : fetchCommentByPostInput) : fetchCommentByPostResponse 13 | } 14 | 15 | extend type Mutation { 16 | postComment(request: postCommentInput) : postCommentResponse 17 | updateComment(request : updateCommentInput) : updateCommentResponse 18 | } 19 | ` 20 | 21 | export const commentResolvers = { 22 | Query: { 23 | ...queryResolvers 24 | }, 25 | Mutation : { 26 | ...mutationResolvers 27 | } 28 | } -------------------------------------------------------------------------------- /services/UserService/src/User/mutationResolvers/createUser.js: -------------------------------------------------------------------------------- 1 | import UserModel from '../Model'; 2 | 3 | export default async(parent,args,context) => { 4 | 5 | try { 6 | const name = args.request.data.name; 7 | const email = args.request.data.email; 8 | const userInfo = { 9 | name, 10 | email 11 | } 12 | 13 | await UserModel.insertUser(userInfo); 14 | 15 | return { 16 | success : true, 17 | error : null 18 | } 19 | 20 | 21 | } 22 | catch(e){ 23 | console.log(e); 24 | 25 | return { 26 | success: false, 27 | error : { 28 | status : 500, 29 | message : e 30 | } 31 | } 32 | 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /services/PostService/src/Post/mutationresolvers/createPost.js: -------------------------------------------------------------------------------- 1 | 2 | import PostModel from '../Model'; 3 | export default async(parent,args,context) => { 4 | 5 | try{ 6 | 7 | let postInfo = { 8 | name : args.request.data.name, 9 | userId : args.request.data.userId, 10 | description : args.request.data.description 11 | } 12 | 13 | await PostModel.createPost(postInfo); 14 | 15 | return { 16 | success : true, 17 | error : null 18 | } 19 | 20 | } 21 | catch(e){ 22 | console.log(e); 23 | 24 | return { 25 | success : false, 26 | error : { 27 | status : 500, 28 | message : e 29 | } 30 | } 31 | 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /services/PostService/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PostService", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon --exec babel-node src/index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@apollo/federation": "^0.9.4", 15 | "apollo-server-express": "^2.9.3", 16 | "body-parser": "^1.19.0", 17 | "express": "^4.17.1", 18 | "graphql": "^14.5.4", 19 | "lodash": "^4.17.15", 20 | "mongoose": "^5.6.13" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.6.0", 24 | "@babel/node": "^7.6.1", 25 | "@babel/preset-env": "^7.6.0", 26 | "dotenv": "^8.1.0", 27 | "nodemon": "^1.19.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/UserService/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UserService", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon --exec babel-node src/index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@apollo/federation": "^0.9.4", 15 | "apollo-server-express": "^2.9.3", 16 | "body-parser": "^1.19.0", 17 | "express": "^4.17.1", 18 | "graphql": "^14.5.4", 19 | "lodash": "^4.17.15", 20 | "mongoose": "^5.6.13" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.6.0", 24 | "@babel/node": "^7.6.0", 25 | "@babel/preset-env": "^7.6.0", 26 | "dotenv": "^8.1.0", 27 | "nodemon": "^1.19.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/CommentService/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CommentService", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon --exec babel-node src/index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@apollo/federation": "^0.9.4", 15 | "apollo-server-express": "^2.9.3", 16 | "body-parser": "^1.19.0", 17 | "express": "^4.17.1", 18 | "graphql": "^14.5.4", 19 | "lodash": "^4.17.15", 20 | "mongoose": "^5.6.13" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.6.0", 24 | "@babel/node": "^7.6.1", 25 | "@babel/preset-env": "^7.6.0", 26 | "dotenv": "^8.1.0", 27 | "nodemon": "^1.19.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /services/PostService/src/Post/index.js: -------------------------------------------------------------------------------- 1 | 2 | import queryResolvers from './queryResolvers'; 3 | import mutationResolvers from './mutationresolvers'; 4 | import PostTypes from './Types'; 5 | import { gql } from 'apollo-server'; 6 | 7 | export const postTypeDefs = gql` 8 | 9 | ${PostTypes} 10 | extend type Query { 11 | fetchPostById(request : fetchPostByIdInput) : fetchPostByIdResponse 12 | fetchPostByUserId(request : fetchPostByUserIdInput) : fetchPostByUserResponse 13 | } 14 | 15 | extend type Mutation { 16 | createPost(request: createPostInput) : createPostResponse 17 | updatePost(request: updatePostInput) : updatePostResponse 18 | } 19 | 20 | ` 21 | 22 | 23 | export const postResolvers = { 24 | 25 | Query:{ 26 | ...queryResolvers 27 | }, 28 | Mutation: { 29 | ...mutationResolvers 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /services/CommentService/src/Comment/Types.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | input fetchCommentByPostInputData { 4 | postId : String! 5 | } 6 | 7 | input fetchCommentByPostInput { 8 | data : fetchCommentByPostInputData 9 | } 10 | 11 | input updateCommentInputData { 12 | commentId : String! 13 | description : String! 14 | } 15 | 16 | input updateCommentInput { 17 | data : updateCommentInputData 18 | } 19 | 20 | input postCommentInputData { 21 | postId : String! 22 | userId : String! 23 | description : String! 24 | } 25 | 26 | input postCommentInput { 27 | data : postCommentInputData 28 | } 29 | 30 | type fetchCommentByPostResponse { 31 | success : Boolean 32 | data : [Comment!] 33 | error : Error 34 | } 35 | 36 | type postCommentResponse { 37 | success : Boolean 38 | error : Error 39 | } 40 | 41 | type updateCommentResponse { 42 | success : Boolean 43 | error : Error 44 | } 45 | 46 | ` -------------------------------------------------------------------------------- /services/UserService/src/User/Model.js: -------------------------------------------------------------------------------- 1 | import Mongoose from 'mongoose'; 2 | 3 | const userSchema = new Mongoose.Schema({ 4 | name : { 5 | type : String, 6 | required : true 7 | }, 8 | email : { 9 | type : String, 10 | required : true 11 | } 12 | },{timestamps : true}) 13 | 14 | class User { 15 | 16 | static getAllUser(){ 17 | 18 | return this.find({}).exec(); 19 | } 20 | 21 | static getUserById(id) { 22 | 23 | return this.findOne({ 24 | _id : Mongoose.mongo.ObjectID(id) 25 | }).exec(); 26 | } 27 | 28 | static insertUser(userInfo) { 29 | 30 | const user = this(userInfo); 31 | 32 | return user.save(); 33 | } 34 | 35 | 36 | static updateUser(userId,userInfo){ 37 | 38 | return this.updateOne({ 39 | _id : Mongoose.mongo.ObjectID(userId) 40 | },{ 41 | $set : userInfo 42 | }).exec(); 43 | 44 | } 45 | 46 | } 47 | 48 | userSchema.loadClass(User); 49 | 50 | export default Mongoose.model('User',userSchema) -------------------------------------------------------------------------------- /services/PostService/src/Post/Types.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 3 | input fetchPostByUserIdInputData { 4 | userId : String 5 | } 6 | 7 | input fetchPostByUserIdInput { 8 | data : fetchPostByUserIdInputData 9 | } 10 | 11 | input fetchPostByIdInputData { 12 | postId : String 13 | } 14 | 15 | input fetchPostByIdInput { 16 | data : fetchPostByIdInputData 17 | } 18 | 19 | input updatePostInputData{ 20 | postId : String 21 | name : String 22 | description : String 23 | } 24 | 25 | input updatePostInput{ 26 | data : updatePostInputData 27 | } 28 | 29 | input createPostInputData { 30 | userId : String 31 | name : String 32 | description : String 33 | } 34 | 35 | input createPostInput{ 36 | data : createPostInputData 37 | } 38 | 39 | type fetchPostByUserResponse { 40 | success : Boolean 41 | data : [Post] 42 | error : Error 43 | } 44 | 45 | type fetchPostByIdResponse { 46 | success : Boolean 47 | data : Post 48 | error : Error 49 | } 50 | 51 | type updatePostResponse{ 52 | success : Boolean 53 | error : Error 54 | } 55 | 56 | type createPostResponse { 57 | success : Boolean 58 | error : Error 59 | } 60 | 61 | ` -------------------------------------------------------------------------------- /services/PostService/src/Post/Model.js: -------------------------------------------------------------------------------- 1 | 2 | import Mongoose from 'mongoose'; 3 | 4 | const postSchema = new Mongoose.Schema({ 5 | name : { 6 | type : String, 7 | required : true 8 | }, 9 | description : { 10 | type : String, 11 | required : true 12 | }, 13 | userId : { 14 | type : String, 15 | required : true 16 | } 17 | },{ timestamps : true }); 18 | 19 | class Post { 20 | 21 | static getPostById(id){ 22 | return this.findOne({ 23 | _id : Mongoose.mongo.ObjectID(id) 24 | }).exec(); 25 | } 26 | 27 | static getPostByUserId(userId) { 28 | 29 | return this.find({ 30 | userId 31 | }).exec(); 32 | } 33 | 34 | static createPost(postInfo) { 35 | 36 | const post = this(postInfo); 37 | 38 | return post.save(); 39 | } 40 | 41 | static updatePost(postId,postInfo){ 42 | 43 | return this.updateOne({ 44 | _id : Mongoose.mongo.ObjectID(postId) 45 | },{ 46 | $set : postInfo 47 | }).exec(); 48 | 49 | } 50 | 51 | 52 | } 53 | 54 | postSchema.loadClass(Post); 55 | 56 | export default Mongoose.model('Post',postSchema); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Building Microservice using Apollo Federation 2 | 3 | This repository is a demo of building microservices using Apollo federation. Apollo federation build a single schema in gateway which stitch all the services.All the microservices are in `./services` folder. 4 | 5 | It contains the simple blog microservices `user` , `post` and `comment` services which gets connected using `gateway` 6 | 7 | ![](./apollo_federation.png) 8 | 9 | ![Demo](./federation_demo.PNG) 10 | 11 | ### Installation 12 | 13 | `User Service` 14 | 15 | ``` 16 | $ cd services/UserService 17 | $ npm install 18 | $ npm run dev 19 | ``` 20 | 21 | `Post Service` 22 | 23 | ``` 24 | $ cd services/PostService 25 | $ npm install 26 | $ npm run dev 27 | ``` 28 | 29 | ` Comment Service ` 30 | 31 | ``` 32 | $ cd services/CommentService 33 | $ npm install 34 | $ npm run dev 35 | ``` 36 | 37 | ` Gateway ` 38 | 39 | ``` 40 | npx lerna init 41 | $ npm install 42 | ``` 43 | - With lerna being a tool for managing JS projects - more info @ https://github.com/lerna/lerna#readme 44 | 45 | This will install all of the dependencies for the gateway and each underlying service 46 | 47 | #### Running API Gateway 48 | 49 | ``` 50 | $ npm run start-gateway 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /services/CommentService/src/Comment/Model.js: -------------------------------------------------------------------------------- 1 | 2 | import Mongoose from 'mongoose'; 3 | 4 | const commentSchema = new Mongoose.Schema({ 5 | postId : { 6 | type : String, 7 | required : true 8 | }, 9 | userId : { 10 | type : String, 11 | required : true 12 | }, 13 | description : { 14 | type : String, 15 | required : true 16 | } 17 | },{ timestamps : true }); 18 | 19 | class Comment { 20 | 21 | static getCommentById(id){ 22 | return this.findOne({ 23 | _id : Mongoose.mongo.ObjectID(id) 24 | }).exec(); 25 | 26 | } 27 | 28 | static getCommentByPost(postId){ 29 | 30 | return this.find({ 31 | postId 32 | }).exec(); 33 | } 34 | 35 | static getCommentByUser(userId) { 36 | 37 | return this.find({ 38 | userId 39 | }).exec(); 40 | 41 | } 42 | 43 | static insertComment(commentInfo) { 44 | 45 | const comment = this(commentInfo); 46 | 47 | return comment.save(); 48 | } 49 | 50 | static updateComment(commentId,description) { 51 | 52 | return this.updateOne({ 53 | _id : Mongoose.mongo.ObjectID(commentId) 54 | },{ 55 | $set : { 56 | description 57 | } 58 | }).exec(); 59 | } 60 | 61 | 62 | } 63 | 64 | commentSchema.loadClass(Comment); 65 | 66 | export default Mongoose.model('Comment',commentSchema); 67 | 68 | -------------------------------------------------------------------------------- /services/UserService/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | import * as bodyParser from 'body-parser'; 4 | import {merge} from 'lodash'; 5 | 6 | import { ApolloServer,gql } from 'apollo-server-express'; 7 | import { buildFederatedSchema } from '@apollo/federation' 8 | import mongoose from 'mongoose'; 9 | 10 | import { userTypeDefs,userResolvers } from './User'; 11 | 12 | import UserModel from './User/Model'; 13 | 14 | const typeDefs = gql` 15 | type Error @key(fields: "status") @key(fields:"message"){ 16 | status : Int 17 | message : String 18 | } 19 | 20 | type User @key(fields: "_id"){ 21 | _id : ID 22 | name : String 23 | email : String 24 | } 25 | 26 | ${userTypeDefs} 27 | ` 28 | 29 | const resolveRef = { 30 | User : { 31 | async __resolveReference(object) { 32 | return await UserModel.getUserById(object._id); 33 | } 34 | } 35 | } 36 | 37 | const resolvers = merge( 38 | resolveRef, 39 | userResolvers 40 | 41 | ) 42 | 43 | const app = express(); 44 | require('dotenv').config(); 45 | 46 | const MONGO_DB = process.env.MONGO_DB; 47 | 48 | mongoose.connect(`mongodb://localhost:27017/${MONGO_DB}`,{ useNewUrlParser : true }) 49 | .then((res) => { 50 | 51 | const server = new ApolloServer({ 52 | schema : buildFederatedSchema([ 53 | { 54 | typeDefs, 55 | resolvers 56 | } 57 | ]), 58 | context : ({req,res,context}) => { 59 | return { 60 | req, 61 | res, 62 | context 63 | } 64 | } 65 | }) 66 | 67 | server.applyMiddleware({app}); 68 | 69 | const PORT = process.env.PORT; 70 | 71 | app.listen(PORT,() => { 72 | console.log(`server is listening to port ${PORT}`); 73 | }) 74 | 75 | }) 76 | .catch((err) => { 77 | console.error(err); 78 | }) -------------------------------------------------------------------------------- /services/PostService/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | import * as bodyParser from 'body-parser'; 4 | import {merge} from 'lodash'; 5 | 6 | import { ApolloServer,gql } from 'apollo-server-express'; 7 | import { buildFederatedSchema } from '@apollo/federation' 8 | import mongoose from 'mongoose'; 9 | 10 | import { postTypeDefs,postResolvers } from './Post'; 11 | import PostModel from './Post/Model'; 12 | 13 | const typeDefs = gql` 14 | 15 | extend type Error @key(fields: "status") @key(fields: "message") { 16 | message: String @external 17 | status: Int @external 18 | } 19 | 20 | extend type User @key(fields: "_id") { 21 | _id : ID @external 22 | posts: [Post] 23 | } 24 | 25 | type Post @key(fields: "_id"){ 26 | _id : ID 27 | name : String 28 | description : String 29 | } 30 | 31 | ${postTypeDefs} 32 | ` 33 | 34 | const resolverReferences = { 35 | User: { 36 | async posts(user) { 37 | return PostModel.getPostByUserId(user._id) 38 | } 39 | } 40 | } 41 | 42 | const resolvers = merge( 43 | resolverReferences, 44 | postResolvers 45 | ) 46 | 47 | const app = express(); 48 | require('dotenv').config(); 49 | 50 | const MONGO_DB = process.env.MONGO_DB; 51 | 52 | mongoose.connect(`mongodb://localhost:27017/${MONGO_DB}`,{ useNewUrlParser : true }) 53 | .then((res) => { 54 | console.log("mongoose connected successfully"); 55 | const server = new ApolloServer({ 56 | schema : buildFederatedSchema([ 57 | { 58 | typeDefs, 59 | resolvers 60 | } 61 | ]), 62 | context : ({req,res,context}) => { 63 | return { 64 | req, 65 | res, 66 | context 67 | } 68 | } 69 | }) 70 | 71 | server.applyMiddleware({app}); 72 | 73 | const PORT = process.env.PORT; 74 | 75 | app.listen(PORT,() => { 76 | console.log(`server is listening to port ${PORT}`); 77 | }) 78 | 79 | }) 80 | .catch((err) => { 81 | console.error(err); 82 | }) -------------------------------------------------------------------------------- /services/CommentService/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | import * as bodyParser from 'body-parser'; 4 | import {merge} from 'lodash'; 5 | 6 | import { ApolloServer,gql } from 'apollo-server-express'; 7 | import { buildFederatedSchema } from '@apollo/federation' 8 | import mongoose from 'mongoose'; 9 | 10 | import { commentTypeDefs,commentResolvers } from './Comment'; 11 | import CommentModel from './Comment/Model'; 12 | 13 | const typeDefs = gql` 14 | 15 | extend type Error @key(fields: "status") @key(fields: "message") { 16 | message: String @external 17 | status: Int @external 18 | } 19 | 20 | extend type Post @key(fields: "_id"){ 21 | _id : ID @external 22 | comments : [ Comment! ] 23 | } 24 | 25 | extend type User @key(fields: "_id"){ 26 | _id : ID @external 27 | name : String @external 28 | email : String @external 29 | } 30 | 31 | 32 | type Comment { 33 | _id : ID 34 | postId : String! 35 | user : User @provides(fields : "name") @provides(fields: "email") 36 | description : String! 37 | } 38 | 39 | ${commentTypeDefs} 40 | ` 41 | const resolveReferences = { 42 | Comment : { 43 | user(comment) { 44 | return { __typename: "User",_id : comment.userId } 45 | } 46 | }, 47 | Post : { 48 | async comments(post) { 49 | return await CommentModel.getCommentByPost(post._id); 50 | } 51 | } 52 | } 53 | 54 | const resolvers = merge( 55 | resolveReferences, 56 | commentResolvers 57 | ) 58 | 59 | const app = express(); 60 | require('dotenv').config(); 61 | 62 | const MONGO_DB = process.env.MONGO_DB; 63 | 64 | mongoose.connect(`mongodb://localhost:27017/${MONGO_DB}`,{ useNewUrlParser : true }) 65 | .then((res) => { 66 | console.log("mongoose connected successfully"); 67 | const server = new ApolloServer({ 68 | schema : buildFederatedSchema([ 69 | { 70 | typeDefs, 71 | resolvers 72 | } 73 | ]), 74 | context : ({req,res,context}) => { 75 | return { 76 | req, 77 | res, 78 | context 79 | } 80 | } 81 | }) 82 | 83 | server.applyMiddleware({app}); 84 | 85 | const PORT = process.env.PORT; 86 | 87 | app.listen(PORT,() => { 88 | console.log(`server is listening to port ${PORT}`); 89 | }) 90 | 91 | }) 92 | .catch((err) => { 93 | console.error(err); 94 | }) --------------------------------------------------------------------------------