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/testing 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 and 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.
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.
Document
This is index html file from nginx-poc folder
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 –
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
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 –
- It is time-consuming and is error-prone.
- 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;
}
}
}
The highlighted ones are the new changes, let me go through each change to make it clearer —
- 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.
- 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: —
- 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.
- 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_name;
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 will 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"';
Error log
This reports 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'
e.g.
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.
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
Case 2
request is for b.js → serve c.js iff query params have latest=false
Case 3
request is for b.js → serve a.js default
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. This article is also published on medium, keep visiting that also for regular updates.
Please share the articles on –
The latest tips and news from the industry straight to your inbox!
Join 30,000+ subscribers for exclusive access to our monthly news and tutorials!
Pingback: Binary Tree and its traversal in Javascript - Weekend Tutorial