Learn Nginx and its basics in 2022
Tags: webdev, nginx, node, javascript
Published: 2022-02-05 08:46:53

---
canonical_url: https://weekendtutorial.com
---

A few weeks ago, I had to do a complex redirect of a request e.g if the request coming to `nginx` has a specific query param or is coming from a specific host then internally redirect to a different path. 

I had completed my logic and was certain about its working but as the saying goes — 

> Your software will eventually fail unless it's verified with all the edge cases.

However, due to our system dependency, I cannot merge the code to staging dev for testing as in case of `nginx` failure, it will block other developers to write/test their node API or client code.

So, to mitigate this, I have set up the `nginx` in my local computer and did thorough testing. Once it’s fine locally, the code is ready to be pushed in staging for further testing. This way I save lots of time without hampering others' work.

In this article, I’ll walk through the basics of `nginx`, installation, and set up locally, setting up logs, and a few others.

Let’s start with the definition of `nginx`.

## What is Nginx?

`Nginx` is a short form of _`engine x`_ is an `HTTP` and `reverse proxy server`. It is heavily used as a load balancer and serves static files, sometimes even complete static websites like the company’s blog hosted on Nginx!.

### Load balancer

In simple terms, the Load balancer is like a middle man sitting in between the concerned parties e.g assume A is the list of clients and B is the list of the servers then —

#### Case 1: With No Load Balancer

All incoming requests would be going to just one server which in the worst case, makes it hang or crash. You probably have heard the term _`Node API or Service API is down`_ which means the box or the server serving that API request is hung or crashed due to request overload or OutOfMemory etc. Thus making the UX unresponsive.

#### Case 2: With Load Balancer

All incoming requests will have to go through the Load Balancer. It maintains the routing table and gets notification if any of the boxes or server goes down (through polling). 

It efficiently distributes the network requests across servers and if one server goes down it redirects the requests to others servers that are online. Thus, maintaining the availability of the server always online.

---

## Nginx Configuration File

This file is a _`tree-like`_ structure and contains the instructions in the form of rules/blocks.

```
# main context (outside any other context i.e global context)

# event context
event {
  worker_connections 1024;
}

#http context
http {
  
  # server context 
  server {
     # code goes here
  }
  server {
     # another server context, code goes here
  }
}
```

Before we dive into creating our own web server, let's learn the `Nginx` configuration file structure in a crisp mode -

### Main Context —

The `main context a.k.a. global context` is the topmost context and all other contexts are part of it e.g `Event context, HTTP context`. It is used to configure details that affect the entire application on a granular level.

### Event Context —

Event Context is contained within the `Main context`. It deals with connection handling in general. All the directives defined within this context deals with how worker processes should handle the incoming connections.

### The HTTP Context —

This is the sibling of the `Event context` and written side-by-side of the event context rather than nested. This context will hold the majority of the configurations if we are using Nginx as a web server or reverse proxy.

**_Note:-_**

There can only be one `Event context and HTTP context` within the `nginx` configuration.

Later in the article, We will see 2 more contexts — server context and location context.

---

## How to install Nginx in macOS?

If you are not using brew then install it first. Open your terminal and do the followings —

install brew

```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

Once the brew is installed, do

```
brew install nginx
```

Once nginx is installed, you can verify it by

```
nginx -v
```

Above should print `nginx version: nginx/<some version number>`

```
e.g.
nginx version: nginx/1.21.0
```

Once nginx is installed, the brew will create the nginx folder at the below location -

```
/usr/local/etc/nginx
```

the default `nginx.conf` will look like this -

```
events {
  worker_connections 1024;
}

http {
  server {
    listen       8080;
    server_name  localhost;
    location / {
      root   html;
      index  index.html index.htm;
    }
  }
}
```

To start the `nginx services` do the following —

```
nginx 
```

if there is any error it will be logged in the terminal, to test if it is serving the default HTML file, hit the URL -

```
http://localhost:8080
```

and to stop it —

```
nginx -s stop
```

---

## How to serve files from a different location?

Let's modify the `nginx.conf` file to read the HTML file from a different location —

First, create a folder that contains the HTML file `index.html` (with below content) that you want to serve e.g I’ve created `nginx-poc` inside the Download folder.

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>This is index html file from nginx-poc folder</h1>
</body>
</html>
```

Copy the complete path of this folder that will be the `root` and open the `nginx.conf` to update it

using vim editor or nano or any other way you prefer to open the file (e.g nano) —

```
nano /usr/local/etc/nginx/nginx.conf
```

and update the root location

```
events {
  worker_connections 1024;
}

http {
  server {
    listen       8080;
    server_name  localhost;
    location / {
      root   /Users/Download/nginx-poc;
      index  index.html index.htm;
    }
  }
}
```

Now stop the `nginx` and start it again to see the updated changes. Hit the localhost URL with port `8080` -


![Nginx Serving HTML from different location](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ypx0f0obhdo7si0a54kv.png)

---

## How to do Url redirection?

Sometimes, the need may arise for URL redirection when the existing resource is moved to a different location. Let’s see how can we achieve this —

Create a `js` folder and a few js files in it inside the nginx-poc folder —

```
|-- nginx-poc
| |-- index.html
| |-- js
| | |-- a.js
| | |-- b.js
| | |-- c.js
| | |-- b-latest.js
```


![Folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfyxgc5rpdie63mglvm7.png)

Just have one simple `console.log(<filename>)` inside each js file -

e.g for a.js

```
console.log('serving a.js');
```

for b-latest.js

```
console.log('serving b-latest.js');
```

and so on.

Let’s say the file `b.js` is no longer useful and we want to serve the `b-latest.js` in place of it. Of course, we can say that wherever our anchor link is pointing to `b.js` we’ll replace it with the `b-latest.js`, but it has 2 issues -

