ngs
Write functional, type-safe nginx scripts (njs) with Gleam
ngs is a Gleam library that provides type-safe bindings to Nginx JavaScript (njs) runtime, allowing you to write functional, type-safe nginx handlers in Gleam instead of plain JavaScript.
Table of Contents
- Overview
- Features
- Installation
- Requirements
- Quick Start
- API Documentation
- Examples
- Testing
- Development
- Project Structure
Overview
ngs bridges the gap between Gleam’s type-safe functional programming and Nginx’s powerful JavaScript scripting capabilities (njs). It enables you to:
- Write nginx handlers using Gleam’s expressive type system
- Leverage njs’s extensive API with compile-time safety
- Offload HTTP server logic to nginx for better performance
- Use Gleam’s pattern matching and functional composition for request handling
The compiled JavaScript conforms to the es2020 standard and requires NJS with QuickJS engine support.
Features
- Type-safe njs bindings - Full coverage of njs HTTP, Stream, and utility APIs
- Functional programming - Use Gleam’s pattern matching and functional composition
- Build system integration - Automated compilation and bundling with esbuild
- Watch mode - Live reloading during development
- Comprehensive examples - 25+ real-world nginx handler implementations (to be finished by AI)
- Integration testing - Bun-based tests with real nginx instances
- Mock infrastructure - Built-in mock servers for Redis, Postgres, Consul, OIDC, ACME, and HTTP
Installation
Add ngs to your Gleam project
gleam add ngs
Set up npm dependencies
npm install
Requirements
- Gleam >= 1.14.0
- NJS with QuickJS engine (ES2020 support)
- Node.js (for esbuild bundling)
- Bun (for running tests)
Quick Start
1. Create a simple nginx handler
Create a Gleam module for your handler:
// src/my_handler.gleam
import njs/http.{type HTTPRequest}
import njs/ngx.{type JsObject}
fn hello(r: HTTPRequest) -> Nil {
r
|> http.return_text(200, "Hello from Gleam!\n")
}
pub fn exports() -> JsObject {
ngx.object()
|> ngx.merge("hello", hello)
}
2. Build the application
npm run build
This compiles your Gleam code to JavaScript and bundles it with esbuild.
3. Configure nginx
Create an nginx configuration that loads your handler:
daemon off;
error_log logs/error.log debug;
pid logs/nginx.pid;
events {
worker_connections 64;
}
http {
js_engine qjs;
js_path "njs/";
js_import main from app.js;
server {
listen 8888;
location / {
js_content main.hello;
}
}
}
4. Run nginx
nginx -c path/to/nginx.conf -p path/to/runtime
Your handler is now running at http://localhost:8888/!
API Documentation
ngs provides comprehensive bindings to njs APIs organized by module:
Core HTTP Module (njs/http)
Type-safe bindings for HTTP request handling:
import njs/http
// Request information
http.headers_in(request)
http.method(request)
http.uri(request)
http.remote_address(request)
// Response handling
http.return_text(request, 200, "Hello")
http.return_code(request, 200)
http.set_status(request, 201)
http.set_headers_out(request, "Content-Type", "application/json")
// Async operations
http.subrequest(request, "/api", options)
Key types:
HTTPRequest- Incoming HTTP requestHTTPResponse- HTTP response (alias of HTTPRequest)HTTPHandler- Handler function type
Stream Module (njs/stream)
Handle TCP/UDP streams in nginx:
import njs/stream
// Session control
stream.allow(session)
stream.decline(session)
stream.deny(session)
// Event handling
stream.on(session, "data", callback)
// Logging
stream.log(session, "Connection established")
Crypto Module (njs/crypto)
Modern cryptographic operations:
import njs/crypto
// Hashing
let hash = crypto.create_hash("sha256")
|> crypto.hash_update("data")
|> crypto.hash_digest(Hex)
// HMAC
let hmac = crypto.create_hmac("sha256", "secret")
|> crypto.hmac_update("data")
|> crypto.hmac_digest(Base64)
// AES encryption
crypto.encrypt(AesGcm(...), key, plaintext)
crypto.decrypt(AesGcm(...), key, ciphertext)
Supports:
- SHA-256, SHA-384, SHA-512
- HMAC with various algorithms
- AES-CTR, AES-CBC, AES-GCM encryption
- RSA-OAEP
- Key generation, import/export
- Key derivation
Buffer Module (njs/buffer)
Efficient binary data handling:
import njs/buffer
// Create buffers
let buf = buffer.alloc(1024)
// Read/write operations
buffer.write_int32_be(buf, 42, 0)
let value = buffer.read_uint32_be(buf, 0)
// String encoding/decoding
buffer.from_string("hello", Utf8)
buffer.to_string(buf, Utf8, 0, 5)
File System Module (njs/fs)
Synchronous and asynchronous file operations:
import njs/fs
// Sync operations
fs.stat_sync("file.txt")
fs.read_file_sync("data.json", Utf8)
fs.write_file_sync("output.txt", "data", Utf8)
// Async operations
use handle <- promise.await(fs.promises_open("file.txt", "r", 0o644))
use result <- promise.await(fs.file_handle_read(handle, buf, 0, 1024, None))
Utility Modules
njs/ngx- Core nginx utilities, logging, shared dictionarynjs/headers- HTTP header manipulationnjs/console- Console loggingnjs/timers- Timer operationsnjs/querystring- URL query parsing/formattingnjs/xml- XML parsingnjs/zlib- Compressionnjs/process- Process informationnjs/text_encoder- Text encodingnjs/text_decoder- Text decodingnjs/shared_dict- Shared memory dictionaries
See hexdocs.pm/ngs for complete API documentation.
Examples
The project includes 25+ example nginx handlers organized by category:
HTTP Handlers
| Example | Description |
|---|---|
http_hello | Basic “Hello World” handler |
http_decode_uri | URI decoding with query parameters |
http_complex_redirects | Complex redirect logic |
http_join_subrequests | Join multiple subrequests |
http_subrequests_chaining | Chain subrequests sequentially |
Authorization
| Example | Description |
|---|---|
http_authorization_jwt | JWT verification |
http_authorization_gen_hs_jwt | Generate HS256 JWT tokens |
http_authorization_auth_request | Auth request pattern |
http_authorization_request_body | Validate request body |
http_authorization_secure_link_hash | Secure link with hash |
Certificate Handling
| Example | Description |
|---|---|
http_certs_dynamic | Dynamic certificate loading |
http_certs_fetch_https | Fetch certificates via HTTPS |
http_certs_subject_alternative | X.509 subject alternative name parsing |
Response Modification
| Example | Description |
|---|---|
http_response_to_lower_case | Convert response to lowercase |
http_response_modify_set_cookie | Modify Set-Cookie headers |
Async Operations
| Example | Description |
|---|---|
http_async_var_auth_request | Async auth request |
http_async_var_js_header_filter | Async header filter |
Utilities
| Example | Description |
|---|---|
http_logging_num_requests | Request counting |
http_rate_limit_simple | Simple rate limiting |
http_api_set_keyval | Key-value API |
misc_file_io | File I/O operations |
misc_aes_gcm | AES-GCM encryption |
Stream Handlers
| Example | Description |
|---|---|
stream_auth_request | Stream authentication |
stream_detect_http | HTTP detection in streams |
stream_inject_header | Inject headers into streams |
Each example includes:
- Gleam handler implementation in
src/app/<name>/ - Nginx configuration in
src/app/<name>/nginx.conf - Integration tests in
tests/<name>/
Testing
ngs uses Bun for integration testing with real nginx instances.
Run all tests
bun test
Run specific test suite
bun test tests/hello/do.test.js
Test infrastructure
The test harness (tests/harness.js) provides:
- nginx process management - Start/stop nginx with custom configs
- Isolated runtime directories - Clean runtime environment for each test
- Mock servers - Pre-configured mock servers for external dependencies:
- Redis (port 16379)
- PostgreSQL (port 15432)
- Consul (port 18500)
- OIDC (port 19000)
- ACME (port 14000)
- HTTP backends (ports 19001-19003)
Test structure
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
import { startNginx, stopNginx, cleanupRuntime, TEST_URL } from "../harness.js";
const MODULE = "http_hello";
describe("http hello", () => {
beforeAll(async () => {
await startNginx(`dist/${MODULE}/nginx.conf`, MODULE);
});
afterAll(async () => {
await stopNginx();
cleanupRuntime(MODULE);
});
test("outputs 'hello' text", async () => {
const res = await fetch(`${TEST_URL}/hello`);
expect(res.status).toBe(200);
const body = await res.text();
expect(body).toBe("Hello World!\n");
});
});
Development
Build system
ngs uses a custom build system (src/ngs.gleam) that:
- Compiles Gleam code to JavaScript
- Bundles applications with esbuild
- Copies nginx configurations to dist directory
Available npm scripts
# Build all applications
npm run build
# Watch mode with live reloading
npm run watch
# Clean dist directory (keeps Gleam cache)
npm run clean
# Full clean including Gleam build cache
npm run purge
Adding a new example
- Create the handler module
mkdir -p src/app/my_example
// src/app/my_example/my_example.gleam
import njs/http.{type HTTPRequest}
import njs/ngx.{type JsObject}
fn handler(r: HTTPRequest) -> Nil {
r |> http.return_text(200, "OK")
}
pub fn exports() -> JsObject {
ngx.object() |> ngx.merge("handler", handler)
}
- Create nginx configuration
# src/app/my_example/nginx.conf
daemon off;
error_log logs/error.log debug;
pid logs/nginx.pid;
events {
worker_connections 64;
}
http {
js_engine qjs;
js_path "njs/";
js_import main from app.js;
server {
listen 8888;
location / {
js_content main.handler;
}
}
}
- Register in build system
Add your app to the apps() list in src/ngs.gleam:
App(
"my_example",
"./build/dev/javascript/ngs/app/my_example/my_example.mjs",
),
- Build and test
npm run build
- Create tests (optional)
mkdir -p tests/my_example
// tests/my_example/do.test.js
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
import { startNginx, stopNginx, cleanupRuntime, TEST_URL } from "../harness.js";
const MODULE = "my_example";
describe("my example", () => {
beforeAll(async () => {
await startNginx(`dist/${MODULE}/nginx.conf`, MODULE);
});
afterAll(async () => {
await stopNginx();
cleanupRuntime(MODULE);
});
test("works correctly", async () => {
const res = await fetch(`${TEST_URL}/`);
expect(res.status).toBe(200);
});
});
Project Structure
ngs/
├── src/
│ ├── njs/ # njs API bindings (19 modules)
│ │ ├── http.gleam # HTTP request handling
│ │ ├── stream.gleam # TCP/UDP stream handling
│ │ ├── crypto.gleam # Cryptographic operations
│ │ ├── buffer.gleam # Binary data manipulation
│ │ ├── fs.gleam # File system operations
│ │ ├── ngx.gleam # Core nginx utilities
│ │ ├── headers.gleam # HTTP headers
│ │ ├── console.gleam # Console logging
│ │ ├── timers.gleam # Timer operations
│ │ ├── querystring.gleam # URL query parsing
│ │ ├── xml.gleam # XML parsing
│ │ ├── zlib.gleam # Compression
│ │ ├── process.gleam # Process information
│ │ ├── text_encoder.gleam # Text encoding
│ │ ├── text_decoder.gleam # Text decoding
│ │ └── shared_dict.gleam # Shared memory
│ ├── app/ # Example handlers (25+)
│ │ ├── http_hello/
│ │ │ ├── http_hello.gleam
│ │ │ └── nginx.conf
│ │ ├── http_authorization_jwt/
│ │ ├── http_rate_limit_simple/
│ │ └── ...
│ └── ngs.gleam # Build system
├── tests/
│ ├── harness.js # Test infrastructure
│ ├── preload.js # Test preloader (builds apps)
│ ├── mocks/ # Mock servers
│ │ ├── redis.js
│ │ ├── postgres.js
│ │ ├── consul.js
│ │ ├── oidc.js
│ │ ├── acme.js
│ │ └── http.js
│ ├── hello/
│ │ └── do.test.js
│ └── ...
├── dist/ # Build output (generated)
├── build/ # Gleam build artifacts (generated)
├── gleam.toml # Gleam project configuration
├── package.json # npm scripts and dependencies
└── README.md # This file
Further Documentation
Complete API documentation is available at hexdocs.pm/ngs.
For njs documentation, see nginx.org/en/docs/njs/.
License
Apache-2.0 - see LICENSE for details.