How to Use Nginx NJS (JavaScript Module): The Complete Edge Computing Guide

How to Use Nginx NJS (JavaScript Module): The Complete Edge Computing Guide

Nginx NJS is a lightweight JavaScript engine built directly into Nginx, allowing you to write custom business logic at the edge of your network. Instead of relying on external microservices or complex Nginx configuration syntax, you can use JavaScript to handle requests dynamically.

NJS runs in the same process as Nginx, with microsecond latency and zero network overhead.

Why Use Nginx NJS?

NJS lets you:
• Process requests with custom business logic without external services
• Perform authentication and authorization at the edge
• Transform HTTP headers and content on-the-fly
• Implement A/B testing, canary deployments, and traffic routing
• Validate requests before they reach your backend
• Generate dynamic responses based on request attributes

The key advantage is performance—your logic runs in-process with Nginx, eliminating network round-trips that external services require.

When NOT to Use NJS

NJS isn’t ideal for:
• Heavy computation (CPU-intensive algorithms)
• Long-running operations (blocking would affect other requests)
• Complex business logic (better suited to your application)
• Accessing external services (no built-in HTTP client)

Getting Started with Nginx NJS

First, verify your Nginx build includes the NJS module:

nginx -T 2>&1 | grep njs

If not found, you’ll need to recompile Nginx with –with-http_js_module.

Basic NJS Example

Create a simple request handler in /etc/nginx/njs/hello.js:

function hello(r) {
r.return(200, “Hello from NJS!”);
}

export default {http: {hello}};

Reference it in your Nginx config:

js_import /etc/nginx/njs/hello.js;

server {
location /api/hello {
js_content hello;
}
}

Reload Nginx: nginx -s reload

Test: curl http://localhost/api/hello
Output: Hello from NJS!

Working with Variables and Headers

Access and modify request data:

function process_request(r) {
// Read query parameters
var user_id = r.args.user_id;

// Read headers
var auth_token = r.headersIn[‘Authorization’];

// Set response headers
r.headersOut[‘X-Custom-Header’] = ‘value’;
r.headersOut[‘X-Timestamp’] = Date.now();

if (!auth_token) {
r.return(401, “Missing Authorization”);
return;
}

r.return(200, “User ” + user_id + ” authenticated”);
}

export default {http: {process_request}};

Common Use Cases

1. Authentication and Authorization

Implement token validation at the edge:

function authenticate(r) {
var token = r.headersIn[‘Authorization’];

if (!token || !token.startsWith(‘Bearer ‘)) {
r.return(401, JSON.stringify({error: “Invalid token”}));
return;
}

var token_value = token.substring(7);

// Validate token format (basic example)
if (token_value.length < 32) { r.return(401, JSON.stringify({error: "Token too short"})); return; } // Token is valid, continue r.internalRedirect("@backend"); } 2. Header Manipulation Add or modify headers: function add_security_headers(r) { r.headersOut['X-Content-Type-Options'] = 'nosniff'; r.headersOut['X-Frame-Options'] = 'DENY'; r.headersOut['Strict-Transport-Security'] = 'max-age=31536000'; r.headersOut['X-Request-ID'] = Date.now().toString(); } 3. Request Routing (Canary Deployments) Route small percentages of traffic to new versions: function canary_route(r) { var request_hash = r.args.user_id.charCodeAt(0); var canary_percentage = 10; // 10% to canary if ((request_hash % 100) < canary_percentage) { r.internalRedirect("@canary_backend"); } else { r.internalRedirect("@stable_backend"); } } 4. Content Transformation Modify responses dynamically: function transform_response(r) { if (r.status == 200 && r.headersOut['Content-Type'].includes('json')) { var body = r.responseBuffer.toString(); var data = JSON.parse(body); data.processed_at = new Date().toISOString(); r.return(200, JSON.stringify(data)); } } Advanced Patterns Caching Values Use Nginx variables to cache computed values: function cache_value(r) { if (r.variables.cached_user === undefined) { r.variables.cached_user = r.args.user_id; } r.return(200, "User: " + r.variables.cached_user); } Working with JSON Parse and generate JSON responses: function json_api(r) { var response = { path: r.uri, method: r.method, query_string: r.args, timestamp: Date.now(), client_ip: r.remoteAddress }; r.headersOut['Content-Type'] = 'application/json'; r.return(200, JSON.stringify(response)); } Conditional Logic Route based on request attributes: function smart_router(r) { var client_type = r.headersIn['X-Client-Type'] || 'web'; switch(client_type) { case 'mobile': r.internalRedirect("@mobile_backend"); break; case 'api': r.internalRedirect("@api_backend"); break; default: r.internalRedirect("@web_backend"); } } Debugging NJS Enable debug logging: error_log /var/log/nginx/error.log debug; js_set test_function; Use console.log-like debugging (capture in error log): function debug_request(r) { ngx.log(ngx.ERR, "Request URI: " + r.uri); ngx.log(ngx.ERR, "Headers: " + JSON.stringify(r.headersIn)); } Performance Considerations NJS scripts run in Nginx worker processes: • Keep scripts lightweight (< 1ms execution) • Avoid heavy computation • Don't make external HTTP calls • Use Nginx variables for caching • Profile your code for bottlenecks Monitoring NJS Performance Track NJS-related metrics: # Check error log for NJS errors tail -f /var/log/nginx/error.log | grep "js:" # Monitor request latency tail -f /var/log/nginx/access.log | awk '{print }' | sort -n | tail -10 Best Practices 1. Test in staging first 2. Keep NJS code simple and focused 3. Use meaningful variable names 4. Add error handling 5. Monitor production performance 6. Document custom logic 7. Version control your NJS files Common Gotchas • NJS doesn't support all JavaScript (no async/await) • No access to the DOM or browser APIs • Limited standard library • Single-threaded—long operations block other requests • Memory-limited—watch for leaks Limitations Current NJS limitations: • No external module imports • No filesystem access • No process spawning • No direct database connections Real-World Examples Rate Limiting function rate_limit(r) { var client_ip = r.remoteAddress; var key = "rate:" + client_ip; // Simple in-memory counter (Nginx variable) var count = parseInt(r.variables.request_count || 0); if (count > 100) {
r.return(429, “Rate limit exceeded”);
return;
}

r.variables.request_count = count + 1;
r.internalRedirect(“@backend”);
}

Conclusion

Nginx NJS brings powerful edge computing capabilities directly to your web server. By processing logic at the edge with NJS, you reduce latency, eliminate backend load, and improve overall application performance.

For complex logic, keep it in your application. For edge decisions—authentication, routing, header manipulation—NJS is the perfect tool.