Build a Simple IoT Device Tracker and Deploy to AWS EC2
This is a quick demo of how to build a web app full-stack, from front to back, and make it interact with MongoDB Atlas.
# Whenever I start running my server on EC2, this link will work, or you will see a blank list :D
http://52.198.4.100/
Demo
Main Steps
- Install packages and set up initial files
- Set up backend .js files
- Set up frontend .js files
- Deploy to AWS EC2
File Structure
Initiate Project
Code Snippets in Steps
Create server.js in /backend. Create connection to MongoDB:
Create /backend/models, and create device.model.js and account.model.js. In account.model.js, create a schema. The account name will be unique, no whitespace, etc.:
In device.model.js, we have 4 fields:
Create /backend/routes, and create devices.js and accounts.js. Go back to server.js first. We’re telling the server to use those files — devices.js and accounts.js.
So when people go to /devices page, the server uses devicesRouter i.e. ./routes/devices.js to render the page.
In accounts.js, handle incoming HTTP get a request from ‘/’ route e.g. .find() will give a list of all users in MongoDB. After finding the promise, the server will return users in JSON format. If there’s an error, there’ll be an error message:
In the ‘/add’ route, in the request body, there’ll be a username and then create a new account instance to save into MongoDB. Eventually, the server will return a response in JSON:
In devices.js, apply similar logic but add e.g. update by id:
We can now test our routes by Postman. Use Postman “body + raw + JSON” to test one of the routes e.g. ‘/update/:id’ by Post method:
So far backend is ready. Now let’s take a look at the front end.
Add below code in /src/index.js. Here App Component is the main component in React which acts as a container for all other components:
Import all separated components files in App.js and start to add routers here. I will add each component accordingly. Here I only show navbar and create-device components details:
In the navbar component, the Link path is the same as what’s stated in App.js. It will help direct users to different components:
In the create-device component, add a constructor. The constructor aids in constructing things like defaulting some properties of the created object.
There’s a note on React document specifying that in the constructor, we should add super(props) before any other statement. Simply put, super refers to the parent class constructor. Only after we called the parent constructor’s property (here is the React. Component) then we can use this. state to overwrite that property and set the local state of the object.
The first step in the create-device component is like:
In this. state, accounts are an “empty” array because we will have a drop-down list for users to choose different accounts.
We can then use componentDidMount() to make sure the component will load from the database (Axios will help us get an array of accounts which is each document object’s account name value) after the HTML from render has finished loading, and set the first account name as default display:
Since there’ll be text boxes to the input account name, description, duration, etc. The following methods were created. Target is the textbox, and this.setState() will make sure the value inside the textbox will change according to whatever is input by users.
When rendering the page, using React events only requires adding a listener when the element is initially rendered, so in the rendering part, it will look like this.methodName:
Add submit method. e.preventDefault() helps prevent HTML submit a form from taking place i.e. making the submit function clean before we specify what to pass to the database. Here when we’re working on connecting the frontend to the backend, we’re asking the frontend to send an HTTP request to the backend. Here I tried Axios library to send HTTP requests.
Once submit is clicked, send device info to the path ‘/devices’ which is the same as server.js, then server.js will then jump to /routes/devices.js, and ‘/add’, submit button will help us send the data to MongoDB to save data there.
In React constructors, other than setting the initial local state of the object, we can also bind event handlers methods to the object. Since onSubmit will change variable state by this.setState. Make sure this method is bound to this. state’s this:
Deploy
- Launch an EC2 instance with Ubuntu 18.04
- The protocol is TCP: Nginx port 80, Node port 5000, MongoDB port 27017
- Log in EC2
$ ssh -i <our .pem file> <VM>@<Public DNS of our instance>
- Create two folders — client (also a deploy folder) & server here
$ cd /home/ubuntu
$ sudo mkdir server
$ sudo mkdir client
$ cd client
$ sudo mkdir deploy# nginx will save all log files in server_logs folder
$ sudo mkdir server_logs
- Install packages
$ sudo apt-get install nodejs
$ sudo apt-get install nginx# download node.js dependencies on the server side
$ sudo apt install npm
$ cd /home/ubuntu/server
$ npm install
- Edit Nginx conf files — customize Nginx files as needed
# clear all default settings: Ctrl + k
$ sudo nano /etc/nginx/nginx.conf# another conf file:
$ cd /etc/nginx/conf.d
$ sudo touch default.conf
$ sudo nano default.conf# once complete, restart nginx
$ sudo service nginx restart
- Make sure our EC2 instance public IP is whitelisted on MongoDB
- Whenever I want to see this web app running, I will run below before I go to my web app public IP http://52.198.4.100/:
$ ssh -i <our .pem file> <VM>@<Public DNS of our instance>
$ cd /home/ubuntu/server
$ npm run start
Features to improve
Below are several features I may be able to extend.
- Make it more visually appealing
- Add submit success messages if possible
That’s it! This post was inspired by these two materials, here and here.
Thanks for reading and have a wonderful day ahead!