1. It is time-consuming and is error-prone.
2. The old URL will give the 404 and that's something we should thrive to reduce. Ideally, there shouldn’t be any 404 redirects, it should be kept as low as possible.

One simple solution would be to do it from `nginx` internal redirect —

```
events {
  worker_connections 1024;
}

http {
  server {
    listen       8080;
    server_name  localhost;

    root   /Users/Download/nginx-poc;
    
    location /js {
      rewrite /js/b.js /js/b-latest.js break;
    }
    
    location / {
      # root   /Users/Download/nginx-poc;
      index  index.html index.htm;
    }
  }
}
```

Let me go through each change to make it clearer —

1. **Commented root in location / —** This is moved to the server context. Adding the root to the server context makes it available for all the location contexts within it.

2. **Added location context to handle the /js request —** This request will handle all the request coming from `/js, /js/*` i.e request for `/js/b.js` will fall in this location. We are rewriting the request URL internally from `/js/b.js` to `/js/b-latest.js` and then we are adding a `break` which means no more parsing of any other rewrite!

**_Note:—_**

1. The `server context` is a child of the `HTTP context`. There could be multiple server contexts, unlike event and HTTP context which are allow allowed once.

2. The `location context` is a child of the `server context`. Similar to server context, multiple location contexts are allowed. They are the ones where actual handling of the incoming request is done.

---

## How to add custom logs?

Logs are really important, they are needed to test our logic. In case any issue comes in production code, we can easily debug by seeing the `nginx logs`. Let me show you how can we set it up locally so that we can test and view the complete logic along with logs in localhost.

By default, `nginx` has 2 types of logs — `access log and error log`

### Access log — 

This logs the visitor's activity e.g requested URL, IP addresses, host, referrer, etc. If a request is served successfully it will log in access.log file.

```
access_log <location of log file> log_format;
```

In `log_format`, we can add what data we want to log but just a note, it is an optional thing. 

One important point to remember is log_format must be placed under the `HTTP context` otherwise it will throw an error.

e.g.

```
Syntax - 

log_format <log_format_name> string;

log_format custom '$remote_addr - $remote_user [$time_local] '
'"$request" "$uri" $status $body_bytes_sent ''"$http_referer" "$http_user_agent" "$gzip_ratio"';
```


![Sample Nginx Access log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q93w3htz2e0wvctahib1.png)

### Error log — 

This logs each glitch and Syslog i.e if a request was not served by any means it will be recorded in the `error.log` file.

```
Syntax -

error_log <location of error.log file> <error-severity-level>

error_log /usr/local/etc/nginx/logs/error.log notice;
```

Configuration file after adding the logs —

```
events {
  worker_connections 1024;
}

http {
  log_format custom '$remote_addr - $remote_user [$time_local] '
      '"$request" "$uri" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent" "$gzip_ratio"';
  
  server {
    
     listen       80;
    
     server_name  localhost;
    
     root   /Users/Downloads/nginx-poc;
     
     access_log /usr/local/etc/nginx/logs/acess.log custom;
     error_log /usr/local/etc/nginx/logs/error.log notice;
     rewrite_log on;
     
     location /js {
       rewrite /js/b.js /js/b-latest.js break;
     }
     
     location / {
       index  index.html index.htm;
     }
   }
}
```

The `rewrite_log` should be **ON** to record the internal redirect. Also, `notice` severity level means it is just a notice which can be simply ignored i.e nothing serious.


![Nginx error.log logging the redirect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzru4qgo92ln43c3rpot.png)

---

## How to handle query params?

It may arise a case when we want to internally redirect the request based on query parameters. Let’s see how can we implement the below 3 cases in the `nginx.conf` file —

```
events {
  worker_connections 1024;
}

http {
  log_format custom '$remote_addr - $remote_user [$time_local] '
      '"$request" "$uri" $status $body_bytes_sent '
      '"$http_referer" "$http_user_agent" "$gzip_ratio"';
  
  server {
    
     listen       80;
    
     server_name  localhost;
    
     root   /Users/Downloads/nginx-poc;
     
     access_log /usr/local/etc/nginx/logs/acess.log custom;
     error_log /usr/local/etc/nginx/logs/error.log notice;
     rewrite_log on;
     
     location /js {
       if ($query_string ~* "latest=true") {
         rewrite /js/b.js /js/b-latest.js break;
       }
       
       if ($query_string ~* "latest=false") {
         rewrite /js/b.js /js/c.js  break;
       }
       
       rewrite /js/b.js /js/a.js break;
     }
     
     location / {
       index  index.html index.htm;
     }
   }
}
```

### Case 1 — 

request is for `b.js` → serve `b-latest.js` iff query params have `latest=true`


![serve b-latest.js case 1 when query param latest is true](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bcf2pwcx3c1atmcqq7sq.png)

### Case 2 — 

request is for `b.js` → serve `c.js` iff query params have `latest=false`


![serve c.js case 2 when query param latest is false](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gso0f3iqy73k4ablz2yq.png)

### Case 3 — 

request is for `b.js` → serve `a.js` default


![No Query param](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uxlon3e0663lsqhz5xyu.png)

---

## Conclusion

Nginx is not just it, and can’t be covered in just one article. But, I hope this will let you get started to know more. With the local setup, It becomes really handy when you want to test your `nginx` logic locally before deploying it to your staging or production.

I really hope you like the article, if yes, please follow me and if possible [buy me a coffee](https://www.buymeacoffee.com/ajayv). This article is originally published on my [website](https://weekendtutorial.com), keep visiting that also for regular updates.

Thank You! Stay tuned for more articles.