Build a Twitter Clone Server with Apollo, GraphQL, Nodejs, and Crowdbotics
In the last few years, GraphQL becomes a popular choice to build an API. It serves a great alternative to the REST APIs approach. Not only it is an open source application-layer query language, in comparison to REST, GraphQL fulfills the approach that a client request's only the desired set of information from the server in a single request.
In this tutorial, you will be building a server using Node.js, Express and Apollo server library. You will learn how to efficiently build a server from scratch that implements GraphQL as the query language to create the API. We will be using MongoDB to create a local instance of the database and store the application data.
To learn more about the basics of GraphQL, how it differs from REST, its building blocks like schema, resolvers, queries, and mutations, please refer to the previous post Creating a GraphQL Server with Nodejs. If you have an idea of what they are, you can continue to read this tutorial.
TLDR;
- Requirements
- Getting Started
- Running your first GraphQL server
- Adding a Mongoose Schema
- How to define GraphQL Types, Queries and Mutations
- Real-time Database Updates with GraphQL API
- Conclusion
Requirements
🔗In order to follow this tutorial, you are required to have installed the following on your local machine:
- Nodejs
v8.x.x
or higher installed along withnpm/yarn
as the package manager
Getting Started
🔗Create a new empty directory and initialize it with a package.json
file by running the following commands from a terminal window.
# create new directorymkdir twitter-clone-apollo-server# traverse inside the directorycd twitter-clone-apollo-server# initialize the package.json filenpm init --yes
The last command will create a package.json
file. We are going to build an integrated server using Expressjs and Apollo server.
Wait, what is an Apollo Server?
The Apollo Server is the server part of GraphQL where you manage and define a GraphQL API and handle responses that are sent back to the client in response to a network request.
When it comes to building a GraphQL API using Apollo on the server, there are two ways you can do that. One is called standalone which is irrespective of the server-side web framework (such as Express, Koa, Hapi and so on) using the apollo-server
library. This package is kept library-agnostic, so it is possible to connect it with a lot of third-party libraries in client and server applications.
Another way is to use the community maintained packages such as apollo-server-express
. Most popular HTTP Node.js frameworks are having their own community packages that you can check here. We are going to leverage this approach.
Run the following command to install the dependencies.
npm install --save express apollo-server-express graphql
To verify if these dependencies are installed, open package.json
file in your favorite code editor and you will get a similar result like below.
1{2 "name": "twitter-clone-apollo-server",3 "version": "0.0.1",4 "description": "",5 "main": "index.js",6 "scripts": {7 "test": "echo \"Error: no test specified\" && exit 1"8 },9 "keywords": [],10 "author": "Aman Mittal <amandeepmittal@live.com> (www.amanhimself.dev)",11 "license": "MIT",12 "dependencies": {13 "apollo-server-express": "^2.5.0",14 "express": "^4.17.0",15 "graphql": "^14.3.0"16 }17}
Running your first GraphQL server
🔗To understand how Apollo Server works, let us create a small bare minimum GraphQL server with the Express framework. Please note that, if you already know how to integrate Apollo server with express and create a hello world example, you are free to skip this section and move on to the next one.
At the root of your project, create a new file: index.js
with the following code. We start by requiring the required dependencies in order to create this server and make it work.
1const express = require('express');2const { ApolloServer, gql } = require('apollo-server-express');
Then define some constants such as for an instance of the Express application, app
and a port number where the local instance of the server is going to run. Note that, the port
is currently using an environment value plus the default value of 5000
as the local port number if there is not environment value provided via process.env.PORT
.
1const app = express();2const port = process.env.PORT || 5000;
The process.env
global variable is injected by the Node at runtime for your application to use and it represents the state of the system environment your application is in when it starts.
Next, we define a basic schema. A schema in GraphQL is defined at the server in the form of objects. Each object corresponds to data types such that they can be queried upon. This object type has a name and fields. Like the below snippet, there is Query
called hello
which is of type string.
1const typeDefs = gql`2 type Query {3 hello: String4 }5`;
There are pre-defined scalar types in GraphQL like the string in the above snippet. Visit this link to read more about them. Queries are what you use to make a request to a GraphQL API.
In order to execute this query, you need a resolver. Resolvers are the link between the schema and the data. They are the functions that contain the logic behind a query or mutation. They are used to retrieve data and return it on the relevant client request.
1const resolvers = {2 Query: {3 hello: () => 'Hello world!'4 }5};
In the above snippet, we are defining a resolver that will return the string Hello World
on querying the server. If you have built servers before using Express, you can think of a resolver as a controller where each controller is built for a specific route.
Lastly, we need to use a middleware function from the Apollo Server Express library to instantiate the GraphQL API.
1const server = new ApolloServer({ typeDefs, resolvers });23server.applyMiddleware({ app });
Here is the complete code for index.js
file.
1const express = require('express');2const { ApolloServer, gql } = require('apollo-server-express');34const app = express();5const port = process.env.PORT || 5000;67const typeDefs = gql`8 type Query {9 hello: String10 }11`;1213const resolvers = {14 Query: {15 hello: () => 'Hello world!'16 }17};1819const server = new ApolloServer({ typeDefs, resolvers });2021server.applyMiddleware({ app });2223app.listen(port, () =>24 console.log(25 `🚀 Server ready at http://localhost:${port}${server.graphqlPath}`26 )27);
Now, go back to the terminal window and run node index.js
command to trigger the server up and running. Visit http://localhost:5000/graphql
in a browser window to see that API endpoint in action. Apollo comes with a default playground in order to test the API.
Adding a Mongoose Schema
🔗Let us mongoose to create a MongoDB based database model inside the Express server app. To proceed, you will have to install the dependency first. Go to your terminal window, terminate the node index.js
command first and then run the following.
npm install -S mongoose
Once this dependency is installed, create a new folder called models
. Inside it, create a new file called TweetModel.js
. This will be responsible for holding the mongoose schema. For those of who do not know what mongoose, well, it is an ORM (object relation mapper) that helps the server app written in Node.js/Expressjs to communicate with the MongoDB database.
Mongoose allows you to define objects with a strongly typed schema that is mapped as a MongoDB collection. This schema architecture allows us to provide an organized shape to the document inside the MongoDB collection where the data is stored.
Start by importing the dependency at the top of the file and then connect the ORM to a local instance of the MongoDB database in the form of a URI: mongodb://localhost:27017/twitter
. The port 27017
is the default port number where MongoDB runs on your local dev machine irrespective of the operating system you are using. The /twitter
in the end is just the name of the database instance. You can name it anything. mongoose.connect()
the function takes this URI as the first argument.
To learn more about how to create and use MongoDB in the cloud using MongoDB atlas, read our earlier post here.
Open TweetModel.js
file and add the following.
1const mongoose = require('mongoose');23mongoose.Promise = global.Promise;4mongoose.connect('mongodb://localhost:27017/twitter', {5 useNewUrlParser: true6});78const Schema = mongoose.Schema;910const tweetSchema = new Schema({11 tweet: String,12 author: String13});1415const TweetModel = mongoose.model('Tweet', tweetSchema);
Notice the tweetSchema
object. It only contains three fields at the moment. The whole tweet
as a string, and the name of the author who tweets. It is important to take notice of this schema, later, when you are going to create GraphQL queries, this is the same schema pattern you will have to follow.
Now, let us define the CRUD operations that this current Tweet model is going to perform on the MongoDB instance. Add the below snippet of code to TweetModel.js
after you have defined the TweetModel
itself.
1export default {2 getTweets: () => TweetModel.find().sort({ _id: -1 }),3 getTweet: _id => TweetModel.findOne({ _id }),4 createTweet: args => TweetModel(args).save(),5 deleteTweet: args => {6 const { _id } = args;78 TweetModel.remove({ _id }, error => {9 if (error) {10 console.log('Error Removing: ', error);11 }12 });1314 return args;15 },16 updateTweet: args => {17 const { _id, tweet } = args;1819 TweetModel.update(20 { _id },21 {22 $set: { tweet }23 },24 { upsert: true },25 error => {26 if (error) {27 console.log('Error Updating: ', error);28 }29 }30 );3132 args.author = 'User123'; // temporary user3334 return args;35 }36};
Using these functions that are defined in the above code snippet, it will be possible to perform CRUD operations with the MongoDB database. These functions perform all sort of functions like getting a tweet by its id
, getting all tweets, creating a new tweet, and updating and deleting a specific tweet. The id
to each tweet document is going to be generated automatically by the MongoDB database. Each of these function is taking an argument by default and that is the name of the author of the tweet. To keep this demo approachable and bare minimum, the author name right now is hard coded.
How to define GraphQL Types, Queries and Mutations
🔗To define queries, mutations, and resolvers, create a new folder called api
. Inside this folder create two new files: Types.js
and Resolvers.js
.
Open Types.js
file and add the following snippet to add the type of the individual tweet based on the mongoose schema and our first mutation to create a new tweet.
1const { gql } = require('apollo-server-express');23const typeDefs = gql`4 # Type(s)56 type Tweet {7 _id: String8 tweet: String9 author: String10 }1112 # Query(ies)13 type Query {14 getTweet(_id: String): Tweet15 getTweets: [Tweet]16 }1718 # Mutation(s)1920 type Mutation {21 createTweet(tweet: String, author: String): Tweet2223 deleteTweet(_id: String): Tweet2425 updateTweet(_id: String!, tweet: String!): Tweet26 }27`;2829module.exports = typeDefs;
In the above snippet, we define the type of the Tweet
that will be used in every query and mutation. We are using gql
for the graphql template literal. You can comment inside the GraphQL template literal using a hash #
. Now open up the Resolvers.js
file and add the following.
1const TweetModel = require('../models/TweetModel');23const resolvers = {4 Query: {5 getTweet: _id => TweetModel.getTweet(_id),67 getTweets: () => TweetModel.getTweets()8 },910 Mutation: {11 createTweet: (_, args) => TweetModel.createTweet(args),1213 deleteTweet: (_, args) => TweetModel.deleteTweet(args),1415 updateTweet: (_, args) => TweetModel.updateTweet(args)16 }17};1819module.exports = resolvers;
In the above file, start by importing the TweetModel
since it will be used to extend GraphQL queries and mutations (defined above) to communicate with the MongoDB database in real time.
Currently, the index.js
file contains the typeDefs
and resolver from the previous hello world example. Let us import these two files from the api/
directory to replace them.
1const express = require('express');2const { ApolloServer } = require('apollo-server-express');34const typeDefs = require('./api/Types');5const resolvers = require('./api/Resolvers');67const app = express();8const port = process.env.PORT || 5000;910const server = new ApolloServer({11 typeDefs,12 resolvers13});1415server.applyMiddleware({ app });1617app.listen(port, () =>18 console.log(19 `🚀 Server ready at http://localhost:${port}${server.graphqlPath}`20 )21);
Now, go to the terminal window and run node index.js
command. If you get zero errors, that means your server is running successfully. Also, make sure if you are using the local instance of MongoDB database, make sure to run the command mongod
to kickstart the MongoDB service.
Real-time Database Updates with GraphQL API
🔗Once the server is running, visit the Apollo playground URL http://localhost:5000/graphql
and run the following mutation.
1mutation {2 createTweet(tweet: "👋 Hello", author: "User12") {3 _id4 tweet5 author6 }7}
On running this mutation you will get the following result.
Add a bunch of more tweets by running the above mutation again. Now, let us run a query to fetch all the tweets from the database.
1query {2 getTweets {3 _id4 tweet5 author6 }7}
Writing the keyword query
is an option only in the case of running a query. This is not possible in the case of running a mutation operation. You have to specify the keyword mutation
. The above query fetches the unique identifier of each tweet in the database as well as the tweet and the name of the author itself. See the result below.
To delete a tweet from the database, all you have to provide is the _id
of the tweet and provide sub-fields. Providing at least sub-field is necessary for the delete mutation to run, otherwise, it will throw an error.
1mutation {2 deleteTweet(_id: "5ce1a2f4f1ef7153d0fc7776") {3 tweet4 author5 }6}
You will get the following result.
Run the query to fetch all the tweets to see how many tweets are left.
The last operation is to update a tweet. Again, it is a mutation. All you to provide is the updated tweet in the form of a string and _id
of the tweet you want to update.
1mutation {2 updateTweet(3 _id: "5cd7cb5f19c9b4673f860600"4 tweet: "This could be my last tweet..."5 ) {6 _id7 tweet8 author9 }10}
The output you get:
By fetching all the tweets you can verify that the updating mutation worked!
Conclusion
🔗If you completed this tutorial, Congratulations!🎉
Not only did you learn how to configure and integrate an Express web server with the Apollo server library and MongoDB database. You ended up building complete CRUD functionality.
Apollo Server is an open source project and is one the most stable solution to create GraphQL APIs for full-stack applications. It also supports client-side out of the box for React, Vue, Angular, Meteor, and Ember as well as Native mobile development with Swift and Java.
You can find the complete code for the tutorial in this Github repository.
More Posts
Browse all posts