Since introducing the NGINX JavaScript module (njs) in 2015 (under its original name, nginScript) and making it generally available in 2017, we have steadily continued to add new features and refine our implementation across dozens of version updates. Normally we wait for an NGINX Plus release to discuss the features in a new NGINX JavaScript version, but we’re so excited about version 0.7.7 that this time we can’t wait!

The significant enhancements in njs 0.7.7 help make your NGINX configuration even more modular, organized, and reusable:

  • You can now declare njs code in the contexts where it applies rather than globally, bringing your custom code closer to the point of usage.
  • Hooks in the njs code itself enable you to modify behavior depending on the execution context.
  • The new fs.FileHandle object makes file operations more efficient.

To learn more about njs and review the list of use cases for which we provide sample code, read Harnessing the Power and Convenience of JavaScript for Each Request with the NGINX JavaScript Module on our blog.

For a complete list of all new features and bug fixes in njs 0.7.7, see the Changes documentation.

Declaring JavaScript Code and Variables in Local Contexts

In previous njs versions, you have to import your JavaScript code and declare the relevant variables – with the js_import, js_path, js_set, and js_var directives – in the top‑level http or stream context, the equivalent of declaring global variables at the top of a main file. But the directives that actually invoke the JavaScript functions and variables appear in a child context – for example, with the js_content directive in an HTTP location{} block and the js_access directive in a Stream server{} block. This creates two issues:

  1. To someone reading through the configuration, the declarations in the http and stream contexts are essentially noise, because there’s no indication where the associated code and variables are actually used.
  2. It’s not obvious in the child context where the code and variables have been imported and declared. Though we recommend including the http{} and stream{} blocks only in the main configuration file (nginx.conf) and using the include directive to read in smaller function‑specific configuration files from the /etc/nginx/conf.d and /etc/nginx/stream.d directories, NGINX configuration is flexible – you can include http{} and stream{} blocks in multiple files. This can be especially problematic in environments where multiple people work on your NGINX configuration and might not always follow established conventions.

In njs 0.7.7 and later, you can import code and declare variables in the contexts where they’re used:

  • HTTP –

    • These directives can appear in the server and location contexts as well as the http context:

      • js_import
      • js_path
      • js_set
      • js_var
    • These directives can appear in the if context as well as the location and limit_except contexts:

      • js_body_filter
      • js_content
      • js_header_filter
  • Stream –These directives can appear in the server context as well as the stream context:

    • js_import
    • js_path
    • js_set
    • js_var

Having all njs configuration for a specific use case in a single file also makes your code more modular and portable.

As an example, in previous njs versions when you added a new script you had to change both nginx.conf (adding js_import and possibly js_path, js_set, and js_var) and the file where the JavaScript function is invoked (here, jscode_local.conf).

In njs 0.7.7 and later, all the configuration related to the util function is in the one file, jscode_integrated.conf:

Modifying Behavior Depending on the Execution Context

Several new features in njs 0.7.7 enable you to modify the behavior of your JavaScript code depending on the context (processing phase) where it is executing.

The HTTP r.internal Property

The HTTP r.internal property is a Boolean flag set to “true” for internal requests (which are handled by location{} blocks that include the internal directive). You can use the r.internal flag to fork logic when a script uses a general event handler that can be called in both internal and non‑internal contexts.

The following classify as internal requests:

  • Requests redirected by the error_page, index, random_index, and try_files directives
  • Requests redirected by the X-Accel-Redirect response header from an upstream server
  • Subrequests invoked by the auth_request and mirror directives, the directives in the ngx_http_addition_module module, or the Server Side Include (SSI) include virtual command (supported by the ngx_http_ssi_module module)
  • Requests changed by the rewrite directive

Improved s.send() Stream Method

In earlier njs versions, the Stream s.send() method is context‑dependent, because the direction in which it sends data is determined by the location (upstream or downstream) of the callback where the method is called. This works fine for synchronous callbacks – which s.send() was originally designed for – but fails with asynchronous functions such as ngx.fetch().

In njs 0.7.7 and later, the direction is stored in a separate internal flag, which s.send() can then use.

More Efficient File Operations with the New fs.FileHandle() Object

The file system module (fs) implements operations on files. The new FileHandle object in the fs module is an object wrapper for a numeric file descriptor. Instances of the FileHandle object are created by the method.

Use the FileHandle object to get a file descriptor, which can be further used to:

  • Perform functions like read() and write() on the file
  • Open a file and perform reads and writes at a specified location without reading the whole file

The following properties of FileHandle have been implemented (for information about the required and optional arguments for each property, see the documentation):

  • filehandle.fd
  • filehandle.stat()
  • filehandle.write()
  • filehandle.write()
  • filehandle.close()

These methods have been updated to support FileHandle (see the linked documentation for information about each method’s arguments):

  • fs.openSync()
  • fs.fstatSync()
  • fs.readSync()
  • fs.writeSync() (buffer)
  • fs.writeSync() (string)

Use njs to Enhance Your Configuration

With njs 0.7.7, we’ve made it easier for your teams to work on and share njs code. The extended contexts for njs directives make it even more straightforward to enhance NGINX configuration with custom JavaScript code. You can make the first move towards an API gateway, reverse proxy, or web server – and one that is more than just another middleware or edge component. You can make it part of your application through JavaScript, TypeScript, or third‑party node modules without adding another component in your stack. All you need is NGINX!

Have questions? Join the NGINX Community Slack and check out the #njs-code-review channel to learn more, ask questions, and get feedback on your njs code.

The post Make Your NGINX Config Even More Modular and Reusable with njs 0.7.7 appeared first on NGINX.