Builder Book logo

Book: Builder Book

  1. Introduction
  2. Project structure. ESLint. Next.js. HOC. Material-UI. Server-side rendering. Styles.
  3. Server. Database. Session. Header and MenuDrop components.
  4. Authentication HOC. Promise. Async/await. Static method for User model. Google OAuth.
  5. Testing with Jest. Transactional emails with AWS SES API. In-app notifications.
  6. Book and Chapter data models. Internal API infrastructure, API methods and Express routes. ReadChapter page.
  7. Set up Github API infrastructure. Sync content API infrastructure. Missing UI infrastructure for Admin user. Redirects for Admin and Customer users. Testing.
  8. Table of Contents. Highlight for 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. Google Analytics. Compression and security. Heroku. Testing deployed project. AWS Elastic Beanstalk.

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

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

We are currently refactoring and updating the code in Builder Book. To reduce confusion, we have temporarily disabled the buy button. You can follow our progress here: https://github.com/builderbook/builderbook/issues/359

You can still buy our second book, SaaS Boilerplate: https://builderbook.org/books/saas-boilerplate/introduction-project-structure


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

  • Deploy project
    - Heroku
    - Testing deployed project
    - AWS Elastic Beanstalk (before Amazon Linux 2 )


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.


link Prepare project for deployment

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).


link Environmental variables, production/development

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.

Let's make one more improvement, go to server/server.js and replace:

const MONGO_URL = process.env.MONGO_URL_TEST;

With:

const MONGO_URL = dev ? process.env.MONGO_URL_TEST : process.env.MONGO_URL;

It is a good idea to use a different databases for production and development. The above change will make our app use the environmental variable MONGO_URL instead of MONGO_URL_TEST in production. Remember to add your values for MONGO_URL and MONGO_URL_TEST to book/9-begin/.env file.

In the next subsection, we will discuss custom logger for our server.


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

We are currently refactoring and updating the code in Builder Book. To reduce confusion, we have temporarily disabled the buy button. You can follow our progress here: https://github.com/builderbook/builderbook/issues/359

You can still buy our second book, SaaS Boilerplate: https://builderbook.org/books/saas-boilerplate/introduction-project-structure

format_list_bulleted
help_outline
lens