Contact me Part I — The Server
Building a web server with NodeJS, GraphQL, Express.
Overview:
A contact app is essential in any informational website. Whether it’s a large scale business website, or a small personal website, it is absolutely crucial for your audience to get in touch with you. Applying feedbacks and answering questions are important to build a good long lasting relationship with your audience. Besides, its plain fun to build this app and I’m going to help you build it.
We will build a simple app where users can enter their email and a message they want to send, which you will receive directly in your inbox. To simplify the process, we will build the server and the client separately. In this part of the tutorial, we will build the server.
The server’s job will be to get message from the client and send it as an email to your email address. We will use Node.js and Express to write our server and GraphQL to handle our queries. To send emails from the server, we will use an external api from SendGrid. Now we know what we are doing, let’s dive right on in.
Setup:
To get started, you will need node and yarn installed on your system . Let’s start by first creating a new directory in your projects directory and changing directory to our newly created directory.#directory
mkdir contactMe
cd contactMe
Now, create a server folder and change directory to the server.
mkdir server
cd server
We are going through all these steps because I like to keep my project files organized. Inside the server folder, we will create a package.json
file to use node modules and run our server on node. We will use the yarn package manager to handle the node modules. First step, initialize yarn.
Next, we need to create an index.js
file as our entry point of the server.
Express:
Express is a minimalist web framework for Node.js. We can use its simple API to build and run our server quickly. First, we need to add it in our app.
yarn add express
Notice that after you run the command above, there is a new file called yarn.lock
in your root directory. This file is used by yarn to store the last known good versions of the dependencies that you installed. This file should not be edited directly.
const express = require('express')
const PORT = 4000
const app = express()
test
app.use('/', (req, res) => {
res.send("Hello World!")
})
app.listen(PORT, () => console.log(`App listening on port ${PORT}`))
Next step is to write a simple express server:
I recommend running nodemon to run the development server because it restarts the app every time you update your code.
nodemon
If everything goes well up until now then you should see this message in your console:
App listening on port 4000
Now if you open localhost:4000
in your browser, you should see your greeting. If yes, then its time to add GraphQL.
GraphQL:
GraphQL is a query language that we will use as an API layer to handle communications between the client and the server. To create a GraphQL server, we will use the apollo-server
package. This will help our client in the next part of the tutorial to send requests using the Apollo client.
In addition to the apollo-server
package, we also need graphql
and body-parser
. We can install all of them with the following command:
yarn add graphql apollo-server-express body-parser
What are these packages for?
Let’s look at them individually:
apollo-server-express
is anapollo-server
module specifically for Express.body-parser
is required for parsing request body to json, specifically for mutation requests.graphql
is a peer-dependency required by theapollo-server
.
Next, we will import these packages in our server and use them to instantiate our new graphqlExpress
server.
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress } = require('apollo-server-express')
const PORT = 4000
const app = express()
app.use('/', (req, res) => {
res.send("Hello World!")
})
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })
app.listen(PORT, () => console.log(`App listening on port ${PORT}`))
Schema:
To fix the above error, we need to define the schema. For that, we need to a package called graphql-tools
in our server.
yarn add graphql-tools
This package helps us use various features of GraphQL, like resolvers
, which we will use later and makeExecutableSchema
. The makeExecutableSchema
, as the name suggests, will return an executable schema that can run with graphqlExpress
. The function requires two arguments:
typeDefs
: Which contains the definitions of our GraphQL schema types.resolvers
: Which contains functions that resolves each mutations and queries for our app.
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const PORT = 4000
const app = express()
app.use('/', (req, res) => {
res.send("Hello World!")
})
const schema = makeExecutableSchema({ typeDefs, resolvers )}
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })
app.listen(PORT, () => console.log(`App listening on port ${PORT}`))
Notice that we still haven’t defined typeDefs and resolvers. In the spirit of being organized, we will import them from a different file. For that we should create new files named typeDefs.js
and resolvers.js
in our root directory.
touch typeDefs.js resolvers.js
In the typeDefs.js
file, we can define all our schema types and return it as a string. The payload we will receive from the client will have an email address and a message. That means, we can design our schema accordingly.
module.exports = `
type Message {
email: String!,
message: String!
}
`
We can now import our typedefs
and resolvers
to be used as arguments for our makeExecutableSchema
function.
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const typeDefs = require('./typeDefs')
const resolvers = require('./resolvers')
const PORT = 4000
const app = express()
app.use('/', (req, res) => {
res.send("Hello World!")
})
const schema = makeExecutableSchema({ typeDefs, resolvers )}
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })
app.listen(PORT, () => console.log(`App listening on port ${PORT}`))
If everything goes well, you should see the following error in your console:
Error: Must provide schema definition with query type or a type named Query.
This error occurs because a schema definition exclusively needs a type named Query. For now we will add a query type definition containing a resolver function allMessage()
.
module.exports = `
type Message {
email: String!,
message: String!
}
type Query {
allMessage: Message
}
`
We now need to define the allMessage()
function which we can do in our resolvers.js
file.
module.exports = {
Query: {
allMessage: () => console.log('Send all messages.')
}
}
For now, if we send a query request for our Message schema, it will display the following message in our console.
Send all messages.
So, how do I send a query request?
Right, to answer that, let me introduce you to GraphQL playground.
GraphQL Playground:
apollo-server-express
comes equipped with a module called GraphiQL which acts as a client to test queries and mutations. We can most definitely use that, but we are going to be a little extra and add a middleware called expressPlayground
which we can get from the graphql-playground-middleware
library. This provides all the necessary functionalities GraphiQL interface plus some extra features which will make development way more convenient.
yarn add graphql-playground-middleware
Next, in our index.js
file:
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const { expressPlayground } = require('graphql-playground-middleware')
const typeDefs = require('./typeDefs')
const resolvers = require('./resolvers')
const PORT = 4000
const app = express()
const schema = makeExecutableSchema({ typeDefs, resolvers )}
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })
app.get('/playground', expressPlayground({ endpoint: '/graphql' }))
app.listen(PORT, () => console.log(`App listening on port ${PORT}`))
We can use any route but we will stick with convention and use ‘/playground’
. The expressPlayground
middleware requires an object as an argument containing the ‘/graphqlendpoint'
which we defined already. After this, we need to remove the main ‘/‘
route to avoid any interference with the ‘/graphql’
or ‘/playground’
routes. Then visit localhost:4000/playground
in your browser, which should open a GraphQl playground interface where you can experiment with queries and mutations for our app.
Query:
Let’s try a simple query in our playground environment:
{
allMessage{
email
message
}
}
We should see the following message in our console, as defined in our resolvers.js
file.
all message
If you do see the message in your console, it means that you are ready to send mutation requests to the server using the playground.
Mutation:
If you are familiar with REST API, mutation is everything but Read in CRUD. In other words, mutation includes creating, updating or deleting data. For our mutation, we need to define our mutation type in the typedef string.
module.exports = `
type Message {
email: String!,
message: String!
}
type Query {
allMessage: Message
}
type Mutation {
createMessage (
email: String!,
message: String!
): Message
}
`
This is basically a function declaration that we can resolve in our resolvers.js
. All mutations require the following things:
- Function Name:
createMessage()
- Argument Schema:
(email: String!, message: String!)
- Schema type it points to:
createMessage(): Message
In resolvers.js
, we can add a new property Mutation and define our createMessagefunction. The function requires arguments that we passed when we called the mutation.
module.exports = {
Query: {
allMessage: () => console.log('Send all messages.')
},
Mutation: {
createMessage: ( _, payload) => console.log('payload', payload)
}
}
In the code above, the payload now contains our email and string and should print in the console if everything goes well. Notice that the first argument in our mutation resolver function is _
. We did that because the mutation function takes an object in the first argument which often we do not use at all.
Now to test this, let’s request the following mutation. Also a reminder, we can do this in a different tab in the playground which enables us to easily navigate between several queries.
mutation {
createMessage(
email: "email@example.com",
message: "How u doin?"
) {
email
message
}
}
Notice the query fields after the mutation call { email, message }
. We need these fields because even though it’s a mutation, it still follows the same rules of a query. That means it needs fields to return some data. Common convention is to return what you just updated in the server so the client can update too. For our app, we don’t need to return any data so we will leave the code like this.
If this works, you should see your payload printed in your console. Next we are going to write our last file which will help us send emails. There are various email API services but we will use SendGrid for our app. Let’s first create the file:
touch sendMeEmail.js
We need to write the file in the next step but first, let’s import and use it in our mutation.
const sendMeEmail = require('./sendMeEmail')
module.exports = {
Query: {
allMessage: () => console.log('Send all messages.')
},
Mutation: {
createMessage: ( _, payload) => sendMeEmail(payload)
}
}
Let’s get the file working with some simple console.log
statements. Here, we are destructuring the payload object to get the individual values that we need to send us the email.
module.exports = ({ email, message }) => {
console.log('email', email)
console.log('message', message)
}
If all of this works, you should see your email and message printed in your console. If yes, then we are ready to play with SendGrid.
SendGrid:
SendGrid is a SaaS, whose API we can use to send our email. For our app, we will use the most basic version of the app because all we need is the API to send an email. First thing, go to sendgrid.com and signup. Then visit the guides page in app.sendgrid.com/guide. In that page we will click start for Integrate using our Web API
. In the language section, we are going to choose node. There, type in your apps name, I am going to use 'messageMe' and press the Create Key
button. This should create a new API key we can integrate in our app.
I recommend not pushing the API key in your repository because well it’s easy to get stolen. For our local environment we will use the current terminal environment to save the API key. We can use the following command:
export SENDGRID_API_KEY=YOUR_API_KEY
To quickly test if our API key is working, add the following console.log
statement in index.js
file
console.log('process.env.SENDGRID_API_KEY:',
process.env.SENDGRID_API_KEY)
If you see your API key on the console, well you’re good to go. Keep in mind that this key will only work for the current terminal process. Meaning, if you open a new terminal window, the variable will be undefined
. If the code above is working, then remove the console.log
statement and proceed on to write the sendMeEmail.js
file.
sendMeEmail.js
First thing, since we are only sending an email, we will use the API that does only that:
yarn add @sendgrid/mail
Next, we have to import the package and initialize it with our API key.
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
module.exports = ({ email, message }) => {
console.log('email', email)
console.log('message', message)
}
Next step is to use the send()
method from SendGrid which takes an object as an argument that we can fill out as follows:
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
module.exports = ({ email, message }) => {
const msg = {
to: YOUR_EMAIL_ADDRESS,
from: email,
subject: 'Message from messageMe app',
text: message
}
sgMail.send(msg)
}
In the code, use:
- to: Address where you want to receive emails.
- from: The email address provided by the user.
- subject: For now it is static and mentions that the message is from this app
- text: The message the user wants to send us.
Finally, the send()
method will send the message to your email. To test it out, send another mutation request from the GraphQL playground. You should be getting an email in your email address. How cool is that?
Preparing to Deploy:
Whenever I make an app, I feel it’s complete only after it’s deployed. We are going to do the same with our app. For deploying, I like using heroku because its super simple.
There are a few things we are missing in our app that we need to do for it run in heroku properly. First we need to add a start script that will start our application. In package.json
, add the following:
"scripts": {
"start": "node index.js"
}
Next, we need to set a port that heroku can use. In index.js, add:
const PORT = process.env.PORT || 4000
This is necessary for deployment using any of the services because each of them have their own port in their server which we can access from the environment variable.
Finally, this step is not necessary yet but will be when we are trying to access the https server from our front end. To get rid of CORS errors, we are going to use the cors
middleware.
yarn add cors
This step is required because we are going to create our client from a different server and accessing our API will result in CORS error. To enable CORS, we will add the cors middleware in our index.js
file:
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const { expressPlayground } = require('graphql-playground-middleware')
const cors = require('cors')
const typeDefs = require('./typeDefs')
const resolvers = require('./resolvers')
const PORT = process.env.PORT || 4000
const app = express()
app.use(cors())
const schema = makeExecutableSchema({ typeDefs, resolvers )}
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })
app.get('/playground', expressPlayground({ endpoint: '/graphql' }))
app.listen(PORT, () => console.log(`App listening on port ${PORT}`))
Now we are all set for deployment.
Heroku:
Next, we are going to deploy our server on heroku. For that you need heroku-cli
that you can download form the heroku website. After that, go to your terminal and login if you have not yet.
heroku login
Next, we need to initialize git
because heroku uses git
to deploy.
git init
git add .
git commit -m "Ready to deploy"
Now, we can use heroku to create a new app:
heroku create
I am going to go with whatever incongruous name heroku provides with. Next, we need to push our commit to heroku:
git push heroku master
After the process completes, we need to add an environment variable for our SendGrid API key. We can do this in heroku.com using their dashboard, or using the following command:
heroku config:set SENDGRID_API_KEY=YOUR_API_KEY
Now, open the app:
heroku open
This should open a page with an error in your browser:
Cannot GET /
That’s perfect because we don’t have the '/'
route configured. However, we can access '/playground'
and send in our mutation. If we get the email, well, we are up and running now. Ready to get started on the front end.
In a nutshell:
A contact app is a small app but is required in many different types of websites. There are various ways and technologies you can use to build this type of app and that’s exactly what we did here. We now not only have the backend for the app, but also a general understanding of how to create a GraphQL server with Node.js and how to use send GraphQL queries using the GraphQL playground to build a complete backend app independent of it’s frontend counterpart.