ngs

Write functional, type-safe nginx scripts (njs) with Gleam

Package VersionHex Docs

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

ngs bridges the gap between Gleam's type-safe functional programming and Nginx's powerful JavaScript scripting capabilities (njs). It enables you to:

The compiled JavaScript conforms to the es2020 standard and requires NJS with QuickJS engine support.

Features

Installation

Add ngs to your Gleam project

gleam add ngs

Set up npm dependencies

npm install

Requirements

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:

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:

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

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:

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:

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 &#39;hello&#39; 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:

  1. Compiles Gleam code to JavaScript
  2. Bundles applications with esbuild
  3. 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

  1. 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)
}
  1. 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;
        }
    }
}
  1. 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",
),
  1. Build and test
npm run build
  1. 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.