Harnessing the Power and Convenience of JavaScript for Each Request with the NGINX JavaScript Module

NGINX | April 23, 2021

Editor – The blog post titled “Introduction to the NGINX JavaScript Module” redirects here. The post has been updated with the NGINX JavaScript Module directives and features supported as of April 2021.

The NGINX JavaScript module (njs) became generally available as a stable module in NGINX Open Source 1.11.10 and NGINX Plus R12. [The module was originally called nginScript, and that name appears in some older posts.] We have been working steadily on NGINX JavaScript since its launch in September 2015, adding the features and language support included in the stable module.

NGINX JavaScript is a unique JavaScript implementation for NGINX and NGINX Plus, designed specifically for server‑side use cases and per‑request processing. It extends NGINX configuration syntax with JavaScript code in order to implement sophisticated configuration solutions.

The use cases are extensive, especially as the NGINX JavaScript module is available for both HTTP and TCP/UDP protocols. Sample use cases for NGINX JavaScript include:

Before discussing NGINX JavaScript in more detail, let’s first address two common misconceptions.

NGINX JavaScript Is Not Lua

The NGINX community has created several programmatic extensions over the years. At the time of writing, Lua is the most popular of these; it’s available as a module for NGINX and a supported, prebuilt third‑party dynamic module for NGINX Plus. The Lua module and add‑on libraries provide deep integration with the NGINX core and a rich set of functionality, including a driver for Redis.

Lua is a powerful scripting language. It, however, remains fairly niche in terms of adoption and is not typically found in the “skillset toolbox” of the frontend developer or DevOps engineer.

NGINX JavaScript does not seek to replace Lua and it will be some time before NGINX JavaScript has a comparable level of functionality. The goal of NGINX JavaScript is to provide programmatic configuration solutions to the widest possible community by using a popular programming language.

NGINX JavaScript Is Not Node.js

NGINX JavaScript does not aim to turn NGINX or NGINX Plus into an application server. In simple terms, the use cases for NGINX JavaScript are akin to middleware, as the execution of JavaScript code happens between the client and the content. Technically speaking, while Node.js shares two things with the combination of NGINX JavaScript and NGINX or NGINX Plus – an event‑driven architecture and the JavaScript programming language – the similarities end there.

Node.js uses the Google V8 JavaScript engine, whereas NGINX JavaScript is a bespoke implementation of the ECMAScript standards, designed specifically for NGINX and NGINX Plus. Node.js has a persistent JavaScript virtual machine (VM) in memory and performs routine garbage collection for memory management, whereas NGINX JavaScript initializes a new JavaScript VM and the necessary memory for each request and frees the memory when the request is completed.

JavaScript as a Server-Side Language

As mentioned above, NGINX JavaScript is a bespoke implementation of the JavaScript language. All other existing JavaScript runtime engines are designed to be executed within a web browser. The nature of client‑side code execution is different from server‑side code execution in many ways – from the availability of system resources to the possible number of concurrent runtimes.

We decided to implement our own JavaScript runtime in order to meet the requirements of server‑side code execution and fit elegantly with NGINX’s request‑processing architecture. Our design principles for NGINX JavaScript are these:

  • Runtime environment lives and dies with the requestThe NGINX JavaScript module uses single‑threaded bytecode execution, designed for quick initialization and disposal. The runtime environment is initialized per request. Startup is extremely quick, because there is no complex state or helpers to initialize. Memory is accumulated in pools during execution and released at completion by freeing the pools. This memory management scheme eliminates the need to track and free individual objects or to use a garbage collector.
  • Non‑blocking code executionNGINX and NGINX Plus’ event‑driven model schedules the execution of individual NGINX JavaScript runtime environments. When an NGINX JavaScript rule performs a blocking operation (such as reading network data or issuing an external subrequest), NGINX and NGINX Plus transparently suspend execution of the associated NGINX JavaScript VM and reschedule it when the event completes. This means that you can write rules in a simple, linear fashion and NGINX and NGINX Plus schedule them without internal blocking.
  • Implement only the language support that we needThe specifications for JavaScript are defined by the ECMAScript standards. NGINX JavaScript follows ECMAScript 5.1 with some ECMAScript 6 for mathematical functions. Implementing our own JavaScript runtime gives us the freedom to prioritize language support for server‑side use cases and ignore what we don’t need. We maintain a list of the currently supported language elements.
  • Close integration with request‑processing phasesNGINX and NGINX Plus process requests in distinct phases. Configuration directives typically operate at a specific phase and native NGINX modules often take advantage of the ability to inspect or modify a request at a particular phase. NGINX JavaScript exposes some of the processing phases through configuration directives to give control over when the JavaScript code is executed. This integration with the configuration syntax promises the power and flexibility of native NGINX modules with the simplicity of JavaScript code.The table below indicates which processing phases are accessible via NGINX JavaScript at the time of writing, and the configuration directives that provide it.
Processing PhaseHTTP ModuleStream Module
Access – Authentication and access controlauth_request and js_contentjs_access
Pre-read – Read/write payloadN/Ajs_preread
Filter – Read/write response during proxyjs_body_filter
js_header_filter
js_filter
Content – Send response to clientjs_contentN/A
Log / Variables – Evaluated on demandjs_setjs_set

