Mastering NJS: Your Complete Introduction

Mastering NJS: Your Complete Introduction

NJS, or njs Script, is a powerful subset of JavaScript specifically designed for extending NGINX and NGINX Plus functionality. It allows you to write custom logic, manipulate HTTP requests and responses, and create sophisticated configurations beyond the capabilities of standard NGINX directives. Think of it as JavaScript for your web server, providing unparalleled flexibility and control. This article serves as your complete introduction, covering its purpose, core concepts, and practical examples to get you started.

Why NJS? The Power of Programmability

NGINX is renowned for its performance, stability, and scalability. However, its declarative configuration, while efficient, can be limiting for complex scenarios. NJS bridges this gap, providing the advantages of imperative programming within the NGINX environment. Here’s why you should consider using NJS:

  • Dynamic Configuration: Move beyond static configurations. Generate configurations on-the-fly based on various factors (time of day, user agent, request headers, etc.).
  • Request and Response Manipulation: Modify headers, bodies, and other aspects of HTTP requests and responses with fine-grained control.
  • Content Generation: Generate dynamic content directly from NJS, bypassing the need for external applications in some cases.
  • Security Enhancements: Implement custom security logic, such as advanced rate limiting, request filtering, and authentication mechanisms.
  • Integration with External Systems: Interact with databases, caches, and other external services directly from NJS.
  • Improved Performance: In many cases, NJS can handle tasks more efficiently than routing requests to separate application servers, reducing latency and improving overall performance. This is because the logic executes within the NGINX process.
  • Declarative Integration: njs can be used in the main, http, stream, server, location, upstream context, and keyval context

Core Concepts: Understanding the NJS Landscape

Before diving into code, let’s grasp the fundamental building blocks of NJS:

  1. JavaScript Subset: NJS is not full-fledged JavaScript. It’s a carefully curated subset designed for security and performance within the NGINX environment. Key differences include:

    • No Browser APIs: window, document, and other browser-specific objects are absent.
    • Limited Standard Library: Only a subset of the standard JavaScript library is available, focusing on core functionalities relevant to server-side scripting. Built-in modules, include: crypto, fs, querystring.
    • Synchronous Execution: NJS code executes synchronously. There are no built-in asynchronous operations like setTimeout or Promise. Asynchronous operations can be added with the js_import directive.
    • Restricted Global Scope: The global scope is limited to prevent accidental interference with NGINX’s internal workings.
  2. NGINX Integration: NJS integrates seamlessly with NGINX through specific directives. The most important are:

    • js_include: Includes external NJS files. This allows you to organize your code into modules.
    • js_content: Defines a location handler that executes NJS code to generate the response.
    • js_set: Creates a variable whose value is the result of an NJS function. This allows you to use NJS to dynamically set NGINX variables.
    • js_header_filter: Executes NJS code after NGINX generates response headers, allowing for modification.
    • js_body_filter: Executes NJS code before NGINX sends the response body to the client, allowing for body modification.
    • js_access: Executes NJS code during the access phase. This can be used for authentication or authorization.
    • js_preread: (Stream module) Executes NJS code after NGINX reads the initial data from the client, before any protocol processing.
    • js_filter: (Stream module) Executes NJS code on data received from the client or upstream server.
    • js_import: Enables asynchronous behavior through import. Allows for imports of modules, including a default export, and exports of a module.
  3. The ngx Object: This is your primary interface to interact with the NGINX request and response. It provides properties and methods to access and manipulate various aspects, including:

    • ngx.req: The request object. Provides access to headers (ngx.req.headersIn), URI (ngx.req.uri), method (ngx.req.method), arguments (ngx.req.args), and more.
    • ngx.res: The response object (available in js_content, js_header_filter, and js_body_filter). Provides access to headers (ngx.res.headersOut), status code (ngx.res.status), and methods to send the response (ngx.res.send, ngx.res.sendBuffer).
    • ngx.log: Logs messages to the NGINX error log. Different log levels are available (e.g., ngx.log(ngx.INFO, "message")).
    • ngx.var: Accesses NGINX variables.
    • ngx.fetch: Asynchronously fetches a resource, returns a promise.
  4. Modules: NJS supports modularity through the js_include directive. You can create separate .js files containing functions and variables, and then include them in your main configuration or other NJS files. This promotes code organization and reusability.

Practical Examples: Putting NJS to Work

Let’s illustrate NJS’s capabilities with some practical examples:

