How to deploy a Node/React App on a Linux server
Tags: webdev, linux, node, javascript
Published: 2022-09-30 15:50:43

When I started learning Node JS few weeks ago, the very first question that came to my mind was: hosting and deployment.

There are several solutions online (Netlify, Heroku etc) but this is for developers who will at some point find themselves in situations where they have to deploy on managed servers. 

So let's dive in with the following steps:
**1.**Provision a Linux Ubuntu server. Check out how to get one from Azure [in this article](https://dev.to/dreysongz/how-to-deploy-a-laravel-app-on-an-azure-ubuntu-server-1fl)

**2.**SSH into the machine and install node and npm globally: 
```js
sudo apt update

curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -

sudo apt install nodejs
```

Verify your node and npm installations:
```js
node -v

npm -v
```

**3.**cd into /var/www/html, create a folder where your node files will sit: 
```js
sudo mkdir node_project
```

Ensure to grant your user ownership over the folder so you can be able to make file edits:
```js
sudo chown -R $USER:www-data node_project
sudo chmod -R 775 node_project
```

Create a blank index.js file, which will be the entry point of the application.

```js
sudo nano index.js
```

**4.**Initialize and install the various dependencies needed for a Node project to run, i.e. the node_modules folder:
```js
npm init
```

The wizard will bring up several questions in the terminal, fill in some defaults and press the space key for the ones you have no answer for:



![Package.json init](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10rvwp8r7mk3t04yhiuq.png)
Type ok to confirm.

**5.**Edit the index.js file and bring in some quick-fire essential code to start up our node server:


![index.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hny1ydtmfga0bd9v0ztr.png)

**6.**Install dependencies needed in the node_modules folder. We only need express installed for this tutorial:
```js
npm install express
```

Test your project and see if its running:
```js
node index.js
```

![node server running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/or6dz4jlbhqknir6bh69.png)

**7.**The server is now running on the port specified (4000), once you go to http://(your-domain):4000 you should see the output. However, this is only good enough for development/staging. We do not want users having to access our application and having to type in port names, for SEO purposes and for the sake of branding. The solution is to make the Apache web server serve as reverse-proxy for the application so we would be able to type http://(your-domain) and still get to the node application.

Create a new virtual host file for our new app (Further reading: [Host several websites on a Linux server using Virtual Hosts](https://dev.to/dreysongz/how-to-host-several-websites-on-a-single-linux-server-1l5b))

```js
sudo nano /etc/apache2/sites-available/node-project.conf
```

Add the following lines:
```js
<VirtualHost *:80>
    ServerName www.node-project.com
    DocumentRoot /var/www/html/node_project

    ProxyRequests Off
    ProxyPreserveHost On
    ProxyVia Full

    <Proxy *>
        Require all granted
    </Proxy>

    ProxyPass / http://localhost:4000/
    ProxyPassReverse / http://localhost:4000/
</VirtualHost>
```
Save and close the file. Enable the config file:
```js
sudo a2ensite node-project.conf
```

Enable proxy modules:
```js
sudo a2enmod proxy proxy_http rewrite headers expires
```

Disable the default virtual host config file:
```js
sudo a2dissite 000-default
```

Restart Apache:
```js
sudo service apache2 restart
```

Navigating to your website will bring you to your Node app.

**8.**Node applications typically shut down after you manually stop the server, after SSH connection time-out or server restarts. Prevent this by making it a default start-up service to _always_ start up in the event of any of the above-mentioned factors. 

Create a systemd service file by the following command:
```js
sudo nano /lib/systemd/system/node-project.service
```

Copy and paste the following code:
```js
[Unit]
Description=Node.js Application
After=syslog.target network.target

[Service]
Type=simple
User=Aduramimo
WorkingDirectory=/var/www/html/node_project
Environment=NODE_ENV=production
ExecStart=/usr/bin/node index.js

Restart=always

[Install]
WantedBy=multi-user.target
```

The _After_ keyword signifies that this service should fire after essential Linux kernel files have loaded properly, _User_ will be your username to the linux server. We do not use root for security reasons. 
_WorkingDirectory_ is the absolute path to our project folder, _Environment_ signifies where in the app the service should target (this is also the same value in your NODE_ENV variable in the .env file), while _ExecStart_ is the actual process to be fired. We call the node daemon where it is been installed on the entry point already defined in step 4 above.

Save and exit.


**9.**Run the following code in turn:
```js
sudo systemctl daemon-reload
sudo systemctl start node-project
sudo systemctl enable node-project
sudo systemctl status node-project
```
You should get the following interface which means that the service is running successfully:
![Node service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1haqb89n3cof2x22hbz.png)

Please note that this will assume your default Node Terminal from now on. Any changes made to your server will be effected by the commands shown above and status also shown there.

Automatically refresh your Node server to watch for file changes in your entry point (index.js) by installing Nodemon with ```
npm install nodemon
``` in your project directory and replace ***/usr/bin/node***  with ***/usr/bin/nodemon*** in the _ExecStart_ command of the service created.

**10.**We are done deploying the Node application. Some Linux distros however, like CentOS or Redhat, might prevent the service file running due to some security feature known as SELinux (Security-Enhanced Linux) that is enabled by default. If this applies to you, follow the following steps to troubleshoot and resolve:

Check the status of SELinux :
```js
sudo sestatus
```

It will be something like this: 

![SELinux status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74oasn2h0w22morx0ifv.png)

Now we need to temporarily disable it by setting the status to **permissive**. This is like disabling SELinux but also logs critical error messages for us to troubleshoot the root cause.
```js
sudo nano /etc/sysconfig/selinux
```


![SELinux](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llgstxy1aizcxhzspzg0.png)

Changed **enforcing** to **permissive**.

Restart the server: ```js
 sudo reboot
``` and then check the status:
```js
 sudo sestatus
``` It should be on **permissive**.

The Node service file will be running now, however, our work is not done, since it is strongly advised not to turn off SELinux or stay on permissive mode.

Look out for the most recent error messages logged by the OS via ```js
sudo nano /var/log/messages
``` you should see something of the following nature in the console:

```
Oct  5 09:07:34 Aduramimo setroubleshoot[1811]: SELinux is preventing 
/usr/bin/node from using the execmem access on a process. 

For complete SELinux messages run: sealert -l e5cb75a6-c29c-4461-b716-d1a376e$
```

Follow the recommendation in the result. Note that you might get a query error of ID not found if you try to run the sealert command above. To get the full ID, run 
```js
sudo sealert -l '*' | grep ‘<‘id_from messages’>
```

Follow the action point suggested. A typical fix will be something like:
```
## sudo ausearch -c 'npm' --raw | audit2allow -M my-npm
## sudo semodule -X 300 -i my-npm.pp
```

We will now re-enable selinux to the enforced state by editing the config file and changing **permissive** to **enforcing** and reboot the server.

```js
sudo nano /etc/sysconfig/selinux

sudo reboot
```

**To deploy a React project:**
The above steps applies for react applications, except for step 7, where we change the React service file slightly:
```js
[Unit]
Description=React.js Application
After=syslog.target network.target

[Service]
Type=simple
User=Aduramimo
WorkingDirectory=/var/www/html/react_project
Environment=NODE_ENV=production
ExecStart=/usr/bin/npm start

Restart=always

[Install]
WantedBy=multi-user.target
```

The major difference is in the _ExecStart_ command which we adjust to reflect how a react app normally starts up.

If you installed Node and NPM via NVM on your server, the above might not work. You might try look at the following alternative steps if the React app fails to startup:

```js
[Unit]
Description=React.js Application
After=syslog.target network.target

[Service]
Type=simple
User=Aduramimo
WorkingDirectory=/var/www/html/react_project
Environment=NODE_ENV=production
ExecStart=/home/{USER}/react.sh

Restart=always

[Install]
WantedBy=multi-user.target
```

Where {USER} is your Linux server.

Navigate to your user's root directory and follow the following:
```js
sudo nano react.sh
```
Copy the following code:
```js
#!/bin/bash
. /home/{USER}/.nvm/nvm.sh
npm start
```

Then finally ```chmod +x /home/{USER}/react.sh```

Restart the systemd daemon and start the service.


You can also try the following method:

Replace the value of ExecStart in the systemd service file to ```/home/{USER}/.nvm/nvm-exec npm start```

For a Node application, Replace the value of ExecStart in the systemd service file to ``` /home/{USER}/.nvm/nvm-exec node /var/www/html/index.js ```