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.