NGINX JavaScript (NJS)

repository·master·Indexed 23 days ago

https://github.com/nginx/njs

A dynamic module that allows developers to extend NGINX functionality using modern JavaScript (ES2023 via QuickJS) for HTTP and Stream protocols without recompilation. It includes the ngx_http_js_module and ngx_stream_js_module, a standalone CLI utility, and njs-types for TypeScript definitions.

Tokens
7.8K
Snippets
26
Records
41
Agent score
77%

What's inside njs

  1. Language baseline for the deprecated njs engine

    master

    The deprecated njs engine implements ECMAScript 5.1 (strict mode) with a curated set of ES6+ extensions.

    Supported features include:

    • Arrow functions, let/const, and template literals.
    • Promise with full prototype methods.
    • async / await (only inside async functions; top-level await is not supported).
    • Rest parameters: function f(...rest).
    • Optional chaining ?., nullish coalescing ??, and logical assignments ||= / &&= / ??= (since version 0.9.6).
    • ES2016 exponentiation operator **.
    • A subset of Symbol (for, keyFor).
    • ES Modules: Only default import and default export are supported. Non-default forms (e.g., import { x } from "...") will result in a Non-default import is not supported error.
    • require() (supported but deprecated; import is preferred).
  2. Select a JavaScript engine for NJS

    master

    NJS supports two interchangeable engines, which can be selected using the js_engine directive:

    • QuickJS (recommended): A modern engine compliant with ES2023. To use it, set js_engine qjs;. Note that building from source requires the QuickJS library.
    • Built-in njs engine (deprecated since 1.0.0): An original engine compliant with ES5.1 plus specific ES6+ extensions. It is currently the default for compatibility but migration to QuickJS is recommended.
    js_engine qjs;
  3. Pick an engine: QuickJS vs njs

    master

    njs provides two interchangeable JavaScript engines.

    • QuickJS (Recommended): Use for all new code. It supports ES2023, including class, generators, spread/rest, destructuring, Map/Set, BigInt, Proxy, and top-level await.
    • njs (Deprecated): Use only for maintaining existing code that cannot yet be ported. It is based on ES5.1 strict with curated ES6+ features. It lacks support for class, destructuring, and many modern ES features.

    You can select the engine per-context using the js_engine directive in nginx.conf (available in http and stream blocks, and can be overridden in server or location blocks). For the CLI, use -n njs or -n QuickJS.

    http {
        js_engine qjs;            # http-wide default
        js_import http.js;
    
        server {
            # js_engine inherits, can be overridden per server or location
        }
    }
  4. Understand the njs runtime model

    master

    To write effective njs modules, understand these core runtime behaviors:

    • Nginx-driven execution: Every execution starts at a directive-bound entry point and ends when the handler resolves. There is no long-lived top-level script.
    • Per-request isolation: Each HTTP request or stream session creates a fresh VM. Warning: State in module scope is visible to subsequent requests on the same worker and will leak data across requests. Do not use module scope for per-request data.
    • Worker isolation: Module scope is NOT shared across different nginx workers.
    • Cross-worker state: To share state across workers, use ngx.shared (a shared dictionary).
    • Event loop: Async work (like ngx.fetch() or r.subrequest()) is integrated with the nginx event loop.
    • Top-level await: Only supported in the QuickJS engine.
  5. How NGINX JavaScript (NJS) works

    master

    NGINX JavaScript (NJS) is a dynamic module that allows you to extend NGINX functionality using JavaScript syntax without recompiling NGINX. It is provided as two separate modules:

    1. ngx_http_js_module: Enables manipulation of data transmitted over HTTP.
    2. ngx_stream_js_module: Enables manipulation of data transmitted via stream protocols (TCP/UDP).

    Administrators use NJS to implement complex access control, security checks, response header manipulation, and asynchronous content handlers or filters.

  6. Understand njs-types versioning

    master

    The versioning of njs-types is aligned with the njs project:

    • Major and Minor versions: These are always aligned with the corresponding njs release (e.g., if njs is 1.2.x, njs-types will be 1.2.x).
    • Patch version: The third number may differ. If njs-types is updated between njs releases, the patch version is incremented to comply with SemVer requirements on npm.

    To find the exact commit used to build a specific package version, check the COMMITHASH file included within the published npm package. This file contains the global revision ID from the upstream Mercurial repository (https://hg.nginx.org/njs/).

  7. Test njs code standalone

    master

    You can test your JavaScript code outside of NGINX using the njs binary. This allows for quick verification of logic and engine-specific behavior.

    • Use the default engine by running the binary with a command.
    • Use the QuickJS engine by specifying -n QuickJS (if linked).
    • Load code as an ES module using the -m flag.
    ./build/njs -c 'console.log(typeof Map)'   # under default njs engine
    ./build/njs -n QuickJS script.js           # under QuickJS (if linked in)
    ./build/njs -m module.mjs                  # load as ES module
  8. How njs integration points work

    master

    JS code in njs is not self-starting; it is driven by nginx directives that bind JS functions to specific request processing phases.

    HTTP Integration (ngx_http_js_module)

    • js_content module.fn: Replaces upstream in the content phase. Context: r. Termination: r.return() or r.send() + r.finish().
    • js_access module.fn: Runs in the access phase. Context: r. Termination: r.return(status) to deny.
    • js_header_filter module.fn: Response header filter. Context: r (mutates headersOut). Termination: synchronous return.
    • js_body_filter module.fn [buffer_type=string|buffer]: Response body filter. Context: r + (data, flags). Termination: r.sendBuffer(out, flags).
    • js_set $var module.fn [nocache]: Variable evaluation. Context: r. Termination: synchronous return.
    • js_periodic module.fn interval=...: Timer-driven (no request). Context: none.

    Stream Integration (ngx_stream_js_module)

    • js_preread module.fn: Before upstream connects. Context: s. Termination: s.allow() / s.deny() / s.done().
    • js_filter module.fn: Data filter (both directions). Context: s (use s.on() to subscribe). Termination: s.done().
    • js_access module.fn: Access phase. Context: s. Termination: s.allow() / s.deny().
    • js_set $var module.fn: Variable evaluation. Context: s. Termination: synchronous return.
    • js_periodic module.fn interval=...: Timer-driven (no session). Context: none.

    Important Note on Async: js_content and js_access support fully async handlers. However, js_header_filter, js_body_filter, and js_set will reject async work if await hits the event loop.

  9. Ask questions about njs functionality

    master
    If you have questions about how a specific feature in njs works or want to know if the project can achieve a particular functionality, open an issue on GitHub. When opening an issue for a question, ensure you apply the question label so it can be correctly identified by the maintainers.
  10. Enable NJS modules in NGINX configuration

    master

    To use NJS, you must load the modules in the top-level (main) context of your nginx.conf file using the load_module directive. By default, modules are installed in /etc/nginx/modules.

    load_module modules/ngx_http_js_module.so;
    load_module modules/ngx_stream_js_module.so;
  11. Reference njs-types in TypeScript files

    master
    {
      "compilerOptions": {
        "target": "ES5",
        "module": "es2015",
        "lib": [
          "ES2015",
          "ES2016.Array.Include",
          "ES2017.Object",
          "ES2017.String"
        ],
        "outDir": "./lib",
        "downlevelIteration": true,
    
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictPropertyInitialization": true,
        "noImplicitThis": true,
        "alwaysStrict": true,
    
        "moduleResolution": "node",
    
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
      },
      "include": [
        "./src",
      ],
      "files": [
        "./node_modules/njs-types/ngx_http_js_module.d.ts",
      ],
    }
    /// <reference path="./node_modules/njs-types/ngx_http_js_module.d.ts" />
  12. Build njs with a QuickJS backend

    master

    njs can be built using QuickJS as the backend. QuickJS must be built separately as a static library (libquickjs.a) and then linked into njs.

    1. Build libquickjs.a in your QuickJS source tree using CFLAGS=-fPIC.
    2. Configure njs by pointing to the QuickJS source directory using --cc-opt for include paths and --ld-opt for library paths.
    3. Build the njs binary.
    # Build libquickjs.a in the QuickJS source tree
    ( cd <QUICKJS_SRC> && CFLAGS=-fPIC make libquickjs.a )
    
    # Configure njs to use it
    make clean
    ./configure \
        --cc-opt='-I<QUICKJS_SRC>' \
        --ld-opt='-L<QUICKJS_SRC>'
    make -j$(nproc) njs