Jump to
Where Should I Deploy My React App?
Deploying your newly created React App typically falls at the hands of whoever is creating the tutorial or example application that you are following. Sometimes this works out fine because they typically have experience with that deployment option, or the deployment option is easy to follow.
Nonetheless, at some point, you are going to wonder what other options are out there and if your future app could benefit from various deployment options.
Today, we are going to take a simple React application and deploy it to several services covering different options. I hope that the application and deployments can widen your understanding of how different apps fit into deployment scenarios.
Common Considerations
Workflow
The workflow consideration usually is the result of the question, how much work am I willing to do? Many cloud providers have found success by offering quick and painless ways to get an application into production that is easy to manage.
Managing a production server can be a lot of work with the amount of configuration, monitoring, and updating that takes place over the server’s lifetime. However, sometimes the application requires this kind of setup. Later, we are going to cover setting up a cloud server running Ubuntu and deploy a React app to that remote server.
Furthermore, there are considerations with continuous integration (CI) and continuous deployment (CD). Does your app require a separate Jenkins server for handling these desirable workflow features? Or, can you choose a deployment option that automatically integrates with your Git repository?
Application Type
Another consideration is the supported application types. Some services only support certain languages. Furthermore, other services are more designed towards certain stacks (i.e JAMstack, MERN, LAMP). These different stacks reflect the application’s language, data storage type, etc. It’s worthwhile to deploy in a way that fits the style of your application.
Security
One of the first considerations that I have when choosing a deployment option is what levels of security do I need? Many applications need to maintain secret keys to allow access to resources or conceal user information.
There are many tutorials available for deploying React applications that do not include the use of third-party API keys. Consequently, one of the most important aspects of any application (security) can have an effect on how the application is designed.
Pricing and Performance
The overarching consideration is how much the deployment will cost. This is usually attached to the desired performance of the application and the desired geographic outreach. Thankfully, most cloud deployment options are transparent about their pricing and have free options to test the service before committing.
7 Ways to Deploy a React App
In the rest of the article, we are going to take a simple React application and deploy it in numerous ways. The application is a single page and makes an API call. It will be transformed to fit different deployment scenarios.
The deployments do not include static site options like Gatsby.js or server-side-rending frameworks like Next.js. However, below is a list of tutorials that can get you started using Gatsby or Next.js:
Gatsby.js
Next.js
One static option we will talk about is deploying to Github Pages. This application will not make the third-party API call, but if you used a different provider, like AWS, Azure, Firebase, as demonstrated in the other application deployments, then you could set up a function call to serverless cloud function.
There are a few prerequisites to cover, but after that, we will be ready to begin!
Prerequisites
- Git installed and set-up
- Internet access
- Basic experience using Git and the command line
- Basic experience with React.js and Node.js
- RapidAPI Account. Visit RapidAPI to get signed up for a free account if you haven’t already!
- Subscription the Aeris Weather API
- Visit the Pricing page to subscribe.
- The free plan allows for 100 API requests a day
Forking the Starter Repository
Open up a new browser and navigate to https://github.com/jdretz/rapidapi-deply-react-app
Towards the top right of the screen, hit the Fork button. A copy of the repository will be added to your Github account.
Next, clone the repository from your Github account to your local system.
git clone https://github.com/yourusername/rapidapi-deply-react-app.git
Open up a new terminal in your cloned project root and run npm install
.
You are now ready to make changes and push those changes up to your new repository. If you plan on doing multiple deployments with the repository you could delete it after each deployment and re-fork the repository or work on your Git skills and revert it to a previous commit.
1. How to deploy a React App on Heroku
Sign up for an account on Heroku.
Download the Heroku-CLI for your platform or using the command line with npm install -g heroku
.
After downloading, open up a new terminal and configure the Heroku-CLI by entering heroku login
into the terminal.
Our Heroku deployment requires a Node backend to complete our API call.
Therefore, we are going to rearrange our project structure and add a Node.js backend.
After following the forking instructions above, open up the project with your text editor.
1. Set-Up Node.js Server
In the root of the project create a folder named client
. Move all of the visible files into that folder.
Next, in the project root, create the files;
- server.js
- .gitignore
Then, add the code below to the corresponding file.
server.js
const express = require('express'); const path = require('path'); const axios = require('axios'); // Loads env variables require('dotenv').config() const app = express(); const PORT = process.env.PORT || 3001; // Adds json parsing middleware app.use(express.json()); // Setup static directory to serve app.use(express.static(path.resolve('client', 'build'))); // Creates weather endpoint app.post('/weather', async (req, res) => { const { location } = req.body // Encode the variable so we can send the location in a URL const encodedLocation = encodeURIComponent(location) try { // Call the Weather API const { data } = await axios({ method: "GET", url: `https://aerisweather1.p.rapidapi.com/observations/${encodedLocation}`, headers: { "content-type": "application/octet-stream", "x-rapidapi-host": "aerisweather1.p.rapidapi.com", "x-rapidapi-key": process.env.RAPIDAPI_KEY, "useQueryString": true } }) // Pull the information that we need from the Weather API response const weatherData = { conditions: data.response.ob.weather, tempC: data.response.ob.tempC, tempF: data.response.ob.tempF } // Return the data object return res.send(weatherData) } catch (e) { console.log(e) return res.status(500).send('Error.') } }) app.get('*', (req, res) => { res.sendFile(path.resolve('client', 'build', 'index.html')); }); // console.log that your server is up and running app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
.gitignore
node_modules .env package-lock.json.*
Finally, navigate into your server folder using the terminal. Inside the server
directory execute the commands,
$ npm init -y $ npm install express axios dotenv --save
After the packages are installed, there is only one more change to be made to the project. Open up client/src/App.js
and change the URL in line 18 of the file to /weather
.
Good work, our API function is now on our backend server. Let’s deploy to Heroku!
2. Deploy to Heroku
In server/package.json
, under scripts add a start script and post-build script.
"scripts": { "start": "node server.js", "heroku-postbuild"::"cd client && npm install && npm run build" ... },
Next, create a file named Procfile
in the project root and add this line to it.
web : npm run start
Push the changes that we just made to your Git repository.
Now, we are ready to create the Heroku app!
Make sure that your terminal is in the project root and run the command heroku create
.
This command returns the app name (i.e “floating-coast-11273″), URL, and Git remote URL.
Now that we have the app name we can add in our RapidAPI-key environment variable.
Locate your API key on RapidAPI. Then, run the command, inserting your RapidAPI key.
heroku config:set RAPIDAPI_KEY=yourkey
Finally, we can push the changes to the Heroku remote Git repository and our app will be built.
In the project root run:
git push heroku master
The app will build and output the URL for the new application, again.
Great work! I hope that was easy!
Remember your app is now public, you can log onto Heroku and delete or deactivate your application if you don’t want people to run up your API request usage.
2. How to deploy a React App on Apache server (Linode)
The Apache deployment requires a Node backend for our API call, but if we use Linode we can simplify the deployment process. Then, after deployment, we can make the custom changes that we need by accessing the remote server.
Despite simplifying the process, running a cloud production server requires more configuration. Consequently, there is increased risk. I have made a Linode StackScript available to help mitigate some of the risks with the process.
Furthermore, when deploying to your own remote server, we are doing more manual tasks. Some cloud providers set up nice continuous deployment workflows, but this setup is very hands-on.
Follow the forking instructions above and then complete the first step in the Heroku deployment to complete the Node.js (Express) back-end set up. When completed, push your changes up to the Git repository.
Sign up on Linode and then visit your Cloud Dashboard.
1. Configure and Deploy the Server
Once on the dashboard select StackScripts on the left sidebar. On the top right of the page click the Create New Stackscript. Name it whatever you like, and write a brief description.
Choose Ubuntu 18.04 for the image and paste in the below code to the script.
#!/bin/bash # <UDF name="project" Label="Name of Project" example="personal_blog" /> # <UDF name="username" Label="Create New User" example="username" /> # <UDF name="password" Label="New User Password" /> # <UDF name="hostname" Label="Change Hostname" example="localhost" /> # <UDF name="sshkey" Label="Add GPG Public Key" example="AAAAB3NzaC1yc2EAAAADAQABAAACAQCdZBMH9fKg995K" default=""/> # <UDF name="disableroot" Label="Disable Login with Password & Disable root user Login?" oneOf="Yes,No" default="Yes" /> # <UDF name="timezone" Label="Set Date Time Zone" oneOf="EST, UTC, MST" default="EST" /> # This sets the variable $IPADDR to the IP address the new Linode receives. IPADDR=$(/sbin/ifconfig eth0 | awk '/inet / { print $2 }' | sed 's/addr://') echo " ~~~~ Running Ubuntu18LTS configuration ~~~~" #Update sudo apt-get update # Set date/Time to EST echo $TIMEZONE | sudo timedatectl set-timezone $TIMEZONE date echo $USERNAME if [ "$USERNAME" != "" ] then echo $USERNAME | adduser --disabled-password --shell /bin/bash --gecos "User" $USERNAME echo $USERNAME:$PASSWORD | chpasswd sudo adduser $USERNAME sudo # SSH Harden sudo mkdir -p /home/$USERNAME/.ssh sudo chmod -R 700 /home/$USERNAME/.ssh sudo chown $USERNAME:$USERNAME /home/$USERNAME/.ssh sudo touch /home/$USERNAME/.ssh/authorized_keys # GPG Public Key sudo echo $SSHKEY >> /home/$USERNAME/.ssh/authorized_keys cd ~ fi echo $HOSTNAME # Check if hostname's been provided if [ "$HOSTNAME" != "" ] then hostnamectl set-hostname $HOSTNAME # Add hostname to Hosts myip="$(curl ifconfig.me)" sudo echo "$myip $HOSTNAME" >> /etc/hosts echo "Host name changed to: " $HOSTNAME fi echo $DISABLEROOT if [ "$DISABLEROOT" == "Yes" ] then # Make sure a user is provided in order to prevent being locked out if [ "$USERNAME" != "" ] then # Disable root login echo "Disabling root login" sudo sed -i -e 's/PermitRootLogin yes/PermitRootLogin no/g' /etc/ssh/sshd_config # If GPG key is provided disable login by password if [ "$SSHKEY" != "" ] then sudo sed -i -e 's/#PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config fi fi fi # Install Apache sudo apt install apache2 -y sudo mkdir /var/www/$PROJECT sudo chown -R $USERNAME:$USERNAME /var/www/$PROJECT sudo chmod -R 755 /var/www/$PROJECT sudo touch /etc/apache2/sites-available/$PROJECT.conf sudo a2enmod proxy proxy_http rewrite headers expires sudo a2dissite 000-default #Install Node and NPM and other dependencies curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - sudo apt-get install -y nodejs sudo apt install build-essential sudo npm install pm2@latest -g # Set Up Firewall sudo apt-get install ufw -y sudo ufw default allow outgoing sudo ufw default deny incoming sudo ufw allow ssh sudo ufw allow 80 sudo ufw status sudo ufw --force enable echo " ~~~~~~~~ Ubuntu 18 LTS configuration is complete ~~~~~~~~" sudo reboot
There are a lot of commands that go into setting up a server. The above script:
- configures the timezone, hostname, IP address
- creates a superuser with your username and password
- sets up SSH keys for remote server access authorization
- downloads Node, NPM, PM2, and other dependencies
- disables root password login
- blocks all incoming traffic except on ports 22 (SSH port) and 80 (HTTP)
- installs and does some configuration for Apache webserver
Save the script.
Next, select the newly created StackScript and, in the top right, hit the Deploy New Linode button.
Fill out the information in the input fields. The project name is used as a folder name on the server so it cannot have spaces.
Then, to set up the remote SSH you must input a public SSH key that you have on your computer.
This key must be generated, and on a Mac can typically be found in the /.ssh/
folder. However, some systems change, and not everyone uses the same operating system. This is the point in the process where you will most likely have difficulty if you have never accessed a remote server before.
For Mac users, this is a helpful post on Stack Overflow in terms of getting your public key.
For Windows, you are probably better off with this Youtube search. You will have to install and configure PuTTY.
Once you have your public key pasted into the form (and on your computer) finish filling out the form with these values.
- Disable root login
- Select Ubuntu 18.04 LTS
- Pick the closest region to your location.
- Select Nanode for Linode plan. It’s the cheapest option at $5 a month, and you can deactivate or delete the Linode as soon as we finish deploying.
- Enter a root user password
Before creating the Linode you need a credit card on file for your account.
Hit Create.
The Linode takes a little bit of time to boot.
2. Set Up Apache Server
The indicator circle in the top right will be green and display Online when the Linode is ready. Select the Networking and copy your new IP address.
Open up a new terminal and SSH into the remote server. On Mac, this can be done with:
ssh the_username_you_entered_earlier@ip_address
Give it one or two tries before starting to troubleshoot. Sometimes the server isn’t ready yet.
Once logged into the server open up the project configuration file for Apache. The file name will be the name of the project that you entered earlier.
sudo nano /etc/apache2/sites-available/projectname.conf
Once in the file copy and paste the code below.
<VirtualHost *:80> ServerName domain.com ServerAlias www.domain.com ProxyRequests Off ProxyPreserveHost On ProxyVia Full <Proxy *> Require all granted </Proxy> ProxyPass / http://127.0.0.1:3001/ ProxyPassReverse / http://127.0.0.1:3001/ </VirtualHost>
The code sets up a proxy so requests made to port 80 are redirected to port 3001. This is the port that our Node server is listening on.
To enable the site run sudo a2ensite projectname.conf
.
To save the file, hit Crtl+X, then Y, and finally, Enter.
3. Clone Repository
Next, we need to pull our project code to the server and build the project. Change directories into /var/www/projectname
with the cd
command.
Clone your Git repository with the command below.
git clone https://github.com/yourusername/rapidapi-deply-react-app.git
You may have changed the repository URL for your account.
Next, we need to install the Node packages for the front-end and back-end and run a build.
Execute the commands:
$ cd rapidapi-deply-react-app/ $ npm install $ cd client && npm install $ npm run build
The first command may be different if your repository or folder has a different name.
Once it’s done building, change directories back the repository folder (i.e rapidapi-deply-react-app).
cd ..
4. Start Application
Before starting the app we need to configure our environment variable that holds the RapidAPI key.
Create a new file name ecosystem.config.js
.
touch ecosystem.config.js
Inside that file, paste the following code.
module.exports = { apps : [ { name: "react_deploy", script: "./server.js", watch: true, env: { "PORT": 3001, "RAPIDAPI_KEY": "yourapikey", "NODE_ENV":"production" } } ] }
Be sure to replace yourapikey
with your actual API key.
Save the file.
This file is specific to the PM2 library.
To start the application run the command below.
sudo systemctl start apache2 && pm2 start ecosystem.config.js
Finally, you can visit the IP address that you copied earlier in the browser to find our app.
You have done some great work if you made it this far! However, the app can still use a little work. Unfortunately, I won’t be going into setting up a custom domain or configuring an SSL certificate. However, you can use the instructions on Let’s Encrypt to get a free SSL certificate once you have a domain name.
Don’t forget to deactivate or delete your Linode in the dashboard so you do not any incur any extra charges.
3. How to deploy a React App on Firebase
Firebase has many of the same options when deploying a React app as AWS and Azure. With Firebase, we can set up a statically hosted application that makes API calls to a cloud function that renders dynamic content on our site.
First, follow the forking directions above to get the starter project. Then open up the project in your text editor with a new terminal in the project root.
You will need a Google Account before proceeding.
1. Set-up Firebase Locally
Install the Firebase CLI.
npm install -g firebase-tools
Log in to Firebase with your Google Account.
firebase login
Make sure your terminal is in the project root and initialize a new Firebase project.
firebase init
Next, you will be taken through a series of prompts.
For the project set-up select;
Create a new project
Next, specify the name of your new project when prompted.
When asked, What would you like to call your new project?
hit enter to use the default.
Then, in the hosting set-up prompts, select the options that I made in the below image.
Next, in the project root, execute npm run build
.
2. Deploy to Firebase
When the build completes run firebase deploy
. When the project is done deploying there should be a URL to visit the new project. Our app should appear when you visit the page.
Next, let’s add a function that will call our API. This will allow us to secure our API key and it will allow our app to display dynamic content.
3. Add Firebase Function
Add functions to the project.
firebase init functions
Next, follow the prompts and respond with the answers from the image below.
Then, change directories into the functions directory with cd functions
Install Axios.
npm install axios
After Axios is installed we can add our RapidAPI secret key to the function configuration with the following line. Enter the below command into the terminal replacing apikey
with your RapidAPI key.
firebase functions:config:set rapidapi.key="apikey"
This is how we can add environment variables to functions. Finally, replace all the code in functions/index.js
with the code below.
const functions = require('firebase-functions'); const axios = require('axios') exports.weather = functions.https.onRequest(async (req, res) => { if (req.method !== "POST") { return res.status(405).send('Method not allowed') } // Assign the location value to the variable location from the body object const { location } = req.body // Encode the variable so we can send the location in a URL const encodedLocation = encodeURIComponent(location) try { // Call the Weather API const { data } = await axios({ method: "GET", url: `https://aerisweather1.p.rapidapi.com/observations/${encodedLocation}`, headers: { "content-type": "application/octet-stream", "x-rapidapi-host": "aerisweather1.p.rapidapi.com", "x-rapidapi-key": functions.config().rapidapi.key, "useQueryString": true } }) // Pull the information that we need from the Weather API response const weatherData = { conditions: data.response.ob.weather, tempC: data.response.ob.tempC, tempF: data.response.ob.tempF } // Return the data object return res.send(weatherData) } catch (e) { console.log(e) return res.status(500).send('Error.') } });
The key is imported on the functions
object and is accessed with functions.config().rapidapi.key
.
We need to add a rewrite rule so our function has a convenient URL. In firebase.json
, add the following rewrite rule above the /index.html
rule.
{ "source": "/weather", "function": "weather" },
Now, the rewrites
section of your firebase.json
file looks like,
"rewrites": [ { "source": "/weather", "function": "weather" }, { "source": "**", "destination": "/index.html" } ] },
Then, change the API call URL on line 18 of App.js
to /weather
.
Finally, redeploy your hosting and functions with,
firebase deploy
.
4. Upgrade Plan for Outbound Networking
Firebase cloud functions require a pay-as-you-go subscription for functions that use outbound networking. The free tier covers functions that you use Google services. Therefore, to send the request to the Aeris Weather API you need to upgrade your account.
You can see the pricing details and upgrade them by following this link.
However, once you upgrade, the statically hosted app can make an API call to our cloud function that, subsequently, makes an API call to the weather API for data. The weather data is returned and displayed on the page!
4. How to deploy a React App on Github
Github Pages is a static website hosting service. This means it takes HTML, CSS, and Javascript files and hosts them as individual pages. This can be useful for documentation, or a portfolio, but requires more work if were hoping to make an API call from our front-end.
Therefore, we can deploy a React application with ease, but cannot set up a simple Node backend to secure our third-party API call or use a built-in serverless function (like some of the other deployment options).
We could set up a serverless function on a different service and configure the cross-origin resource sharing (CORS) to only allow access from our domain. Nonetheless, let’s deploy the application.
First, follow the instructions to fork the application.
1. Install and Configure Github Pages
Install the Github Pages library.
npm install gh-pages --save-dev
Next, open up package.json
and paste in the homepage
property at the top of the object.
"homepage": "https://yourusername.github.io/yourrepositoryname",
You will need to locate your username and repository name to modify the URL accordingly.
Next, add the following properties to the script
object.
"scripts": { // ... other commands "predeploy": "npm run build", "deploy": "gh-pages -d build" },
2. Deploy to Github Pages
Push your changes to your repository.
$ git add package.json package-lock.json $ git commit -m "deploy setup for Github Pages" $ git push
In a terminal located at the root of the project (where you installed gh-pages
in the previous step), run the command npm run deploy
.
This creates a new branch that hosts your React app named gh-pages.
3. Confirm Deployment on Github
Open a browser and navigate to your Github repository.
Click on the Settings tab and scroll down to the Github Pages section.
The Source attribute needs to be set to the gh-pages branch. You can also find your site URL and customize the domain name.
If you visit the page the API call for weather data will not work. There is not a function set up to fetch the data because we cannot secure our API key on the front end.
The Axios API call URL in App.js
could be modified to call a serverless function set up on a cloud provider. Check out one of the other deployment options for setting up serverless functions.
Regardless, it’s very easy to turn a React app into a website with Github Pages.
5. How to deploy a React App on AWS Amplify
AWS is a cloud hosting service. The deployment our the React App is going to be similar to our deployments for Azure and Firebase. We are going to host the static files on a storage system and make API calls to serverless functions. This is a great option for our application, and can further be expanded to incorporate server calls for database records if the app progresses.
Sign Up for an account on Amazon Web Services.
Fork the repository following the instructions above.
1. Set Up AWS Amplify
Open up the project in your text editor and open a new terminal the root of the project directory.
Install AWS packages.
npm install aws-amplify aws-amplify-react --save
Install the Amplify CLI-tools globally
npm install -g @aws-amplify/cli
Configure the Amplify CLI
amplify configure
This will start a series of prompts.
First, sign in to your AWS administrator account and specify a region that is close to you.
Then, create a username for the new programmatic user that will be connected to your local computer. To use the default, hit enter.
This pulls up the user creation page on AWS. Give the new user AministratorAccess.
Stop when you see the page with the ACCESS ID and Secret key
In the terminal, you are prompted to enter the Access key ID and the Secret access key. The secret access key is only available once, so make sure you do not navigate away from this page until after you enter the secretAccessKey
in the user prompt.
Name the profile “default”.
Now that we have an Amplify user we can initialize an Amplify project. In the root of the directory enter the command below.
amplify init
Again, we have a series of prompts to fill out.
The two red arrows point to aspects of the prompts that may be different for your situation. The red box outlines the name of the user profile that we created when we configured the Amplify CLI.
Later, we use the aws-amplify
library to call a Lambda function. However, this needs to be configured in the front-end React application.
Open src/index.js
and add the three lines below that configure the client.
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import Amplify from 'aws-amplify' // ADD import config from './aws-exports' // ADD Amplify.configure(config) // ADD ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
Now that we have initialized an Amplify project we can add the backend API.
2. Add Backend API to Project
It’s time for another command and another series of prompts!
Run amplify add api
in the project root.
Then, apply the following answers to the prompts.
There is now a local Lambda function that we can edit before we push it up to our Amplify project.
3. Create Weather API Function Call
A new folder named amplify gets created (if you ran the commands from the root of the project) in our React project. Open up the file at amplify/backend/function/src/app.js
.
Notice that this is the Express function app that was created in the previous step. The file has function examples that use the base API route we entered in the prompts (weather).
Replace all the code in amplify/backend/function/src/app.js
with the code below.
/* Amplify Params - DO NOT EDIT ENV FUNCTION_CALLWEATHERAPI_NAME REGION Amplify Params - DO NOT EDIT *//* Copyright 2017 - 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ var express = require('express') var bodyParser = require('body-parser') var awsServerlessExpressMiddleware = require('aws-serverless-express/middleware') var axios = require('axios') // Import get secrets function var secret = require('./secret-manager') // declare a new express app var app = express() app.use(bodyParser.json()) app.use(awsServerlessExpressMiddleware.eventContext()) // Enable CORS for all methods app.use(function (req, res, next) { res.header("Access-Control-Allow-Origin", "*") res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") next() }); app.post('/weather', async function (req, res) { // Assign the location value to the variable location from the body object const { location } = req.body // Encode the variable so we can send the location in a URL const encodedLocation = encodeURIComponent(location) try { // Call AWS Secrets to get RapidAPI key const secretObj = await secret() // Call the Weather API const { data } = await axios({ method: "GET", url: `https://aerisweather1.p.rapidapi.com/observations/${encodedLocation}`, headers: { "content-type": "application/octet-stream", "x-rapidapi-host": "aerisweather1.p.rapidapi.com", "x-rapidapi-key": secretObj["RAPIDAPI-KEY"], "useQueryString": true } }) // Pull the information that we need from the Weather API response const weatherData = { conditions: data.response.ob.weather, tempC: data.response.ob.tempC, tempF: data.response.ob.tempF } // Return the data object return res.send(weatherData) } catch (e) { console.log(e) return res.status(500).send('Error.') }; }); app.listen(3000, function () { console.log("App started") }); // Export the app object. When executing the application local this does nothing. However, // to port it to AWS Lambda we will create a wrapper around that will load the app from // this file module.exports = app
This file now implements the API call the weather API for our app. However, Axios is not installed and it is used in the function. In addition, we have not implemented the secret-manager
file, but that will be done in a later step.
In the terminal, change directories into the amplify/backend/function/src
folder. This folder is an initialized Node package. We know this because there is a package.json
file. Therefore, we can download Node packages here to use in our functions.
Install the Axios library so it can be used in our function.
npm install axios --save
While we are here, we need to install the aws-sdk
for accessing secrets later.
npm install aws-sdk --save
4. Modify the Client API Call
We need to call our new function in src/App.js
. However, in an Amplify application, this is done differently then the current API call set up.
Instead of using Axios, we use the aws-amplify
library that we installed earlier.
Import the API
object from aws-amplify
at the top of the page with the other imports.
import { API } from 'aws-amplify';
fetchWeather
function in App.js
with the code below.const fetchWeather = (e) => { e.preventDefault() setLoading(true) setError(false) const myInit = { body: { location }, }; //make edit to redeploy API.post('weatherAPI', '/weather', myInit) .then((data) => { setTempC(data.tempC) setTempF(data.tempF) setConditions(data.conditions) }) .catch(e => { setError(true) console.log(e) }) .finally(() => { setLoading(false) }) }
It’s important to note that the first two arguments in the post
function depend on the values we entered into the prompts when we created the function.
5. Add API Key with AWS Secrets Manager
You may notice later in the Amplify Console that there is a place to add environment variables. Unfortunately, these variables are not available in the Lambda function that we just set up. Therefore, we need to establish a workaround.
The workaround that we are going to implement was suggested by Troy Goode on the Amplify-CLI issues page on Github.
On your AWS account, open up the Secrets Manager service
Create a new secret named rapidapi that holds the value of your RapidAPI key.
Back in the project, in the folder amplify/backend/function/callWeatherApi/src
, create a file name secret-manager.js
. Inside of the file, add the code:
const AWS = require('aws-sdk') module.exports = async () => { const secretsManager = new AWS.SecretsManager() const secret = await secretsManager.getSecretValue({ SecretId: 'rapidapi' }).promise() if (!secret) { throw new Error('Secret not found') } return JSON.parse(secret.SecretString) }
This gives the /weather
endpoint the ability to call the Secrets Manager for our secret key. However, the Lambda function does not currently have access to that secret. We can give the Lambda function the access it needs in the amplify/backend/function/callWeatherApicallWeatherApi-cloudformation-template.json
file.
Open up the callWeatherApicallWeatherApi-cloudformation-template.json
file.
This is a JSON configuration file that controls aspects of our ”cloud stack” that is implemented when the application is deployed. Furthermore, it can control IAM access for various resources.
There is a Resources
parameter that contains an object. On that object there is a parameter named lambdaexecutionpolicy
. Further down in this object exists the PolicyDocument
object that holds various policy statements in a Statements
array.
Add the object below to the array.
{ "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue" ], "Resource": { "Fn::Sub": [ "arn:aws:secretsmanager:${region}:${account}:secret:rapidapi-RLE73o", { "region": { "Ref": "AWS::Region" }, "account": { "Ref": "AWS::AccountId" } } ] } }
The following line may differ depending on your secret’s name:
"arn:aws:secretsmanager:${region}:${account}:secret:rapidapi-RLE73o"
You can double-check the value by clicking on the secret on the Secrets Manager
page.
The PolicyDocument
object should look like the code below.
"PolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": { "Fn::Sub": [ "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*", { "region": { "Ref": "AWS::Region" }, "account": { "Ref": "AWS::AccountId" }, "lambda": { "Ref": "LambdaFunction" } } ] } }, { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue" ], "Resource": { "Fn::Sub": [ "arn:aws:secretsmanager:${region}:${account}:secret:rapidapi-RLE73o", { "region": { "Ref": "AWS::Region" }, "account": { "Ref": "AWS::AccountId" } } ] } } ] }
This the access rights granted, we are ready to initialize the deployment process.
6. Deploy to Amplify Hosting
First, push the changes that you have made to your Git repository.
We are now ready to deploy our application.
In the project root, add hosting to services with the command below.
amplify add hosting
Again, select the two options below when prompted that are displayed in teal.
A browser will open. Connect your Github repository by selecting the Connect App button, choosing Github, authorizing Github, and selecting your forked repository.
Select the master branch.
Create a new service role if needed. Use the defaults.
Create a new environment named “master”.
On the next page deploy the application and watch the build process.
Back in the terminal, hit Enter to end the command prompt. You can visit the link on the build page when the app is done to view the application!
One of the advantages of using the Amplify-CLI and Amplify Console is having the ability to run builds when new code is pushed to the Github repository. This deployment requires a little understanding of AWS, but in the end there are benefits to utilizing their services.
To not incur usage charges, delete resources and resource groups on AWS.
6. How to deploy a React App on Azure
Azure has static hosting capabilities and web application capabilities (as well as many other cloud services). Recently, they added a Static Web App resource that is geared towards frontend frameworks like React, Angular, Vue, and static publishing frameworks like Gatsby and Next. Furthermore, the resource can be integrated with Github to make the workflow easier.
It truly is easier if you are using VSCode to set up your Azure projects. They have many plugins that can save you time. Therefore, this deployment requires that you have;
- Visual Studio Code installed
- An Azure account with an active subscription. Create an account here.
- Azure Functions extension for Visual Studio Code
Follow the forking instructions above.
1. Configure an Azure Function in Project
Open up the forked project in Visual Studio Code.
Create a new empty folder at the root of the project named api/
.
Open the command palette (press F1) and type in Azure Functions: Create New Project
Next, click Browse in the dropdown and select the empty api
folder.
You will be asked to choose a language. Select Javascript.
Then, select HTTPTrigger for the template and name the function “weather”.
In this example, we will deploy a web application.
You will need to have an account with Azure. You can sign up here.
Next, for this example, select Anonymous access.
Finally, add a route declaration so we can easily call the function. Open up api/weather/function.json
and add,
"route":"weather"
under methods. The file should now read;
{ "bindings": [ { "authLevel": "anonymous", "type": "httpTrigger", "direction": "in", "name": "req", "methods": [ "get", "post" ], "route": "weather" // new }, { "type": "http", "direction": "out", "name": "res" } ] }
2. Add API Function
In an open terminal, change directories into api
. Then install Axios with npm install axios --save
.
Next, remove the code inside of api/weather/index.js
and replace it with the code below.
const axios = require('axios') module.exports = async function (context, req) { if (req.method !== "POST") { context.res = { status: 405, body: 'Method not allowed' } } const { location } = req.body const encodedLocation = encodeURIComponent(location) try { // Call the Weather API const { data } = await axios({ method: "GET", url: `https://aerisweather1.p.rapidapi.com/observations/${encodedLocation}`, headers: { "content-type": "application/octet-stream", "x-rapidapi-host": "aerisweather1.p.rapidapi.com", "x-rapidapi-key": process.env["RAPIDAPI_KEY"], "useQueryString": true } }) // Pull the information that we need from the Weather API response const weatherData = { conditions: data.response.ob.weather, tempC: data.response.ob.tempC, tempF: data.response.ob.tempF } // Return the data object context.res = { body: weatherData } } catch (e) { console.log(e) context.res = { status: 500, body: "An error occurred" } } }
Azure uses a context
object to return the res
object. Most aspects are pretty standard in terms of making an API call to the Aeris Weather API and returning the data. Also, there are comments in the code that detail what the function is doing
3. Deploy Static Web App on Azure
Push your changes to the forked Git repository.
Navigate to your Azure portal and create a new resource.
Search for Static Web App
Select Create.
You will need to make a new resource group, pick a name, select a resource region, and authorize Azure to access your Github account.
Then select your account and the repository name for the forked repository. Click on the master branch.
Before creating the resource click on Next: Build. Enter the values in the image to tell the static web app where to find certain resources. Make sure your values match the ones in the image.
Finally, click Review + Create. The resource is created and a build is initiated in your repository on Github. You can view the build process by going to the resource on Azure and clicking on the popup link (“Thank you for using Azure Static Web App! We have not received any content for your site yet. Click here to check the status of your GitHub Action runs.”).
If you follow the link you can see when your Github action is completed. Next, go back to the resource page and click on the URL link in the dashboard.
The browser will open displaying our weather app, but we haven’t added the API key to the environment yet.
4. Add API Key
On the static app resource page (same page where the URL is found) click on Configuration in the sidebar. Then select Add to add a new value.
The value name needs to add the name that we used in the app for deployment (“RAPIDAPI_KEY”).
Insert your API key into the value field. After hitting “OK”, click Save at the top of the Configuration pane.
Head back over to the app and test out a location.
The environment variable should now be loaded and weather data can be fetched!
To not incur usage charges, delete resources and resource groups on the Azure portal.
7. How to deploy a React App on Netlify
Netlify makes things easier. With Netlify, we can configure serverless functions in our project that Netlify converts to AWS Lambda functions. That means that the serverless function set-up is simplified and we can make an easy call to a serverless route for our weather app data.
There is a bit of configuration that we need to take care of first, but I think you will agree that there are clear benefits to this process.
Sign up for an account on Netlify.
Follow the forking instructions above.
Open up the project with your text editor.
Remember, we don’t currently have an API call in place because we can’t secure our API key on the front end. Therefore, we need to implement an API call.
1. Add Call to Weather API
In the project, root create the folder functions
and inside of the folder add the file weather.js
.
Add the below code to weather.js
.
const axios = require('axios'); exports.handler = async function (event, context) { let RAPIDAPI_KEY; // Access API key from environment RAPIDAPI_KEY = process.env.RAPIDAPI_KEY // Validate HTTP request type if (event.httpMethod !== "POST") { return { statusCode: 405, body: "Method Not Allowed" }; } // Get the body of the request const body = JSON.parse(event.body) // Assign the location value to the variable location from the body object const { location } = body // Encode the variable so we can send the location in a URL const encodedLocation = encodeURIComponent(location) try { // Call the Weather API const { data } = await axios({ method: "GET", url: `https://aerisweather1.p.rapidapi.com/observations/${encodedLocation}`, headers: { "content-type": "application/octet-stream", "x-rapidapi-host": "aerisweather1.p.rapidapi.com", "x-rapidapi-key": RAPIDAPI_KEY, "useQueryString": true } }) // Pull the information that we need from the Weather API response const weatherData = { conditions: data.response.ob.weather, tempC: data.response.ob.tempC, tempF: data.response.ob.tempF } // Return the data object return { statusCode: 200, body: JSON.stringify(weatherData) } } catch (e) { console.log(e) return { statusCode: 500, body: 'Server error.' } } }
I left comments in the code to explain what is happening in the function. You can read more about Netlify functions on their documentation page.
Next, on line 18 of App.js
, change the URL to /.netlify/functions/weather
2. Add netlify.toml
file
This is a configuration file that Netlify needs so it can turn your function files into serverless functions.
Create the netlify.toml
file at the project root and add the code below.
[build] functions = "functions"
3. Deploy to Netlify
Push the changes to the repository.
$ git add netlify.toml functions/weather.js src/App.js $ git commit -m "add serverless function" $ git push
Log in to Netlify.
Click the button on the page that reads New Site from Git.
You will have to authorize Netlify to access your Github repositories.
After authorization, there will be a list of your Github repositories. Select the one that we just created.
On the next page, it will ask for a build step and publish directory. We do not have a build step so leave that blank.
Add public
to the publish directory input. Deploy the site.
It will build quickly and there will be a link for your new site!
If you click on the link you should see our app, but it won’t work yet because we didn’t add our API key to the environment variables.
4. Add API Key to Environment Variables
In Site Settings, select the Build & Deploy tab. Scroll towards the bottom and add our key to the environment variables section.
Remember, it needs to equal the value that we placed in the serverless function.
After adding the environment variable you can trigger a new deploy from the Deploys tab.
The application should be functioning at the URL you are provided!
To not incur usage charges, deactivate or delete the site.
Conclusion
The application morphed into almost seven different applications throughout the process. Some aspects change when we factor in how we want to deploy and how to handle things like security. Understanding the end environment can save you time later if you can develop the needed structure ahead of time.
Furthermore, choosing certain deployment services can afford better workflows. Or, you can choose an option that gives you control of the end environment and create your own custom workflow.
The probability that your React app will need to conceal secrets, API-keys, etc. is fairly high. Therefore, you should know some of the options that can offer this protection.
Deploying an app brings along new challenges, but the first time or two tends to be the most difficult. Soon, you develop a better understanding and can quickly set up application deployments that fit your needs. If you have any questions please leave a comment below!
Leave a Reply