ngs

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

Package Version Hex 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

ExampleDescription
http_helloBasic “Hello World” handler
http_decode_uriURI decoding with query parameters
http_complex_redirectsComplex redirect logic
http_join_subrequestsJoin multiple subrequests
http_subrequests_chainingChain subrequests sequentially

Authorization

ExampleDescription
http_authorization_jwtJWT verification
http_authorization_gen_hs_jwtGenerate HS256 JWT tokens
http_authorization_auth_requestAuth request pattern
http_authorization_request_bodyValidate request body
http_authorization_secure_link_hashSecure link with hash

Certificate Handling

ExampleDescription
http_certs_dynamicDynamic certificate loading
http_certs_fetch_httpsFetch certificates via HTTPS
http_certs_subject_alternativeX.509 subject alternative name parsing

Response Modification

ExampleDescription
http_response_to_lower_caseConvert response to lowercase
http_response_modify_set_cookieModify Set-Cookie headers

Async Operations

ExampleDescription
http_async_var_auth_requestAsync auth request
http_async_var_js_header_filterAsync header filter

Utilities

ExampleDescription
http_logging_num_requestsRequest counting
http_rate_limit_simpleSimple rate limiting
http_api_set_keyvalKey-value API
misc_file_ioFile I/O operations
misc_aes_gcmAES-GCM encryption

Stream Handlers

ExampleDescription
stream_auth_requestStream authentication
stream_detect_httpHTTP detection in streams
stream_inject_headerInject 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 '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:

  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.

Search Document