NGINX JavaScript (njs) is a JavaScript engine for NGINX that implements a subset of ECMAScript 5.1 (strict mode) with extensions from ES6 and later. It can be built as:
- Standalone CLI - for testing JavaScript code independently
- NGINX module - integrated into NGINX for HTTP/Stream processing
- With QuickJS - alternative JavaScript engine backend
See README.md for detailed project information.
See README.md for complete dependency list. Common requirements:
sudo apt install gcc make libedit-dev libpcre3-dev zlib1g-dev libssl-devFor standalone njs binary (no NGINX integration):
./configure
make njsThe binary will be at ./build/njs.
First, build QuickJS library:
cd <QUICKJS_SRC_DIR>
CFLAGS='-fPIC' make libquickjs.aThen build njs with QuickJS support:
cd <NJS_SRC_DIR>
make clean
./configure \
--cc-opt='-I<QUICKJS_SRC_DIR>' \
--ld-opt='-L<QUICKJS_SRC_DIR>'
make njsConfigure and build NGINX with njs as a static module.
Basic build:
cd <NGINX_SRC_DIR>
./auto/configure \
--add-module=<NJS_SRC_DIR>/nginx \
--with-stream \
--with-debug
make -j4Build with QuickJS engine:
cd <NGINX_SRC_DIR>
./auto/configure \
--add-module=<NJS_SRC_DIR>/nginx \
--with-stream \
--with-debug \
--with-cc-opt="-I<QUICKJS_SRC_DIR>" \
--with-ld-opt="-L<QUICKJS_SRC_DIR>"
make -j4Build with address sanitizer:
cd <NGINX_SRC_DIR>
./auto/configure \
--add-module=<NJS_SRC_DIR>/nginx \
--with-stream \
--with-debug \
--with-cc="clang" \
--with-cc-opt="-O0 -fsanitize=address" \
--with-ld-opt="-fsanitize=address"
make -j4Common configure options:
--add-module=<path>- Build njs as static module--with-stream- Enable stream module (required for ngx_stream_js_module)--with-debug- Enable debug logging--with-cc="<compiler>"- Specify compiler (gcc, clang, etc.)--with-cc-opt="<flags>"- Additional C compiler flags--with-ld-opt="<flags>"- Additional linker flags
./configure --help # Show all options
./configure --debug=YES # Enable runtime checks
./configure --debug-memory=YES # Enable memory allocation debug
./configure --debug-opcode=YES # Enable function tracing (see below)
./configure --debug-generator=YES # Enable generator debug
./configure --debug=YES --debug-memory=YES --address-sanitizer=YES # Full debug build for memory issuesmake unit_test # Run njs-specific tests
make test262 # Run ECMAScript test262 compliance testsTests are located in <NGINX_TESTS_DIR> and use Perl's prove harness.
Basic test run:
cd <NJS_SRC_DIR>
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
prove -I <NGINX_TESTS_LIB_DIR> nginx/t/Run specific test with verbose output:
TEST_NGINX_VERBOSE=1 TEST_NGINX_LEAVE=1 \
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js.t -vCommon test environment variables:
TEST_NGINX_BINARY=<path>- Path to nginx binaryTEST_NGINX_VERBOSE=1- Enable verbose test outputTEST_NGINX_LEAVE=1- Keep test artifacts in/tmp/nginx-test-*TEST_NGINX_CATLOG=1- Print error.log after tests completeTEST_NGINX_GLOBALS=<config>- Add global nginx configTEST_NGINX_GLOBALS_HTTP=<config>- Add http-level config (e.g.,js_engine qjs;)TEST_NGINX_GLOBALS_STREAM=<config>- Add stream-level config
For complete variable documentation, see <NGINX_TESTS_LIB_DIR>/Test/Nginx.pm.
Test with QuickJS engine:
TEST_NGINX_GLOBALS_HTTP='js_engine qjs;' \
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js_headers.t -v- Check test output - Add
TEST_NGINX_VERBOSE=1and-vflag to prove - Check error logs - Look at
/tmp/nginx-test-<random>/error.log- Important: Remove old test directories first to see latest logs
- Use
TEST_NGINX_LEAVE=1to prevent cleanup - Use
TEST_NGINX_CATLOG=1to print logs automatically
./build/njs -c "console.log('test')" # Execute code
./build/njs -d # Interactive shell with disassembly
./build/njs -d script.js # Show bytecode for script
./build/njs -h # Show all optionsTo debug specific JavaScript code paths:
-
Add
debuggerstatement in your JS code:function test() { if (someCondition) { debugger; // Trigger breakpoint } }
-
Set GDB breakpoint:
gdb ./build/njs (gdb) break njs_vmcode_debugger (gdb) run test.js -
Inspect VM state in GDB:
# First, dump bytecode to find the index you want to inspect ./build/njs -d test.js # In GDB, inspect value at specific index (gdb) p *njs_scope_value_get(vm, 0x0123) # Use hex literal from bytecode
With --debug-opcode=YES, njs prints execution trace:
./build/njs -o script.js
# Example output:
ENTER main
0 | 00000 FUNCTION COPY 5250000212D8 0124
0 | 00000 FUNCTION FRAME 0124 0
0 | 00000 FUNCTION CALL 0223
CALL NATIVE unmapped ??() ??:0 [0xAE5EDD13E7A0]
ENTER f
0 | 00000 AWAIT 0133
EXIT AWAIT fnjs uses a register-based virtual machine:
- Each instruction has operands (immediate values or indexes)
- Indexes encode:
index | level_type (4-bit) | var_type (4-bit)
NJS_LEVEL_LOCAL = 0 // Local variable in current function
NJS_LEVEL_CLOSURE = 1 // Closure variable from parent function
NJS_LEVEL_GLOBAL = 2 // Global variable
NJS_LEVEL_STATIC = 3 // Static/absolute scope variable
NJS_VARIABLE_CONST = 0
NJS_VARIABLE_LET = 1
NJS_VARIABLE_CATCH = 2
NJS_VARIABLE_VAR = 3
NJS_VARIABLE_FUNCTION = 4
Variable values are stored in: vm->levels[NJS_LEVEL_*][index]
./build/njs -d
>> var a = 42; function f(v) { return v + 1}
shell:main
1 | 00000 MOVE 0123 0133
1 | 00024 STOP 0033
shell:f
1 | 00000 ADD 0203 0103 0233
1 | 00032 RETURN 0203Breakdown:
MOVE 0123 0133- Move value from index 0x0133 to 0x0123ADD 0203 0103 0233- Add values at 0x0103 and 0x0233, store in 0x0203- Indexes are in hex, encode level and variable type
# 1. Make changes to njs source
cd <NJS_SRC_DIR>
# ... edit files ...
# 2. Rebuild nginx (njs is built as static module)
cd <NGINX_SRC_DIR>
make -j4
# 3. Run tests
cd <NJS_SRC_DIR>
rm -fr /tmp/nginx-test* # Clean old test artifacts
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js*.t# Quick test
./build/njs -c "var a = [1,2,3]; console.log(a.map(x => x*2))"
# Interactive testing
./build/njs
>> var obj = {a: 1, b: 2}
>> Object.keys(obj)
['a','b']# Build nginx with njs engine
cd <NGINX_SRC_DIR>
./auto/configure \
--add-module=<NJS_SRC_DIR>/nginx \
--with-stream \
--with-debug
make -j4
# Test with njs engine
cd <NJS_SRC_DIR>
TEST_NGINX_GLOBALS_HTTP='js_engine njs;' \
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js_headers.t -v
# Build nginx with QuickJS engine
cd <NGINX_SRC_DIR>
./auto/configure \
--add-module=<NJS_SRC_DIR>/nginx \
--with-stream \
--with-debug \
--with-cc-opt="-I<QUICKJS_SRC_DIR>" \
--with-ld-opt="-L<QUICKJS_SRC_DIR>"
make -j4
# Test with QuickJS engine
cd <NJS_SRC_DIR>
TEST_NGINX_GLOBALS_HTTP='js_engine qjs;' \
TEST_NGINX_BINARY=<NGINX_BINARY_PATH> \
prove -I <NGINX_TESTS_LIB_DIR> nginx/t/js_headers.t -v