Builder Book logo

Book: Builder Book

  1. Introduction
  2. Set up Node.js project. VS code editor and lint. Set up Next.js project. Material-UI integration. Server-side rendering. Custom styles.
  3. HTTP. Express server. Next-Express server, nodemon. Index.getInitialProps. User data model and mongoose. MongoDB database and dotenv. Testing server-database connection. Retrieving document. Session and cookie. MenuWithAvatar and Header components.
  4. Authentication HOC. getInitialProps method. Login page and NProgress. Asynchronous execution. Promise.then. async/await. Google Oauth API infrastructure. setupGoogle, verify, passport, strategy. Express routes /auth/google, /oauth2callback, /logout. generateSlug. this. Set up at Google Cloud Platform.
  5. Testing method with Jest. Transactional email API with AWS SES service. Set up AWS SES service, security credentials. sendEmail method. Export and import syntax for server code. EmailTemplate data model. Update User.signInOrSignUp. Informational success/error messages. Notifier component. notify method.
  6. Book data model. Chapter data model. MongoDB index. API infrastructure and user roles. Read chapter API.
  7. Set up Github API infrastructure. Sync content API infrastructure. Missing UI infrastructure for Admin user. Two improvements. Testing.
  8. Table of Contents. Sections. Sidebar. Toggle TOC. Highlight for section. Active section. Hide Header. Mobile browser.
  9. BuyButton component. Buy book API infrastructure. Setup at Stripe dashboard and environmental variables. isPurchased and ReadChapter page. Redirect. My books API and MyBooks page. Mailchimp API.
  10. Prepare project for deployment. Environmental variables, production/development. Logger. SEO, robots.txt, sitemap.xml. Compression and security. Deploy project. Heroku. Testing deployed project. AWS Elastic Beanstalk.

Chapter 9: Prepare project for deployment. Environmental variables, production/development. Logger. SEO, robots.txt, sitemap.xml. Compression and security. Deploy project. Heroku. Testing deployed project. AWS Elastic Beanstalk.

We regularly update the codebase with recent syntax and stable versions for libraries. The latest update was May 2021.


The section below is a preview of Builder Book. To read the full text, you will need to purchase the book.


