Gatsby offers high page score and favors great SEO. It becomes an excellent option to build static sites. But is that all?
Not really. You can also build pages that are able to collect user data. There are more than a few popular solutions out there, some of them don’t even require you to code anything.
But what if you don’t mind coding, you have your own server or you are one of those types that just build all by yourself? (Who? Me!!?)
This can be useful to build a comment section for your blog, to build a survey or just to save email addresses.
Can you have your Gatsby static site talking to your own server?
Yes, you can! Here is a solution that uses a reverse-proxy setup in NGINX and a minimum ExpressJS server to supercharge your static page.
This article contains affiliate links. If you click on it and make a purchase, I earn a small commission without any extra cost for you.
4.1. Project structure
4.2. Create server
4.3. Test in development
5.2. Backend server block
This tutorial is about deploying and application in production. There are many different ways to enable a reverse proxy server. Here you will see it done in a virtual machine with Ubuntu, NodeJS and NGINX.
Cloud hosting
When it comes to hosting websites there are multiple options out there, but each one comes with trade-offs. For me, price is important, but also the freedom to configure my servers in any way I see fit.
Digital Ocean satisfies those conditions. Initially I was worried about having to spend too much time setting it up, but the tutorials and the help of their community is remarkable.
Heroku is another hoster with traditionally good benefit over cost, but they configure everything for you. Some see that as an advantage, but it becomes problematic as soon as the project grows. I know from experience.
The link above contains a discount at Digital Ocean. It grants $100 of credit over 60 days towards any of their plans.
Start by installing the Gatsby CLI, if you haven’t already. Create a new site.
npm install -g gatsby-cli
gatsby new gatsby-plus-backend
cd gatsby-plus-backend
gatsby develop
Let’s build a small form to collect some user input.
Stop the build server (Ctrl-C again
) and bring back the develop version (gatsby develop
).
Create a file in the components folder with the following content:
import React, { useState } from "react"
const Form = () => {
const [title, setTitle] = useState('')
const [excerpt, setExcerpt] = useState('')
const [author, setAuthor] = useState('')
const handleSubmit = async e => {
e.preventDefault()
try {
let payload = {
poem: title,
excerpt: excerpt,
author: author
}
const response = await fetch('/api/save-poem/', {
method: "POST",
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "omit", // include, *same-origin, omit
headers: { "Content-Type": "application/json" },
redirect: "follow", // manual, *folslow, error
referrer: "client", // no-referrer, *client
body: JSON.stringify(payload),
})
const answer = await response.json()
if (answer.success) {
alert(answer.message)
}
else {
alert(answer.errors[0].msg)
}
}
catch (err) {
alert('Error connecting to backend:', err)
}
}
const handleTitleChange = e => {
setTitle(e.currentTarget.value)
}
const handleExcerptChange = e => {
setExcerpt(e.currentTarget.value)
}
const handleAuthorChange = e => {
setAuthor(e.currentTarget.value)
}
return (
<div className='backend'>
<form onSubmit={handleSubmit} >
<input placeholder='Poem title' id='title' onChange={handleTitleChange} />
<input placeholder='Excerpt' id='excerpt' onChange={handleExcerptChange} />
<input placeholder='Poem author' id='author' onChange={handleAuthorChange} />
<button type='submit'>Send</button>
</form>
</div>
)
}
export default Form
This is a small form with two input fields and a submit button. The fields are used to collect title and author of a poem.
Text is stored in the state as it is typed in the input fields. The send button sends the stored values to the save-poem endpoint, which you will create later.
All built with React hooks. Isn’t that beautiful?
Next, import your new component to the home page.
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
import Form from "../components/form" // highlight-line
And render it:
const IndexPage = () => (
<Layout>
<SEO title="Home" />
<h1>Hi people</h1>
<p>Welcome to your new Gatsby site.</p>
<p>Now go build something great.</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Form /> // highlight-line
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
export default IndexPage
Now we need a server to receive that call.
You will create a simple server with ExpressJS.
You have a choice to make.
It is possible to create the server inside the folder of your Gatsby project, but I don’t recommend it because when you deploy the server you will have to run npm install
in production, which your Gatsby project doesn’t need.
With Gatsby you can install all packages in your developer machine, run gatsby build
and send the resulting “public” folder to production.
And that is a good alternative because installing packages is a heavy task. If you can do it in your developer machine, then you may be able to save some money by acquiring a server that is not so powerful.
So, keeping your Express server in a separate folder will allow you to keep building your Gatsby site in your dev environment. And that is a major time - and money - saver.
Move out of your Gatsby project folder to create the server:
mkdir myserver
cd myserver
npm init
Follow the prompts to configure your project. Set the entry point to server.js
. Change the other default options if you want.
Install dependencies:
npm install express axios express-validator --save
Here you have ExpressJS, Axios to better handle requests, and Express-validator to give you some protection from ill intent.
The validator is not necessary nor is the objective of this tutorial, but handling user input is such a serious issue that I included it here.
Install also Nodemon to keep the server running in your development machine. Nodemon is great because it automatically restarts the server when you change something, saving some time.
Nodemon is a global dependency, so:
npm i -g nodemon
Create a very simple server:
const express = require('express')
const app = express()
const port = 3000
const bodyparser = require('body-parser')
const path = require('path')
// Middleware
const apiRoutes = require('./middleware/api.js')
// Bodyparser
app.use(bodyparser.urlencoded({ extended: true }))
app.use(bodyparser.json())
// Routing
app.use('/api', apiRoutes)
app.listen(port, () => console.log(`Server listening on port ${port}`))
The apiRoutes
in the middleware section is where you will write you endpoints. So create it:
const express = require('express')
const router = express.Router()
const axiosjs = require('axios')
const https = require('https')
const { body } = require('express-validator')
const { sanitizeBody } = require('express-validator')
const axios = axiosjs.create({
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
})
router.post('/save-poem', [
body('*')
.not().isEmpty()
.trim()
.escape(),
], (req, res) => {
console.log('New poem', JSON.stringify(req.body, 0, 2))
return res.status(200).json({
success: true,
message: 'Poem saved'
})
})
module.exports = router
With this code you are using an Express router to capture the post requests.
Axios handles the HTTP part. The validator is configured with the notEmpty, trim, and escape rules to clean up malicious text.
Then you output the request to the terminal and return a success message to the frontend.
In a real application, this is where you would save the data to a database.
For the purpose of checking your progress, add a GET route right before the /save-poem
endpoint:
const axios = axiosjs.create({
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
})
router.get('/welcome', (req, res) => { // highlight-line
return res.status(200).json({ // highlight-line
success: true, // highlight-line
message: 'Welcome to my api' // highlight-line
}) // highlight-line
}) // highlight-line
router.post('/save-poem', [
body('*')
])
Time to test your client / server setup in development environment.
Navigate to the root of your server project and run it with Nodemon:
cd myserver
nodemon server.js
It should say it is listening in port 3000.
Check it: open a browser and navigate to localhost:3000/api/welcome
. You should see the json object we created above for the welcome route.
{"success":true,"message":"Welcome to my api"}
Great. Now, as it is, Gatsby will address all API requests to static assets, which is what we need in production.
But to test the routes in development mode, we need to proxy the calls to the port we just opened with Nodemon.
Don’t worry, Gatsby has you covered with developmentMiddleware. Add this to your /gatsby-config
file:
var proxy = require("http-proxy-middleware") // highlight-line
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `@gatsbyjs`,
},
developMiddleware: app => { // highlight-line
app.use( // highlight-line
"/api/", // highlight-line
proxy({ // highlight-line
target: "http://localhost:3000" // highlight-line
}) // highlight-line
) // highlight-line
}, // highlight-line
plugins: [
`gatsby-plugin-react-helmet`,
From the root of your Gatsby project start it in development mode:
gatsby develop
Go to your browser and navigate to localhost:8000
. Add the following text to your input fields:
Poem title: The more loving one
Excerpt: If equal affection cannot be, let the more loving one be me
Poem author: W. H. Auden
Click on send and you should see an alert message attesting the poem was saved.
If you check the terminal window where you have Nodemon running, there too you should see a message with the text you added to your input fields.
Hooray! It works!
Now let’s deploy it.
This is but one way of deploying and application like this. In this example you will use NGINX on Ubuntu.
The installation of NGINX on Linux is not the objective of this tutorial. You can find detailed instructions in sites like Digital Ocean.
The key factor is the reverse proxy configuration that is responsible for serving frontend, backend, and allowing communication between them.
For this setup, build your Gastby site on your development machine. As discussed above, this is a great way to save resources on you production server. And then commit changes to your repository. So:
cd gatsby-plus-backend
gatsby build
git add .
git commit -m 'Ready for deployment'
git push
This way your repository will contain root files and two folders: “src” and “public”. The “public” folder is all you need to serve your frontend in production, so you could choose to commit only that.
For this tutorial and for simplicity, everything is going.
Now ssh into your server and create folders for your backend server and for your Gatsby frontend.
mkdir apps/frontend
mkdir apps/backend
Transfer your apps into these folders with Git.
Next create a link between your frontend public folder and the folder that NGINX uses to serve your site. This way, you don’t have to move files around every time you commit changes. Just pull with Git and you are good to go.
sudo ln /home/user/apps/frontend/public /var/www/mysite.com/html
Mind you that user and mysite.com will be different for you.
The backend can already be put to work. Start it with PM2:
pm2 start server.js --log-date-format="YYYY-MM-DD HH:mm Z"
Check if the server was properly started:
pm2 logs
If there are errors, type ctrl-c
to stop the logs and do:
pm2 ls
pm2 stop server
Now move on to the incantations necessary to create the server blocks for NGINX, starting with the frontend.
The example below serves with Certbot SSL certificates. If you need a hand setting those up, check out this tutorial.
server {
if ($host = www.mysite.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = mysite.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name mysite.com www.mysite.com;
return 404; # managed by Certbot
}
server {
listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl on;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
root /var/www/mysite.com/html;
index index.html index.htm index.nginx-debian.html;
server_name mysite.com www.mysite.com;
add_header Strict-Transport-Security max-age=500;
location / {
# First attempt to server request as file, then as directory, then fallback to 404.
try_files $uri $uri/ =404;
}
if ($scheme != "https") {
return 301 https://$host$request_uri;
} # managed by Certbot
}
As before, replace “mysite.com” with your URL.
This returns 301 for the port 80. For HTTPS, on port 443, it uses your SSL certificate and serves the contents of the mysite.com/html
folder.
At the bottom, the location /
bracket is the way you serve a static site with NGINX.
Now to the backend server block.
Still on the same file, add a new location entrance:
location / {
# First attempt to server request as file, then as directory, then fallback to 404.
try_files $uri $uri/ =404;
}
location /api {
proxy_set_header 'Access-Control-Allow-Origin' 'http://mysite.com';
proxy_set_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
proxy_set_header 'Access-Control-Allow-Headers' 'X-Requested-With,Accept,Content-Type,Origin';
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
proxy_buffering on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header origin `http://mysite.com';
}
if ($scheme != "https") {
return 301 https://$host$request_uri;
} # managed by Certbot
}
This block allows calls to your website /api
URL to find your local server on port 3000. Neat!
Restart NGINX
sudo systemctl restart nginx
sudo systemctl status nginx
Open a browser and visit your production URL.
Add a poem:
Poem title: To a mouse
Excerpt: The best laid schemes of mice and men go often askew
Poem author: Robert Burns
If all is good, you should see the confirmation alert. Congratulations! That was quite a feat.
You may also enjoy my other tutorial about migrating a blog from Wordpress to GatsbyJS.
Here is the repo for the Gatsby website: Gatsby-Express-NGINX
And this is the minimal Express server: Minimal-Gatsby-Backend
Questions? Let me know in the comments section.
November 12, 2019.