Getting Started with NGINX JavaScript – A Real‑World Example

NGINX JavaScript is implemented as a module that you can compile into an NGINX Open Source binary or dynamically load into NGINX or NGINX Plus. Instructions for enabling NGINX JavaScript with NGINX and NGINX Plus appear at the end of this article.

In this example we use NGINX or NGINX Plus as a simple reverse proxy and use NGINX JavaScript to construct access log entries in a specialized format, that:

  • Includes the request headers sent by the client
  • Includes the response headers returned by the backend
  • Uses key‑value pairs for efficient ingestion into and searching with log processing tools such as the ELK Stack (now called Elastic Stack), Graylog, and Splunk

The NGINX configuration for this example is extremely simple.

Loading gist…

As you can see, NGINX JavaScript code does not sit inline with the configuration syntax. Instead we use the js_import directive to specify the file that contains all of our JavaScript code. The js_set directive defines a new NGINX variable, $access_log_headers, and the JavaScript function that populates it. The log_format directive defines a new format called kvpairs which writes each log line with the value of $access_log_headers.

The server block defines a simple HTTP reverse proxy that forwards all requests to https://www.example.com. The access_log directive specifies that all requests will be logged with the kvpairs format.

Let’s now look at the JavaScript code that prepares a log entry.

Loading gist…

The return value from the kvAccess function – a log entry – is passed to the js_set configuration directive in rawheader_logging.conf. Bear in mind that NGINX variables are evaluated on demand and this in turn means that the JavaScript function defined by js_set is executed when the value of the variable is required. In this example, $access_log_headers is used in the log_format directive and so kvAccess() is executed at log time. Variables used as part of map or rewrite directives (not illustrated in this example) trigger the corresponding JavaScript execution at an earlier processing phase.

We can see this NGINX JavaScript‑enhanced logging solution in action by passing a request through our reverse proxy and observing the resulting log file entry, which includes request headers with the in. prefix and response headers with the out. prefix.

[@portabletext/react] Unknown block type "codeBlock", specify a component for it in the `components.types` prop

Much of the utility of NGINX JavaScript is a result of its access to NGINX internals. This example utilizes several properties of the request (r) object. The Stream NGINX JavaScript module (for TCP and UDP applications) utilizes a session object (s) with its own set of properties. For other examples of NGINX JavaScript solutions for both HTTP and TCP/UDP, see Use Cases for the NGINX JavaScript Module.

We’d love to hear about the use cases that you come up with for NGINX JavaScript – please tell us about them in the comments section below.

Use Cases for the NGINX JavaScript Module

Check out these blog posts to explore other HTTP and TCP/UDP use cases for the NGINX JavaScript module:

[ngx_snippet name=’njs-enable-instructions’]


Share

About the Author

Liam Crilly
Liam CrillySr Director, Product Management

More blogs by Liam Crilly

Related Blog Posts

Automating Certificate Management in a Kubernetes Environment
NGINX | 10/05/2022

Automating Certificate Management in a Kubernetes Environment

Simplify cert management by providing unique, automatically renewed and updated certificates to your endpoints.

Secure Your API Gateway with NGINX App Protect WAF
NGINX | 05/26/2022

Secure Your API Gateway with NGINX App Protect WAF

As monoliths move to microservices, applications are developed faster than ever. Speed is necessary to stay competitive and APIs sit at the front of these rapid modernization efforts. But the popularity of APIs for application modernization has significant implications for app security.

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh
NGINX | 12/09/2021

How Do I Choose? API Gateway vs. Ingress Controller vs. Service Mesh

When you need an API gateway in Kubernetes, how do you choose among API gateway vs. Ingress controller vs. service mesh? We guide you through the decision, with sample scenarios for north-south and east-west API traffic, plus use cases where an API gateway is the right tool.

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services
NGINX | 01/20/2021

Deploying NGINX as an API Gateway, Part 2: Protecting Backend Services

In the second post in our API gateway series, Liam shows you how to batten down the hatches on your API services. You can use rate limiting, access restrictions, request size limits, and request body validation to frustrate illegitimate or overly burdensome requests.

New Joomla Exploit CVE-2015-8562
NGINX | 12/15/2015

New Joomla Exploit CVE-2015-8562

Read about the new zero day exploit in Joomla and see the NGINX configuration for how to apply a fix in NGINX or NGINX Plus.

Why Do I See “Welcome to nginx!” on My Favorite Website?
NGINX | 01/01/2014

Why Do I See “Welcome to nginx!” on My Favorite Website?

The ‘Welcome to NGINX!’ page is presented when NGINX web server software is installed on a computer but has not finished configuring

Deliver and Secure Every App
F5 application delivery and security solutions are built to ensure that every app and API deployed anywhere is fast, available, and secure. Learn how we can partner to deliver exceptional experiences every time.
Connect With Us
Harnessing the Power and Convenience of JavaScript for Each Request with the NGINX JavaScript Module | F5