GraphQL Tutorial #5 – Introduction to Apollo GraphQL
In this tutorial, we will learn how to create one full-stack ‘todo’ application using Apollo GraphQL, Node and React. We will use ‘material ui’ library for building the UI in React.js, ‘Apollo’ for connecting to the GraphQL server, ‘express’ for building the graphql server and MongoDB as the database.
We will create two folders inside the main project folder : ‘server’ and ‘client’. ‘server’ is for storing the server side code and ‘client’ is for storing the client side code.
Server setup :
First of all, create one folder called ‘server’ and open one terminal inside the folder. Next, start one ‘npm’ project inside the folder using ‘npm init’ command. Hit enter for each configuration options. After the configuration is completed, it will create one ‘package.json’ file inside the folder.
We need to install the following dependencies on the server side :
graphql: This package is the JavaScript reference implementation for GraphQL
express-graphql: GraphQL HTTP server middleware to use with Express
express: Express framework
mongoose: It is the MongoDB object modelling tool
nodemon: It will automatically restart the server if any change is detected
Install these dependencies with the following command :
npm i graphql express-graphql express mongoose nodemon
It will install the latest dependencies inside the project folder. Following are the versions that are installed on my machine :
If you want to use the same versions, you can change them inside the package.json file.
For implementing the server, we need the following files :
1. Main server file
2. GraphQL Schema file
3. MongoDB model object files
Hosting the database:
You can host a MongoDB database on any website like MongoDB Atlas[https://www.mongodb.com/cloud/atlas], but for this tutorial, we are going to start one Mongo server locally. Make sure that you have MongoDB installed on your system. We will also use Robo3T as the MongoDB management tool.
Create one folder ‘db’ inside the root project directory and execute the following command to start MongoDB in this directory :
mongod --dbpath ./
It will print the port number where MongoDB is started:
waiting for connections on port 27017
You can try to open ‘http://localhost:27017’ on a browser to verify that the MongoDB is actually started on this port. It will print a message like below on the browser:
“It looks like you are trying to access MongoDB over HTTP on the native driver port.”
Start the server:
Create one folder ‘src’ and create one file ‘server.js’ in it. All our source code file will go inside this ‘src’ folder.
Open the ‘server.js’ file and add the below code in it :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const express = require("express"); const expressGraphQL = require("express-graphql"); const mongoose = require("mongoose"); const server = express(); mongoose.connect("mongodb://localhost:27017"); mongoose.connection.once("open", () => { console.log("connected to the MongoDB database"); }); server.listen(4000, () => { console.log("server is listening on 4000"); }); |
Inside the ‘package.json’ file, modify the ‘scripts’ block like below :
1 2 3 |
"scripts": { "dev":"nodemon ./src/server.js" } |
Now, in the root directory, execute the below command to start the server :
npm run dev
If everything goes well, you will see the below message in the terminal:
server is listening on 4000
connected to the MongoDB database
i.e. the server is running on port ‘4000’ and MongoDB is also connected.
Create the Models:
Our application will store a list of tasks and for that we need only one model object class. Create one new folder ‘models’ inside the ‘src’ folder and create one new file ‘task.js’ inside ‘src/models’ folder. Add the following code in this file :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const mongoose = require("mongoose"); const Schema = mongoose.Schema; const taskSchema = new Schema({ _id: { type: mongoose.Schema.Types.ObjectId, auto: true }, taskName: String }); module.exports = mongoose.model("TaskModel", taskSchema); |
i.e. our tasks will have one autogenerated ‘_id’ and one ‘taskName’.
Create the Schema:
We need the following query and mutation for our app:
Query:
– Fetch all tasks
– Fetch one single task with its id
Mutation:
– Add one task
– Delete one task
Let’s start with the fetch all tasks query. Create one folder called ‘schema’ inside the ‘src’ folder and create one file ‘schema.js’ in it and add the below code in this file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
const graphql = require("graphql"); const TaskModel = require("../models/task"); const { GraphQLObjectType, GraphQLList, GraphQLString, GraphQLSchema, GraphQLNonNull } = graphql; const Task = new GraphQLObjectType({ name: "Task", fields: () => ({ _id: { type: GraphQLID }, taskName: { type: GraphQLString } }) }); const Query = new GraphQLObjectType({ name: "Query", fields: { tasks: { type: new GraphQLList(Task), resolve(parent, args) { return TaskModel.find({}); } } } }); module.exports = new GraphQLSchema({ query: Query }); |
Here,
– we are using ‘graphql’ module and the model class ‘task’
– GraphQLObjectType, GraphQLList, GraphQLString, GraphQLSchema, GraphQLNonNull are used for creating a GraphQL object, list, string, schema and non-null value.
– ‘Task’ is the type of ‘task’ object
– ‘Query’ is used to hold all different types of queries. Currently, we have only one query ‘tasks’ to fetch the list of tasks from the MongoDB database. Inside the ‘resolve’ function, we are fetching all the tasks.
– Finally, we are exporting one GraphQL schema using the query.
Change the ‘server.js’ file as below to use the above schema in the express server :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
const express = require("express"); const expressGraphQL = require("express-graphql"); const mongoose = require("mongoose"); const schema = require("./schema/schema"); const server = express(); mongoose.connect("mongodb://localhost:27017"); mongoose.connection.once("open", () => { console.log("connected to the MongoDB database"); }); server.use( "/graphql", expressGraphQL({ schema, graphiql: true }) ); server.listen(4000, () => { console.log("server is listening on 4000"); }); |
It will start our server with one query ‘tasks’. Open “localhost:4000/graphql” on a browser window and try to execute the query like below :
As you can see that the server is returning an empty list of tasks.
Add, Delete a task :
We can add the other properties like add, delete, fetch single task similarly in the schema.js file.
The final schema.js file will look like as below :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
const graphql = require("graphql"); const TaskModel = require("../models/task"); const { GraphQLObjectType, GraphQLList, GraphQLBoolean, GraphQLString, GraphQLSchema, GraphQLID, GraphQLNonNull } = graphql; const Task = new GraphQLObjectType({ name: "Task", fields: () => ({ _id: { type: GraphQLID }, taskName: { type: GraphQLString } }) }); const Query = new GraphQLObjectType({ name: "Query", fields: { tasks: { type: new GraphQLList(Task), resolve(parent, args) { return TaskModel.find({}); } }, task: { type: Task, args: { taskId: { type: GraphQLID } }, resolve(parent, args) { return TaskModel.findById(args.taskId); } } } }); const Mutation = new GraphQLObjectType({ name: "Mutation", fields: { addTask: { type: Task, args: { taskName: { type: new GraphQLNonNull(GraphQLString) } }, resolve(parent, args) { let task = new TaskModel({ taskName: args.taskName }); return task.save(); } }, deleteTask: { type: GraphQLBoolean, args: { taskId: { type: GraphQLID } }, resolve: async (parent, args) => { const result = await TaskModel.deleteOne({ _id: args.taskId }); if (result.n === 0) { return false; } else { return true; } } } } }); module.exports = new GraphQLSchema({ query: Query, mutation: Mutation }); |
Here, we have defined two queries and two mutations. ‘tasks’ is for retrieving all tasks, ‘task’ is for retrieving one single task, ‘addTask’ is for adding a task and ‘deleteTask’ is for deleting a task.
Restart the server and try the below queries on GraphiQL :
a) Add a task :
1 2 3 4 5 |
mutation { addTask(taskName: "Todo task 1") { taskName } } |
b) Get all tasks :
1 2 3 4 5 6 |
{ tasks { _id taskName } } |
c) Get one single task :
1 2 3 4 5 6 7 |
{ task(taskId: "5cd9414231f7c46c4e12a184") { taskName _id } } |
d) Delete one task:
1 2 3 |
mutation { deleteTask(taskId: "5cd9414231f7c46c4e12a184") } |
Client React application:
Our final Application will look like below :
Using the ‘ADD’ button, you can add any new task. The task will be saved in MongoDB as explained above. Similarly, with the ‘DELETE’ button, you can delete a specific task.
First of all, move to the root folder and create one new react app ‘client’ using the ‘create-react-app’ cli :
create-react-app client
We are going to use material ui in this application. Following are the dependencies we need to install using ‘npm install’ command :
1 2 3 4 5 6 7 8 9 10 |
"dependencies": { "@material-ui/core": "^3.9.3", "@material-ui/icons": "^3.0.2", "apollo-boost": "^0.3.1", "graphql": "^14.3.0", "react": "^16.8.6", "react-apollo": "^2.5.5", "react-dom": "^16.8.6", "react-scripts": "3.0.1" } |
Install these dependencies, move to the ‘client’ folder and start the app using ‘npm start’ command. It will run the app on ‘localhost’ ‘3000’ port.
We need to add three React components for the application :
AddTaskComponent : Component for adding a new task
TaskComponent : Component for showing all tasks
TaskComponent : Component for each task with the delete button
Also, we need one ‘queries’ file to list down all the graphql queries.
Let’s create these files inside the ‘client/src/’ folder with the following changes :
queries.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import { gql } from "apollo-boost"; const getTasks = gql` { tasks { _id taskName } } `; const addTask = gql` mutation($taskName: String!) { addTask(taskName: $taskName) { taskName } } `; const deleteTask = gql` mutation($taskId: ID!) { deleteTask(taskId: $taskId) } `; export { getTasks, addTask, deleteTask }; |
Here, we have created one query ‘getTasks’ and two mutations ‘addTask’ and ‘deleteTask’. You can see that the queries and mutations are the same as we have used in the ‘graphiql’ tool above.
AddTaskComponent.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import "./App.css"; import TextField from "@material-ui/core/TextField"; import Button from "@material-ui/core/Button"; import React, { Component } from "react"; import CircularProgress from "@material-ui/core/CircularProgress"; import { graphql, compose } from "react-apollo"; import { addTask, getTasks } from "./queries"; class AddTaskComponent extends Component { constructor(props) { super(props); this.state = { loading: false, text: "" }; } addTask() { this.props.addTask({ variables: { taskName: this.state.text }, refetchQueries: [{ query: getTasks }] }); } render() { return ( <div> <TextField id="outlined-full-width" label="Add Task" style={{ margin: 8 }} placeholder="Add new task" fullWidth onChange={e => this.setState({ text: e.target.value })} margin="normal" variant="outlined" InputLabelProps={{ shrink: true }} /> {!this.state.loading ? ( <Button variant="contained" color="primary" onClick={e => { this.addTask(); }} > Add </Button> ) : ( <CircularProgress /> )} </div> ); } } export default compose(graphql(addTask, { name: "addTask" }))(AddTaskComponent); |
Here, the ‘addTask’ function is used to add a new task.
‘this.props.addTask’ will call the ‘addTask’ mutation with ‘taskName’ parameter. ‘this.state.text’ is the text that the user has entered. Once this mutation is completed, it will call the ‘getTasks’ query to fetch the tasks again.
TaskComponent.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import "./App.css"; import React, { Component } from "react"; import CircularProgress from "@material-ui/core/CircularProgress"; import { graphql, compose } from "react-apollo"; import Grid from "@material-ui/core/Grid"; import { getTasks, deleteTask } from "./queries"; import TaskListElement from "./TaskListElement"; class TaskComponent extends Component { deleteTask = id => { this.props.deleteTask({ variables: { taskId: id }, refetchQueries: [{ query: getTasks }] }); }; fetchTasks() { var data = this.props.getTasks; if (data.loading) { return <CircularProgress />; } else { return data.tasks.map(task => { return ( <TaskListElement key={task._id} taskId={task._id} taskName={task.taskName} deleteTask={this.deleteTask} /> ); }); } } render() { return ( <div style={{ padding: 20 }}> <Grid container alignItems="center" direction="column"> {this.fetchTasks()} </Grid> </div> ); } } export default compose( graphql(getTasks, { name: "getTasks" }), graphql(deleteTask, { name: "deleteTask" }) )(TaskComponent); |
This component is used for listing down all tasks. ‘fetchTasks’ is used to fetch all tasks and ‘deleteTask’ is used for deleting a task. Actually ‘TaskListElement’ will call ‘deleteTask’ method. “TaskListElement” component is for each task with a ‘Delete’ button.
TaskListElement.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import React, { Component } from "react"; import Paper from "@material-ui/core/Paper"; import Typography from "@material-ui/core/Typography"; import "./App.css"; import Button from "@material-ui/core/Button"; import Grid from "@material-ui/core/Grid"; import { withStyles } from "@material-ui/core/styles"; const styles = theme => ({ button: { margin: theme.spacing.unit }, paper: { padding: theme.spacing.unit * 2, margin: "10px", width: "50%" } }); class TaskListElement extends Component { render() { const { classes } = this.props; return ( <Paper className={classes.paper}> <Grid container spacing={16}> <Grid item xs={12}> <Typography variant="h6">{this.props.taskName}</Typography> <Grid item> <Button variant="contained" color="secondary" className={classes.button} onClick={e => { this.props.deleteTask(this.props.taskId); }} > Delete </Button> </Grid> </Grid> </Grid> </Paper> ); } } export default withStyles(styles)(TaskListElement); |
This component is used for showing each task. We are using it inside ‘TaskComponent’.
For using ‘Apollo’ in our app, we need to make a little bit change to the ‘App.js’ file :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import React from "react"; import AppBar from "@material-ui/core/AppBar"; import Toolbar from "@material-ui/core/Toolbar"; import Typography from "@material-ui/core/Typography"; import "./App.css"; import TaskComponent from "./TaskComponent"; import ApolloClient from "apollo-boost"; import { ApolloProvider } from "react-apollo"; import AddTaskComponent from "./AddTaskComponent"; const client = new ApolloClient({ uri: "http://localhost:4000/graphql" }); function App() { return ( <ApolloProvider client={client}> <div className="App"> <AppBar position="static"> <Toolbar variant="dense"> <Typography variant="h6" color="inherit"> To Do </Typography> </Toolbar> </AppBar> <AddTaskComponent /> <TaskComponent /> </div> </ApolloProvider> ); } export default App; |
We have created one ‘ApolloClient’ to connect to the ‘graphql’ server and wrapped the application with ‘ApolloProvider’ component.
App.css :
1 2 3 4 5 6 7 |
.App { text-align: center; } .body { margin: 15px 35px 35px 35px; } |
That’s it. Here we have completed our Apollo GraphQL full stack application. Make sure to run the express server and MongoDB with the application. You can check the database using Robo3T to learn more about how the database is adding data.
Tutorial Index:
- GraphQL Tutorial #1 -Introduction
- GgraphQL Tutorial #2 – Setting Basic Project in GraphQL
- GraphQL Tutorial #3 – GraphQL Components
- GraphQL Tutorial #4 – GraphQL Operations
- GraphQL Tutorial #5 – Introduction to Apollo GraphQL
- GraphQL Tutorial #6 – 51 Most Important GraphQL Interview Q&A