├── .gitignore ├── api ├── config.js ├── graphql │ ├── comments │ │ ├── schema.js │ │ ├── model.js │ │ ├── resolver.js │ │ └── storage.js │ ├── shows-tv │ │ ├── schema.js │ │ ├── connector.js │ │ └── resolver.js │ ├── schema.js │ └── posts │ │ ├── model.js │ │ ├── schema.js │ │ ├── storage.js │ │ └── resolver.js └── server.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea -------------------------------------------------------------------------------- /api/config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | db: 'mongodb://localhost:27017/workshop-graphql', 4 | tvMazeUrl: 'http://api.tvmaze.com' 5 | }; -------------------------------------------------------------------------------- /api/graphql/comments/schema.js: -------------------------------------------------------------------------------- 1 | const schema = [` 2 | 3 | # data to create comment 4 | input CommentInput { 5 | postId: ID!, 6 | name: String!, 7 | email: String!, 8 | body: String! 9 | } 10 | 11 | type Comment { 12 | id: ID, 13 | post: Post, 14 | name: String, 15 | email: String, 16 | body: String 17 | } 18 | 19 | extend type Query { 20 | comments: [Comment] 21 | } 22 | 23 | type Subscription { 24 | commentAdd(data: CommentInput): Comment 25 | } 26 | `]; 27 | 28 | module.exports = schema; -------------------------------------------------------------------------------- /api/graphql/shows-tv/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const schema = [` 4 | 5 | # show data 6 | type Show { 7 | id: Int, 8 | name: String, 9 | type: String, 10 | language: String 11 | genres: [String], 12 | status: String, 13 | runtime: Int, 14 | premiered: String, 15 | akas: [Aka] 16 | } 17 | 18 | type Aka { 19 | name: String, 20 | country: Country 21 | } 22 | 23 | type Country { 24 | name: String, 25 | code: String, 26 | timezone: String 27 | } 28 | 29 | type Query { 30 | shows: [Show] 31 | showById(id: ID!): Show 32 | } 33 | `]; 34 | 35 | module.exports = schema; -------------------------------------------------------------------------------- /api/graphql/shows-tv/connector.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fetch = require('node-fetch'); 4 | 5 | 6 | class ShowConnector { 7 | constructor(url = '') { 8 | if (!url) throw new Error()('The url is not provider'); 9 | this.API_ROOT = url; 10 | } 11 | 12 | async fetch(url) { 13 | const URL = `${this.API_ROOT}${url}`; 14 | const res = await fetch(URL).then(r => r.json()); 15 | 16 | if (res.errors) throw Error(res.message); 17 | return res; 18 | } 19 | 20 | getShows() { 21 | return this.fetch('/shows?page=1'); 22 | } 23 | 24 | getShowById(id) { 25 | return this.fetch(`/shows/${id}`); 26 | } 27 | 28 | getShowsAkas(id) { 29 | return this.fetch(`/shows/${id}/akas`); 30 | } 31 | } 32 | 33 | module.exports = ShowConnector; -------------------------------------------------------------------------------- /api/graphql/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const merge = require('lodash.merge'); 4 | const {makeExecutableSchema} = require('graphql-tools'); 5 | 6 | const showSchema = require('./shows-tv/schema'); 7 | const showResolrver = require('./shows-tv/resolver'); 8 | const postSchema = require('./posts/schema'); 9 | const postResolrver = require('./posts/resolver'); 10 | const commentSchema = require('./comments/schema'); 11 | const commentResolrver = require('./comments/resolver'); 12 | 13 | 14 | const typeDefs = [ 15 | ...showSchema, 16 | ...postSchema, 17 | ...commentSchema 18 | ]; 19 | 20 | 21 | const resolvers = merge( 22 | showResolrver, 23 | postResolrver, 24 | commentResolrver 25 | ); 26 | 27 | 28 | const executableSchema = makeExecutableSchema({ 29 | typeDefs, 30 | resolvers 31 | }); 32 | 33 | module.exports = executableSchema; 34 | -------------------------------------------------------------------------------- /api/graphql/posts/model.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | const schema = new mongoose.Schema({ 6 | title: { 7 | type: String, 8 | required: 'The title field is required' 9 | }, 10 | body: { 11 | type: String, 12 | required: 'The body field is required' 13 | }, 14 | author:{ 15 | type: String, 16 | required: 'The author field is required' 17 | }, 18 | email:{ 19 | type: String 20 | } 21 | }, {timestamps: true}); 22 | 23 | if (!schema.options.toJSON) schema.options.toJSON = {}; 24 | 25 | /** 26 | * Add a tranforma method to change _id by id 27 | * whent toJSON is used. 28 | */ 29 | schema.options.toJSON.transform = (doc, ret) => { 30 | // remove the _id of every document before returning the result 31 | ret.id = ret._id; 32 | delete ret._id; 33 | return ret; 34 | }; 35 | 36 | 37 | module.exports = mongoose.model('Post', schema); -------------------------------------------------------------------------------- /api/graphql/comments/model.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | const schema = new mongoose.Schema({ 6 | name: { 7 | type: String, 8 | required: 'The name field is required' 9 | }, 10 | body: { 11 | type: String, 12 | required: 'The body field is required' 13 | }, 14 | email: { 15 | type: String, 16 | required: 'The email field is required' 17 | }, 18 | postId:{ 19 | type: mongoose.Schema.Types.ObjectId, 20 | required: 'The postId field is required' 21 | } 22 | }, {timestamps: true}); 23 | 24 | if (!schema.options.toJSON) schema.options.toJSON = {}; 25 | 26 | /** 27 | * Add a tranforma method to change _id by id 28 | * whent toJSON is used. 29 | */ 30 | schema.options.toJSON.transform = (doc, ret) => { 31 | // remove the _id of every document before returning the result 32 | ret.id = ret._id; 33 | delete ret._id; 34 | return ret; 35 | }; 36 | 37 | 38 | module.exports = mongoose.model('Comment', schema); -------------------------------------------------------------------------------- /api/graphql/shows-tv/resolver.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resolver = { 4 | Query: { 5 | shows(root, args, {showConnector}) { 6 | let results = []; 7 | 8 | try { 9 | results = showConnector.getShows(); 10 | } catch (err) { 11 | throw new Error('Error: find all shows') 12 | } 13 | 14 | return results; 15 | }, 16 | showById(root, args, {showConnector}) { 17 | let results = {}; 18 | 19 | try { 20 | results = showConnector.getShowById(args.id); 21 | } catch (err) { 22 | throw new Error('Error: find show by id') 23 | } 24 | 25 | return results; 26 | } 27 | }, 28 | Show: { 29 | akas({id}, args, {showConnector}) { 30 | let results = []; 31 | try { 32 | results = showConnector.getShowsAkas(id); 33 | } catch (err) { 34 | throw new Error('Error: find all akas') 35 | } 36 | 37 | return results; 38 | } 39 | } 40 | }; 41 | 42 | 43 | module.exports = resolver; -------------------------------------------------------------------------------- /api/graphql/comments/resolver.js: -------------------------------------------------------------------------------- 1 | const resolver = { 2 | Query: { 3 | async comments(root, args, {commentStorage}) { 4 | let results = []; 5 | try { 6 | results = await commentStorage.find(); 7 | } catch (err) { 8 | throw new Error('Error: find all comment'); 9 | } 10 | return results; 11 | } 12 | }, 13 | Comment: { 14 | async post({postId}, args, {postStorage}) { 15 | let result = {}; 16 | if (!postId) return {}; 17 | try { 18 | result = await postStorage.findById(postId); 19 | } catch (err) { 20 | throw new Error('Error: find post'); 21 | } 22 | return result; 23 | } 24 | }, 25 | Subscription: { 26 | async commentAdd(root, args, {commentStorage}) { 27 | let result = {}; 28 | try { 29 | result = await commentStorage.save({...args.data}); 30 | } catch (err) { 31 | throw new Error('Error: save comment'); 32 | } 33 | return result; 34 | } 35 | } 36 | }; 37 | 38 | module.exports = resolver; -------------------------------------------------------------------------------- /api/graphql/posts/schema.js: -------------------------------------------------------------------------------- 1 | const schema = [` 2 | 3 | # data to create post 4 | input PostInput { 5 | author: String!, 6 | email: String, 7 | title: String!, 8 | body: String! 9 | } 10 | 11 | # data to update post 12 | input PostEditInput { 13 | author: String, 14 | email: String, 15 | title: String, 16 | body: String 17 | } 18 | 19 | # data post 20 | type Post { 21 | id: ID, 22 | author: String, 23 | title: String, 24 | body: String, 25 | comments: [Comment], 26 | createdAt: String 27 | } 28 | 29 | union SearchResult = Post 30 | 31 | extend type Query { 32 | # find all post 33 | posts: [Post] 34 | # find all post and comments 35 | search(search: String!): [SearchResult] 36 | # find post by id 37 | postById(id: ID!): Post 38 | } 39 | 40 | type Mutation { 41 | # create post 42 | postAdd(data: PostInput): Post 43 | # update post 44 | postEdit(id: ID!, data: PostEditInput): Post 45 | # delete post 46 | postDelete(id: ID!): Post 47 | } 48 | `]; 49 | 50 | module.exports = schema; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workshop-graphql-medellinjs", 3 | "version": "1.0.0", 4 | "description": "workshop for medellinjs, configuration of graphql with a server nodejs", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon api/server.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/IvanVilla1585/workshop-graphql-medellinjs.git" 12 | }, 13 | "keywords": [ 14 | "graphql", 15 | "workshop", 16 | "medellinjs" 17 | ], 18 | "author": "Ivan Dario Villa Ramirez ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/IvanVilla1585/workshop-graphql-medellinjs/issues" 22 | }, 23 | "homepage": "https://github.com/IvanVilla1585/workshop-graphql-medellinjs#readme", 24 | "dependencies": { 25 | "apollo-server-express": "^1.3.2", 26 | "bluebird": "^3.5.1", 27 | "body-parser": "^1.18.2", 28 | "express": "^4.16.2", 29 | "graphql": "^0.12.3", 30 | "graphql-tools": "^2.19.0", 31 | "lodash.merge": "^4.6.0", 32 | "mongoose": "^4.13.10", 33 | "node-fetch": "^1.7.3" 34 | }, 35 | "devDependencies": { 36 | "nodemon": "^1.14.11" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /api/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const express = require('express'); 4 | const mongoose = require('mongoose'); 5 | const Promise = require('bluebird'); 6 | const bodyParse = require('body-parser'); 7 | const {graphqlExpress, graphiqlExpress} = require('apollo-server-express'); 8 | 9 | const config = require('./config'); 10 | const schema = require('./graphql/schema'); 11 | const ShowConnector = require('./graphql/shows-tv/connector'); 12 | const PostStorage = require('./graphql/posts/storage'); 13 | const CommentStorage = require('./graphql/comments/storage'); 14 | 15 | const app = express(); 16 | const port = 3000; 17 | 18 | mongoose.Promise = Promise; 19 | 20 | const conn = mongoose.createConnection(config.db); 21 | 22 | app.use(bodyParse.json()); 23 | 24 | app.use('/graphql', graphqlExpress(req => { 25 | return { 26 | schema, 27 | context: { 28 | showConnector: new ShowConnector(config.tvMazeUrl), 29 | postStorage: new PostStorage(conn), 30 | commentStorage: new CommentStorage(conn) 31 | } 32 | } 33 | })); 34 | 35 | app.get('/graphiql', graphiqlExpress({ 36 | endpointURL: '/graphql' 37 | })); 38 | 39 | app.listen(port, () => console.log(`server running in the port ${port}`)); -------------------------------------------------------------------------------- /api/graphql/posts/storage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./model'); 4 | 5 | class PostStorage { 6 | constructor(conn = null) { 7 | if (!conn) throw new Error('connection not provider'); 8 | 9 | this.Model = conn.model('Post'); 10 | } 11 | 12 | async save(data) { 13 | let result = {}; 14 | try { 15 | result = await new this.Model(data).save(); 16 | } catch (err) { 17 | console.log('err', err) 18 | throw new Error(err.messages); 19 | } 20 | return result; 21 | } 22 | 23 | async update(id, data) { 24 | let result = {}; 25 | try { 26 | result = await this.Model.findByIdAndUpdate(id, {$set: data}, {new: true}); 27 | if (!result) { 28 | result = {failed: true, message: 'Post not found'} 29 | } 30 | } catch (err) { 31 | throw new Error(err.messages); 32 | } 33 | return result; 34 | } 35 | 36 | async delete(id) { 37 | let result = {}; 38 | try { 39 | result = await this.Model.findByIdAndRemove(id); 40 | if (!result) { 41 | result = {failed: true, message: 'Post not found'} 42 | } 43 | } catch (err) { 44 | throw new Error(err.messages); 45 | } 46 | return result; 47 | } 48 | 49 | async find(query = {}) { 50 | let results = []; 51 | try { 52 | results = await this.Model.find(query); 53 | } catch (err) { 54 | throw new Error(err.messages); 55 | } 56 | return results; 57 | } 58 | 59 | async findById(id) { 60 | let result = {}; 61 | try { 62 | result = await this.Model.findById(id); 63 | if (!result) { 64 | result = {failed: true, message: 'Post not found'} 65 | } 66 | } catch (err) { 67 | throw new Error(err.messages); 68 | } 69 | return result; 70 | } 71 | } 72 | 73 | module.exports = PostStorage; -------------------------------------------------------------------------------- /api/graphql/comments/storage.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./model'); 4 | 5 | class CommentStorage { 6 | constructor(conn = null) { 7 | if (!conn) throw new Error('connection not provider'); 8 | 9 | this.Model = conn.model('Comment'); 10 | } 11 | 12 | async save(data) { 13 | let result = {}; 14 | try { 15 | result = await new this.Model(data).save(); 16 | } catch (err) { 17 | console.log('err', err) 18 | throw new Error(err.messages); 19 | } 20 | return result; 21 | } 22 | 23 | async update(id, data) { 24 | let result = {}; 25 | try { 26 | result = await this.Model.findByIdAndUpdate(id, {$set: data}, {new: true}); 27 | if (!result) { 28 | result = {failed: true, message: 'Comment not found'} 29 | } 30 | } catch (err) { 31 | throw new Error(err.messages); 32 | } 33 | return result; 34 | } 35 | 36 | async delete(id) { 37 | let result = {}; 38 | try { 39 | result = await this.Model.findByIdAndRemove(id); 40 | if (!result) { 41 | result = {failed: true, message: 'Comment not found'} 42 | } 43 | } catch (err) { 44 | throw new Error(err.messages); 45 | } 46 | return result; 47 | } 48 | 49 | async find(query) { 50 | let results = []; 51 | try { 52 | results = await this.Model.find(query); 53 | } catch (err) { 54 | throw new Error(err.messages); 55 | } 56 | return results; 57 | } 58 | 59 | async findById(id) { 60 | let result = {}; 61 | try { 62 | result = await this.Model.findById(id); 63 | if (!result) { 64 | result = {failed: true, message: 'Comment not found'} 65 | } 66 | } catch (err) { 67 | throw new Error(err.messages); 68 | } 69 | return result; 70 | } 71 | } 72 | 73 | module.exports = CommentStorage; -------------------------------------------------------------------------------- /api/graphql/posts/resolver.js: -------------------------------------------------------------------------------- 1 | const resolver = { 2 | Query: { 3 | async posts(root, args, {postStorage}) { 4 | let results = []; 5 | try { 6 | results = await postStorage.find(); 7 | } catch (err) { 8 | throw new Error('Error: find all posts'); 9 | } 10 | return results; 11 | }, 12 | async search(root, args, {postStorage, commentStorage}) { 13 | let results = []; 14 | try { 15 | const resultsPost = await postStorage.find({body: {$regex: new RegExp(args.search, 'i')}}); 16 | const resultsComments = await commentStorage.find({body: {$regex: new RegExp(args.search, 'i')}}); 17 | results = [...resultsPost, ...resultsComments] 18 | } catch (err) { 19 | throw new Error('Error: search comments and posts'); 20 | } 21 | return results; 22 | }, 23 | async postById(root, args, {postStorage}) { 24 | let result = {}; 25 | try { 26 | result = await postStorage.findById(args.id); 27 | } catch (err) { 28 | throw new Error('Error: find post by id'); 29 | } 30 | return result; 31 | } 32 | }, 33 | Post: { 34 | async comments({id}, args, {commentStorage}) { 35 | let result = []; 36 | try { 37 | result = await commentStorage.find({postId: id}); 38 | } catch (err) { 39 | throw new Error('Error: find comments'); 40 | } 41 | return result; 42 | } 43 | }, 44 | Mutation: { 45 | postAdd(root, args, {postStorage}) { 46 | let result = {}; 47 | try { 48 | result = postStorage.save({...args.data}); 49 | } catch (err) { 50 | throw new Error('Error: save post'); 51 | } 52 | return result; 53 | }, 54 | async postEdit(root, args, {postStorage}) { 55 | let result = {}; 56 | try { 57 | result = await postStorage.update(args.id, {...args.data}); 58 | } catch (err) { 59 | throw new Error('Error: update post'); 60 | } 61 | return result; 62 | }, 63 | async postDelete(root, args, {postStorage}) { 64 | let result = {}; 65 | try { 66 | result = await postStorage.delete(args.id); 67 | } catch (err) { 68 | throw new Error('Error: delete post'); 69 | } 70 | return result; 71 | } 72 | } 73 | }; 74 | 75 | module.exports = resolver; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Workshop graphql with nodejs 2 | 3 | ## Prerequisites 4 | 5 | - [nodejs 8.9.4 LTS](https://nodejs.org/es/download/) 6 | - [MongoDB](https://docs.mongodb.com/manual/tutorial/) 7 | 8 | 9 | ## Let's get started 10 | 11 | - Open the terminal and create a new folder for the project: ```mkdir workshop-graphql``` 12 | - Navigate to the root of the folder: ```cd workshop-graphql``` 13 | - Create the package.json file: ```npm init``` 14 | - Create a folder called api: ```mkdir api``` 15 | - Create a file called server.js: ```touch api/server.js``` 16 | 17 | 18 | ## Create Server 19 | 20 | - Install express ```npm i -S express``` 21 | - Install nodemon as dev dependency ```npm i -D nodemon``` 22 | - On the package.json we are going to add the following script ```"start": "nodemon api/server.js"``` 23 | - This will be our initial server.js 24 | 25 | ```js 26 | 'use strict' 27 | 28 | const express = require('express'); 29 | 30 | const app = express(); 31 | 32 | const port = 3000; 33 | 34 | app.listen(port, () => console.log(`Server is now running on http://localhost:${port}`)); 35 | ``` 36 | 37 | 38 | ## Create the Shows Entity 39 | 40 | 41 | - Install next dependencies ```npm i -S apollo-server-express body-parser graphql graphql-tools lodash.merge node-fetch``` 42 | - Create a folder called graphql: ```mkdir api/graphql``` 43 | - Create a folder called shows-tv: ```mkdir api/graphql/shows-tv``` 44 | - On the folder shows-tv we are going to create the following files: 45 | 46 | - Create a file called schema.js and added the next code 47 | ```js 48 | 'use strict' 49 | 50 | const schema = [` 51 | 52 | # show data 53 | type Show { 54 | id: Int, 55 | name: String, 56 | type: String, 57 | language: String 58 | genres: [String], 59 | status: String, 60 | runtime: Int, 61 | premiered: String, 62 | akas: [Aka] 63 | } 64 | 65 | type Aka { 66 | name: String, 67 | country: Country 68 | } 69 | 70 | type Country { 71 | name: String, 72 | code: String, 73 | timezone: String 74 | } 75 | 76 | type Query { 77 | shows: [Show] 78 | showById(id: ID!): Show 79 | } 80 | `]; 81 | 82 | module.exports = schema; 83 | ``` 84 | 85 | - Create a file called resolver.js and added the next code 86 | ```js 87 | 'use strict' 88 | 89 | const resolver = { 90 | Query: { 91 | shows(root, args, {showConnector}) { 92 | let results = []; 93 | 94 | try { 95 | results = showConnector.getShows(); 96 | } catch (err) { 97 | throw new Error('Error: find all shows') 98 | } 99 | 100 | return results; 101 | }, 102 | showById(root, args, {showConnector}) { 103 | let results = {}; 104 | 105 | try { 106 | results = showConnector.getShowById(args.id); 107 | } catch (err) { 108 | throw new Error('Error: find show by id') 109 | } 110 | 111 | return results; 112 | } 113 | }, 114 | Show: { 115 | akas({id}, args, {showConnector}) { 116 | let results = []; 117 | try { 118 | results = showConnector.getShowsAkas(id); 119 | } catch (err) { 120 | throw new Error('Error: find all akas') 121 | } 122 | 123 | return results; 124 | } 125 | } 126 | }; 127 | 128 | 129 | module.exports = resolver; 130 | ``` 131 | 132 | - Create a file called connector.js and added the next code 133 | ```js 134 | 'use strict' 135 | 136 | const fetch = require('node-fetch'); 137 | 138 | 139 | class ShowConnector { 140 | constructor(url = '') { 141 | if (!url) throw new Error()('The url is not provider'); 142 | this.API_ROOT = url; 143 | } 144 | 145 | async fetch(url) { 146 | const URL = `${this.API_ROOT}${url}`; 147 | const res = await fetch(URL).then(r => r.json()); 148 | 149 | if (res.errors) throw Error(res.message); 150 | return res; 151 | } 152 | 153 | getShows() { 154 | return this.fetch('/shows?page=1'); 155 | } 156 | 157 | getShowById(id) { 158 | return this.fetch(`/shows/${id}`); 159 | } 160 | 161 | getShowsAkas(id) { 162 | return this.fetch(`/shows/${id}/akas`); 163 | } 164 | } 165 | 166 | module.exports = ShowConnector; 167 | ``` 168 | 169 | ## Configure the global schema 170 | 171 | - On the folder graphql we are going to create a file called schema.js and added next code: 172 | ```js 173 | 'use strict' 174 | 175 | const merge = require('lodash.merge'); 176 | const {makeExecutableSchema} = require('graphql-tools'); 177 | 178 | const showSchema = require('./shows-tv/schema'); 179 | const showResolver = require('./shows-tv/resolver'); 180 | 181 | 182 | const typeDefs = [ 183 | ...showSchema 184 | ]; 185 | 186 | 187 | const resolvers = merge( 188 | showResolver 189 | ); 190 | 191 | 192 | const executableSchema = makeExecutableSchema({ 193 | typeDefs, 194 | resolvers 195 | }); 196 | 197 | module.exports = executableSchema; 198 | ``` 199 | 200 | ## Configure the graphql server 201 | 202 | - On the folder api we are going to create a file called config.js and added next code: 203 | ```js 204 | 'use strict' 205 | 206 | module.exports = { 207 | db: 'mongodb://localhost:27017/workshop-graphql', 208 | tvMazeUrl: 'http://api.tvmaze.com' 209 | }; 210 | ``` 211 | 212 | - On the file server.js we are going to added next code: 213 | 214 | - import the next files: 215 | ```js 216 | 217 | const bodyParse = require('body-parser'); 218 | const {graphqlExpress, graphiqlExpress} = require('apollo-server-express'); 219 | 220 | const config = require('./config'); 221 | const schema = require('./graphql/schema'); 222 | const ShowConnector = require('./graphql/shows-tv/connector'); 223 | ``` 224 | 225 | - Configure graphql and graphiql: 226 | ```js 227 | app.use(bodyParse.json()); 228 | 229 | app.use('/graphql', graphqlExpress(req => { 230 | return { 231 | schema, 232 | context: { 233 | showConnector: new ShowConnector(config.tvMazeUrl) 234 | } 235 | } 236 | })); 237 | 238 | app.get('/graphiql', graphiqlExpress({ 239 | endpointURL: '/graphql' 240 | })); 241 | ``` 242 | 243 | - The server.js file should be seen as follows: 244 | ```js 245 | 'use strict' 246 | 247 | const express = require('express'); 248 | const bodyParse = require('body-parser'); 249 | const {graphqlExpress, graphiqlExpress} = require('apollo-server-express'); 250 | 251 | const config = require('./config'); 252 | const schema = require('./graphql/schema'); 253 | const ShowConnector = require('./graphql/shows-tv/connector'); 254 | 255 | const app = express(); 256 | const port = 3000; 257 | 258 | app.use(bodyParse.json()); 259 | 260 | app.use('/graphql', graphqlExpress(req => { 261 | return { 262 | schema, 263 | context: { 264 | showConnector: new ShowConnector(config.tvMazeUrl) 265 | } 266 | } 267 | })); 268 | 269 | app.get('/graphiql', graphiqlExpress({ 270 | endpointURL: '/graphql' 271 | })); 272 | 273 | app.listen(port, () => console.log(`Server is now running on http://localhost:${port}`)); 274 | ``` 275 | 276 | 277 | ## Create the Post Entity 278 | 279 | - Install next dependencies: ```npm i -S mongoose bluebird``` 280 | - Create a folder called posts: ```mkdir api/graphql/posts``` 281 | - On the folder posts we are going to create the following files: 282 | 283 | - Create a file called schema.js and added the next code 284 | ```js 285 | 286 | 'use strict' 287 | 288 | const schema = [` 289 | 290 | # data to create post 291 | input PostInput { 292 | author: String!, 293 | email: String, 294 | title: String!, 295 | body: String! 296 | } 297 | 298 | # data to update post 299 | input PostEditInput { 300 | author: String, 301 | email: String, 302 | title: String, 303 | body: String 304 | } 305 | 306 | # data post 307 | type Post { 308 | id: ID, 309 | author: String, 310 | title: String, 311 | body: String, 312 | createdAt: String 313 | } 314 | 315 | extend type Query { 316 | # find all post 317 | posts: [Post] 318 | # find post by id 319 | postById(id: ID!): Post 320 | } 321 | 322 | type Mutation { 323 | # create post 324 | postAdd(data: PostInput): Post 325 | # update post 326 | postEdit(id: ID!, data: PostEditInput): Post 327 | # delete post 328 | postDelete(id: ID!): Post 329 | } 330 | `]; 331 | 332 | module.exports = schema; 333 | ``` 334 | 335 | - Create a file called resolver.js and added the next code 336 | ```js 337 | 'use strict' 338 | 339 | const resolver = { 340 | Query: { 341 | async posts(root, args, {postStorage}) { 342 | let results = []; 343 | try { 344 | results = await postStorage.find(); 345 | } catch (err) { 346 | throw new Error('Error: find all posts'); 347 | } 348 | return results; 349 | }, 350 | async postById(root, args, {postStorage}) { 351 | let result = {}; 352 | try { 353 | result = await postStorage.findById(args.id); 354 | } catch (err) { 355 | throw new Error('Error: find post by id'); 356 | } 357 | return result; 358 | } 359 | }, 360 | Mutation: { 361 | postAdd(root, args, {postStorage}) { 362 | let result = {}; 363 | try { 364 | result = postStorage.save({...args.data}); 365 | } catch (err) { 366 | throw new Error('Error: save post'); 367 | } 368 | return result; 369 | }, 370 | async postEdit(root, args, {postStorage}) { 371 | let result = {}; 372 | try { 373 | result = await postStorage.update(args.id, {...args.data}); 374 | } catch (err) { 375 | throw new Error('Error: update post'); 376 | } 377 | return result; 378 | }, 379 | async postDelete(root, args, {postStorage}) { 380 | let result = {}; 381 | try { 382 | result = await postStorage.delete(args.id); 383 | } catch (err) { 384 | throw new Error('Error: delete post'); 385 | } 386 | return result; 387 | } 388 | } 389 | }; 390 | 391 | module.exports = resolver; 392 | ``` 393 | 394 | - Create a file called model.js and added the next code 395 | ```js 396 | 'use strict' 397 | 398 | const mongoose = require('mongoose'); 399 | 400 | const schema = new mongoose.Schema({ 401 | title: { 402 | type: String, 403 | required: 'The title field is required' 404 | }, 405 | body: { 406 | type: String, 407 | required: 'The body field is required' 408 | }, 409 | author:{ 410 | type: String, 411 | required: 'The author field is required' 412 | }, 413 | email:{ 414 | type: String 415 | } 416 | }, {timestamps: true}); 417 | 418 | if (!schema.options.toJSON) schema.options.toJSON = {}; 419 | 420 | /** 421 | * Add a tranforma method to change _id by id 422 | * whent toJSON is used. 423 | */ 424 | schema.options.toJSON.transform = (doc, ret) => { 425 | // remove the _id of every document before returning the result 426 | ret.id = ret._id; 427 | delete ret._id; 428 | return ret; 429 | }; 430 | 431 | 432 | module.exports = mongoose.model('Post', schema); 433 | ``` 434 | 435 | - Create a file called storage.js and added the next code 436 | ```js 437 | 'use strict' 438 | 439 | require('./model'); 440 | 441 | class PostStorage { 442 | constructor(conn = null) { 443 | if (!conn) throw new Error('connection not provider'); 444 | 445 | this.Model = conn.model('Post'); 446 | } 447 | 448 | async save(data) { 449 | let result = {}; 450 | try { 451 | result = await new this.Model(data).save(); 452 | } catch (err) { 453 | throw new Error(err.messages); 454 | } 455 | return result; 456 | } 457 | 458 | async update(id, data) { 459 | let result = {}; 460 | try { 461 | result = await this.Model.findByIdAndUpdate(id, {$set: data}, {new: true}); 462 | if (!result) { 463 | result = {failed: true, message: 'Post not found'} 464 | } 465 | } catch (err) { 466 | throw new Error(err.messages); 467 | } 468 | return result; 469 | } 470 | 471 | async delete(id) { 472 | let result = {}; 473 | try { 474 | result = await this.Model.findByIdAndRemove(id); 475 | if (!result) { 476 | result = {failed: true, message: 'Post not found'} 477 | } 478 | } catch (err) { 479 | throw new Error(err.messages); 480 | } 481 | return result; 482 | } 483 | 484 | async find(query = {}) { 485 | let results = []; 486 | try { 487 | results = await this.Model.find(query); 488 | } catch (err) { 489 | throw new Error(err.messages); 490 | } 491 | return results; 492 | } 493 | 494 | async findById(id) { 495 | let result = {}; 496 | try { 497 | result = await this.Model.findById(id); 498 | if (!result) { 499 | result = {failed: true, message: 'Post not found'} 500 | } 501 | } catch (err) { 502 | throw new Error(err.messages); 503 | } 504 | return result; 505 | } 506 | } 507 | 508 | module.exports = PostStorage; 509 | ``` 510 | 511 | ## Configure the global schema 512 | 513 | - On the file schema.js we are going to added next code: 514 | ```js 515 | 516 | const postSchema = require('./posts/schema'); 517 | const postResolver = require('./posts/resolver'); 518 | 519 | 520 | const typeDefs = [ 521 | ...showSchema, 522 | ...postSchema 523 | ]; 524 | 525 | 526 | const resolvers = merge( 527 | showResolver, 528 | postResolver 529 | ); 530 | ``` 531 | 532 | ## Configure the post storage 533 | 534 | - On the file server.js we are going to added next code: 535 | ```js 536 | 537 | const mongoose = require('mongoose'); 538 | const Promise = require('bluebird'); 539 | 540 | const PostStorage = require('./graphql/posts/storage'); 541 | 542 | mongoose.Promise = Promise; 543 | const conn = mongoose.createConnection(config.db); 544 | 545 | app.use('/graphql', graphqlExpress(req => { 546 | return { 547 | schema, 548 | context: { 549 | showConnector: new ShowConnector(config.tvMazeUrl), 550 | postStorage: new PostStorage(conn) 551 | } 552 | } 553 | })); 554 | ``` 555 | - The server.js file should be seen as follows: 556 | ```js 557 | 'use strict' 558 | 559 | const express = require('express'); 560 | const mongoose = require('mongoose'); 561 | const Promise = require('bluebird'); 562 | const bodyParse = require('body-parser'); 563 | const {graphqlExpress, graphiqlExpress} = require('apollo-server-express'); 564 | 565 | const config = require('./config'); 566 | const schema = require('./graphql/schema'); 567 | const ShowConnector = require('./graphql/shows-tv/connector'); 568 | const PostStorage = require('./graphql/posts/storage'); 569 | 570 | const app = express(); 571 | const port = 3000; 572 | 573 | mongoose.Promise = Promise; 574 | 575 | const conn = mongoose.createConnection(config.db); 576 | 577 | app.use(bodyParse.json()); 578 | 579 | app.use('/graphql', graphqlExpress(req => { 580 | return { 581 | schema, 582 | context: { 583 | showConnector: new ShowConnector(config.tvMazeUrl), 584 | postStorage: new PostStorage(conn) 585 | } 586 | } 587 | })); 588 | 589 | app.get('/graphiql', graphiqlExpress({ 590 | endpointURL: '/graphql' 591 | })); 592 | 593 | app.listen(port, () => console.log(`server running in the port ${port}`)); 594 | ``` 595 | 596 | ## Create the Comment Entity 597 | 598 | - Create a folder called posts: ```mkdir api/graphql/comments``` 599 | - On the folder comments we are going to create the following files: 600 | 601 | - Create a file called schema.js and added the next code 602 | ```js 603 | 'use strict' 604 | 605 | const schema = [` 606 | 607 | # data to create comment 608 | input CommentInput { 609 | postId: ID!, 610 | name: String!, 611 | email: String, 612 | body: String! 613 | } 614 | 615 | type Comment { 616 | id: ID, 617 | post: Post, 618 | name: String, 619 | email: String, 620 | body: String 621 | } 622 | 623 | extend type Query { 624 | comments: [Comment] 625 | } 626 | 627 | type Subscription { 628 | commentAdd(data: CommentInput): Comment 629 | } 630 | `]; 631 | 632 | module.exports = schema; 633 | ``` 634 | 635 | - Create a file called resolver.js and added the next code 636 | ```js 637 | 'use strict' 638 | 639 | const resolver = { 640 | Query: { 641 | async comments(root, args, {commentStorage}) { 642 | let results = []; 643 | try { 644 | results = await commentStorage.find(); 645 | } catch (err) { 646 | throw new Error('Error: find all comment'); 647 | } 648 | return results; 649 | } 650 | }, 651 | Comment: { 652 | async post({postId}, args, {postStorage}) { 653 | let result = {}; 654 | if (!postId) return {}; 655 | try { 656 | result = await postStorage.findById(postId); 657 | } catch (err) { 658 | throw new Error('Error: find post'); 659 | } 660 | return result; 661 | } 662 | }, 663 | Subscription: { 664 | async commentAdd(root, args, {commentStorage}) { 665 | let result = {}; 666 | try { 667 | result = await commentStorage.save({...args.data}); 668 | } catch (err) { 669 | throw new Error('Error: save comment'); 670 | } 671 | return result; 672 | } 673 | } 674 | }; 675 | 676 | module.exports = resolver; 677 | ``` 678 | 679 | - Create a file called model.js and added the next code 680 | ```js 681 | 'use strict' 682 | 683 | const mongoose = require('mongoose'); 684 | 685 | const schema = new mongoose.Schema({ 686 | name: { 687 | type: String, 688 | required: 'The name field is required' 689 | }, 690 | body: { 691 | type: String, 692 | required: 'The body field is required' 693 | }, 694 | email: { 695 | type: String 696 | }, 697 | postId:{ 698 | type: mongoose.Schema.Types.ObjectId, 699 | required: 'The postId field is required' 700 | } 701 | }, {timestamps: true}); 702 | 703 | if (!schema.options.toJSON) schema.options.toJSON = {}; 704 | 705 | /** 706 | * Add a tranforma method to change _id by id 707 | * whent toJSON is used. 708 | */ 709 | schema.options.toJSON.transform = (doc, ret) => { 710 | // remove the _id of every document before returning the result 711 | ret.id = ret._id; 712 | delete ret._id; 713 | return ret; 714 | }; 715 | 716 | 717 | module.exports = mongoose.model('Comment', schema); 718 | ``` 719 | 720 | - Create a file called storage.js and added the next code 721 | ```js 722 | 'use strict' 723 | 724 | require('./model'); 725 | 726 | class CommentStorage { 727 | constructor(conn = null) { 728 | if (!conn) throw new Error('connection not provider'); 729 | 730 | this.Model = conn.model('Comment'); 731 | } 732 | 733 | async save(data) { 734 | let result = {}; 735 | try { 736 | result = await new this.Model(data).save(); 737 | } catch (err) { 738 | console.log('err', err) 739 | throw new Error(err.messages); 740 | } 741 | return result; 742 | } 743 | 744 | async update(id, data) { 745 | let result = {}; 746 | try { 747 | result = await this.Model.findByIdAndUpdate(id, {$set: data}, {new: true}); 748 | if (!result) { 749 | result = {failed: true, message: 'Comment not found'} 750 | } 751 | } catch (err) { 752 | throw new Error(err.messages); 753 | } 754 | return result; 755 | } 756 | 757 | async delete(id) { 758 | let result = {}; 759 | try { 760 | result = await this.Model.findByIdAndRemove(id); 761 | if (!result) { 762 | result = {failed: true, message: 'Comment not found'} 763 | } 764 | } catch (err) { 765 | throw new Error(err.messages); 766 | } 767 | return result; 768 | } 769 | 770 | async find(query) { 771 | let results = []; 772 | try { 773 | results = await this.Model.find(query); 774 | } catch (err) { 775 | throw new Error(err.messages); 776 | } 777 | return results; 778 | } 779 | 780 | async findById(id) { 781 | let result = {}; 782 | try { 783 | result = await this.Model.findById(id); 784 | if (!result) { 785 | result = {failed: true, message: 'Comment not found'} 786 | } 787 | } catch (err) { 788 | throw new Error(err.messages); 789 | } 790 | return result; 791 | } 792 | } 793 | 794 | module.exports = CommentStorage; 795 | ``` 796 | 797 | ## Configure the global schema 798 | 799 | - On the file schema.js we are going to added next code: 800 | ```js 801 | 802 | const commentSchema = require('./comments/schema'); 803 | const commentResolver = require('./comments/resolver'); 804 | 805 | 806 | const typeDefs = [ 807 | ...showSchema, 808 | ...postSchema, 809 | ...commentSchema 810 | ]; 811 | 812 | 813 | const resolvers = merge( 814 | showResolver, 815 | postResolver, 816 | commentResolver 817 | ); 818 | ``` 819 | 820 | ## Configure the Comment storage 821 | 822 | - On the file server.js we are going to added next code: 823 | ```js 824 | 825 | const CommentStorage = require('./graphql/comments/storage'); 826 | 827 | app.use('/graphql', graphqlExpress(req => { 828 | return { 829 | schema, 830 | context: { 831 | showConnector: new ShowConnector(config.tvMazeUrl), 832 | postStorage: new PostStorage(conn), 833 | commentStorage: new CommentStorage(conn) 834 | } 835 | } 836 | })); 837 | ``` 838 | - The server.js file should be seen as follows: 839 | ```js 840 | 'use strict' 841 | 842 | const express = require('express'); 843 | const mongoose = require('mongoose'); 844 | const Promise = require('bluebird'); 845 | const bodyParse = require('body-parser'); 846 | const {graphqlExpress, graphiqlExpress} = require('apollo-server-express'); 847 | 848 | const config = require('./config'); 849 | const schema = require('./graphql/schema'); 850 | const ShowConnector = require('./graphql/shows-tv/connector'); 851 | const PostStorage = require('./graphql/posts/storage'); 852 | const CommentStorage = require('./graphql/comments/storage'); 853 | 854 | const app = express(); 855 | const port = 3000; 856 | 857 | mongoose.Promise = Promise; 858 | 859 | const conn = mongoose.createConnection(config.db); 860 | 861 | app.use(bodyParse.json()); 862 | 863 | app.use('/graphql', graphqlExpress(req => { 864 | return { 865 | schema, 866 | context: { 867 | showConnector: new ShowConnector(config.tvMazeUrl), 868 | postStorage: new PostStorage(conn), 869 | commentStorage: new CommentStorage(conn) 870 | } 871 | } 872 | })); 873 | 874 | app.get('/graphiql', graphiqlExpress({ 875 | endpointURL: '/graphql' 876 | })); 877 | 878 | app.listen(port, () => console.log(`server running in the port ${port}`)); 879 | ``` --------------------------------------------------------------------------------