Example 1: Simple Content Generation (js_content)

“`nginx

nginx.conf

http {
js_include /etc/nginx/conf.d/hello.js;

server {
    listen 80;
    server_name example.com;

    location /hello {
        js_content hello.handler;
    }
}

}
“`

“`javascript
// /etc/nginx/conf.d/hello.js
function handler(r) {
r.headersOut[‘Content-Type’] = ‘text/plain’;
r.return(200, ‘Hello from NJS!’);
}

export default {handler};
“`

This example defines a location /hello that uses js_content to execute the handler function in hello.js. The function sets the Content-Type header and sends a “Hello from NJS!” response.

Example 2: Modifying Request Headers (js_set)

“`nginx

nginx.conf

http {
js_include /etc/nginx/conf.d/add_header.js;
js_set $my_header add_header.add;
server {
listen 80;

    location / {
        proxy_set_header X-My-Header $my_header;
        proxy_pass http://backend;
    }
}

}
“`

javascript
// /etc/nginx/conf.d/add_header.js
function add(r) {
return "Value-" + r.headersIn['User-Agent'];
}
export default {add};

This example uses js_set to create a variable $my_header whose value is the result of the add function. The function takes the User-Agent header and prepends “Value-” to it. This new value is then added as the X-My-Header in the request sent to the backend server.

Example 3: Header Filtering (js_header_filter)

“`nginx

nginx.conf

http {
js_include /etc/nginx/conf.d/filter.js;

server {
    listen 80;

    location / {
        proxy_pass http://backend;
        js_header_filter filter.addPoweredBy;
    }
}

}
“`

“`javascript
// /etc/nginx/conf.d/filter.js
function addPoweredBy(r) {
r.headersOut[‘X-Powered-By’] = ‘NGINX with NJS’;
}

export default {addPoweredBy};
“`

This example uses js_header_filter to execute the addPoweredBy function after the backend server has generated the response headers. The function adds an X-Powered-By header to the response.

Example 4: Simple Request Logging

javascript
// /etc/nginx/conf.d/log_request.js
function logRequest(r) {
ngx.log(ngx.INFO, "Request URI: " + r.uri + ", Method: " + r.method);
}
export default {logRequest};

“`nginx

nginx.conf

http {
js_include /etc/nginx/conf.d/log_request.js;

server {
    listen 80;

    location / {
         js_access log_request.logRequest;
        proxy_pass http://backend;
    }
}

}
“`

This logs the request URI and method to the NGINX error log at the INFO level for every request to the / location.

Example 5: Asynchronous Fetch (js_import)

“`javascript
// /etc/nginx/conf.d/fetch.js
async function fetchExample(r) {
try {
let res = await ngx.fetch(‘http://example.com’);
let body = await res.text();
r.return(200, body);

} catch (e) {
r.return(500, e);
}
}

export default { fetchExample };

“`

“`nginx

nginx.conf

http {
js_import /etc/nginx/conf.d/fetch.js;

server {
    listen 80;

    location /fetch {
        js_content fetch.fetchExample;
    }
}

}
“`
This example demonstrates how to fetch data from external sources, such as an API endpoint or another website.

Getting Started: Installation and Setup

To use NJS, you need an NGINX or NGINX Plus installation that includes the NJS module.

  • NGINX Plus: NJS is included by default.
  • NGINX (Open Source): You need to install the nginx-module-njs package. The installation process varies depending on your operating system and package manager (e.g., apt install nginx-module-njs, yum install nginx-module-njs). You can also compile NGINX from source with the --with-http_js_module and --with-stream_js_module (if you need stream module support) configuration options.

Debugging and Troubleshooting

  • NGINX Error Log: The NGINX error log is your primary debugging tool. Use ngx.log to output diagnostic messages.
  • ndk_debug_output: This directive (part of the ngx_http_js_module) enables more verbose debugging output, including stack traces for NJS errors.
  • Syntax Errors: NJS is strict about JavaScript syntax. Carefully review your code for errors. The NGINX error log will usually provide helpful error messages pointing to the line number and type of error.

Conclusion: Unleash the Potential of NJS

NJS empowers you to extend NGINX far beyond its traditional capabilities. By understanding its core concepts and leveraging its integration with NGINX, you can create highly customized, performant, and dynamic web server configurations. This introduction provides a solid foundation for mastering NJS; explore the official documentation and experiment with the examples to unlock its full potential. With NJS, you’re not just configuring a web server – you’re programming it.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top