In Chapter 9, you'll start with the codebase in the 9-begin folder of our builderbook repo and end up with the codebase in the 9-end folder. We'll cover the following topics in this chapter:

  • Prepare project for deployment
    - Environmental variables, production/development
    - Logger
    - SEO: robots.txt, sitemap.xml
    - Google Analytics
    - Compression and security
    - Express route /_next/*

  • Deploy project
    - Heroku
    - Testing deployed project
    - AWS Elastic Beanstalk


In the previous chapter, Chapter 8, you learned about Stripe API and implemented infrastructure that allows our application to sell content (for example, a book). You also learned abour Mailchimp API so your application adds the email address of a signed-up user and buyer to a corresponding Mailchimp list/audience.

In this last chapter of the book, Chapter 9, we will prepare our application for a production environment and deploy our application to two hosting services: Heroku and AWS Elastic Beanstalk.


Prepare project for deployment link

So far, we've been running our app locally at http://localhost:8000. If you print the value for process.env.NODE_ENV and run our application locally - you will see undefined on your logs (either browser or server). The value of dev, thus, is true because of how we defined it:

const dev = process.env.NODE_ENV !== 'production';

To deploy our app, we need to:
- set the root URL ROOT_URL to https://builderbook.org instead of http://localhost:8000
- set the process.env.NODE_ENV value to production (sometimes hosting service sets value to production by default)
- when NODE_ENV is in production, tell our app to use production-level environmental variables (for example, live API keys for Stripe instead of test API keys).


Environmental variables, production/development link

Throughout our app, we defined dev with this line of code:

const dev = process.env.NODE_ENV !== 'production';

Find this line of code, for example, in your book/9-begin/server/stripe.js file.

This code says that dev has a value of true when the process.env.NODE_ENV value is not production. Open server/server.js and find this snippet:

const dev = process.env.NODE_ENV !== 'production';
const MONGO_URL = process.env.MONGO_URL_TEST;

const options = {
    useNewUrlParser: true,
    useCreateIndex: true,
    useFindAndModify: false,
    useUnifiedTopology: true,
};
mongoose.connect(
    MONGO_URL,
    options,
);

const port = process.env.PORT || 8000;
const ROOT_URL = `http://localhost:${port}`;

Add the following three console.log() statements right after the code snippet above:

console.log(process.env.NODE_ENV);
console.log(dev);
console.log(ROOT_URL);

Start your app with yarn dev and pay attention to the terminal output:

undefined
true
http://localhost:8000

So dev is true because process.env.NODE_ENV is undefined (we, as developers, did not set it!), and ROOT_URL is http://localhost:8000.

Our goal is to set process.env.NODE_ENV, and once it is set, use it to specify a production-specific ROOT_URL and other environmental variables, such as API keys.

Open package.json and find the scripts block.
Prepend NODE_ENV=production to the dev command, so it becomes:

"dev": "NODE_ENV=production nodemon server/server.js --watch server",

Start your app with yarn dev and now the terminal prints:

production
false
http://localhost:8000
> Could not find a valid build in the '.next' directory! Try building your app with 'next build' before starting the server.
[nodemon] app crashed - waiting for file changes before starting...

Alright, not bad! You successfully set the environment to production.

Next.js tells us that we need to build our app with NODE_ENV=production before we run it. In the scripts of package.json, modify the build command like this:

"build": "NODE_ENV=production next build",

Run yarn build. When complete, start your app with yarn dev. Now the app runs locally but with NODE_ENV=production. You'll notice that the ROOT_URL is still http://localhost:8000. Obviously, we need to change how we define ROOT_URL - by writing a conditional construct. Replace this line inside server/server.js:

const ROOT_URL = http://localhost:${port};

with:

const ROOT_URL = dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP;

Remember to add the following env variables to your .env file:

URL_APP="http://localhost:8000"
PRODUCTION_URL_APP="https://9-end.builderbook.org"

Use your own value for PRODUCTION_URL_APP!

Now run yarn build and yarn dev. The terminal outputs:

production
false
https://9-end.builderbook.org

Try logging in - you'll see that it will fail. This makes sense because we did not add routes from server/google.js, https://builderbook.org/auth/google, and https://builderbook.org/oauth2callback, to our Google OAuth app on Google Cloud Platform. We only added development-related routes http://localhost:8000/auth/google and http://localhost:8000/oauth2callback.

We added Express routes /auth/google and /oauth2callback to our server with:

setupGoogle({ server, ROOT_URL });

All you need to do is add production-level URLs in addition to development-level URLs to your Google OAuth web app at your Google Cloud Platform dashboard.

However, we still have a problem. The sendRequest() method inside lib/api/sendRequest.js also uses ROOT_URL, and all API methods in our app use the sendRequest() method to send a request (GET or POST) from browser to server. Open lib/api/sendRequest.js and find this snippet:

const response = await fetch(
    `${ROOT_URL}${path}`,
    Object.assign({ method: 'POST', credentials: 'same-origin' }, options, { headers }),
);

We suggest, for the sake of reusability, creating a new getRootUrl() function that contains conditional logic and outputs the proper ROOT_URL depending on the value of process.env.NODE_ENV. Create a new file book/9-begin/lib/api/getRootUrl.js with the following content:

export default function getRootURL() {
    const dev = process.env.NODE_ENV !== 'production';
    const ROOT_URL = dev ? process.env.URL_APP : process.env.PRODUCTION_URL_APP;

    return ROOT_URL;
}

To use getRootUrl in lib/api/sendRequest.js, follow these steps:

  • import getRootUrl function with:
    import getRootUrl from './getRootUrl';
  • update the snippet that contains ROOT_URL like this:
    const response = await fetch(
      `${getRootUrl()}${path}`,
      Object.assign({ method: 'POST', credentials: 'same-origin' }, options, { headers }),
    );
  • remove unnecessary code:
    const port = process.env.PORT || 8000;
    const ROOT_URL = `http://localhost:${port}`;

Go ahead and use getRootUrl inside server/server.js as well:

  • import getRootUrl with

    const getRootUrl = require('../lib/api/getRootUrl');
  • update the snippet that contains ROOT_URL by replacing:

    const ROOT_URL = `http://localhost:${port}`;

    with:
    const ROOT_URL = getRootUrl();

  • keep the following line of code, since server.listen() uses port:
    const port = process.env.PORT || 8000;

The third location where we will use getRootUrl is server/models/Book.js:

  • import getRootUrl with
    const getRootUrl = require('../../lib/api/getRootUrl');
  • replace this line:
    const ROOT_URL = 'http://localhost:8000';
    with:
    const ROOT_URL = getRootUrl();

We leave it to you to make similar changes to the book/9-begin/server/stripe.js file.

Start your app with yarn dev and look at the terminal:

production
false
https://builderbook.org

This output proves that getRootUrl() successfully set the proper value for ROOT_URL.

Please note that when you deploy your app to your own custom domain, you should replace https://9-end.builderbook.org with your actual domain name. Please do remember adding URL_APP and PRODUCTION_URL_APP (with corresponding values) to your book/9-begin/.env file.

We recommend removing NODE_ENV=production from the dev and build scripts inside book/9-begin/package.json. Otherwise, you will be using production-level env variables on your local machine. When we deploy our application to Heroku or AWS Elastic Beanstalk, we will pass an extra env variable NODE_ENV=production. Some hosting services set NODE_ENV to production by default. If you want to set NODE_ENV to production, you can prepend a command like this:

NODE_ENV=production yarn dev

If you change scripts in book/9-begin/package.json, please remember to undo the changes.

We should make sure that our app uses live API keys instead of test ones. Let's test it out for our Github keys. Open server/github.js. Find the const API_KEY line of code and add console.log() right after it:

const API_KEY = dev ? process.env.GITHUB_TEST_SECRETKEY : process.env.GITHUB_LIVE_SECRETKEY;
console.log(API_KEY);

Important note - the Github OAuth app does not support multiple domains (Google OAuth does, see above). Therefore, you should create a second Github OAuth app and set https://builderbook.org and https://builderbook.org/auth/github/callback for the domain and callback URL. Reminder - in your first Github OAuth app, you set http://localhost:8000 and http://localhost:8000/auth/github/callback.

Paste GITHUB_LIVE_SECRETKEY and GITHUB_LIVE_CLIENTID to your book/9-begin/.env file. Start your app with yarn dev and you will see that the terminal printed the proper value for API_KEY, which is the value you specified for process.env.GITHUB_LIVE_SECRETKEY inside .env.

Before we deploy our app, we need to modify the start command. We stopped using the yarn next command to start our app since Chapter 2, where we introduced Express server. Thus, we should update it to start our custom Express/Next server. Update it like this:

"start": "node server/server.js"

The final scripts section inside package.json should be:

"scripts": {
    "dev": "nodemon server/server.js --watch server",
    "build": "next build",
    "start": "node server/server.js",
    "lint": "eslint components pages lib server",
    "test": "jest --coverage"
},

After you are done with testing, remove all the console.log() statements from server/server.js and server/github.js.


You've reached the end of the Chapter 9 preview. To continue reading, you will need to purchase the book.

We regularly update the codebase with recent syntax and stable versions for libraries. The latest update was May 2021.