noex

|

Build fault-tolerant, scalable systems with TypeScript

Inspired by 40+ years of Erlang/OTP

Why noex?

Node.js is fast and flexible, but building reliable distributed systems requires proven patterns. noex brings battle-tested Erlang/OTP concepts to TypeScript, so you can focus on your business logic instead of infrastructure.

Battle-tested patterns

GenServer, Supervisor, Registry - concepts that power WhatsApp, Discord, and millions of telecom systems worldwide. Now available in your TypeScript codebase.

Fault-tolerant by design

Embrace the 'let it crash' philosophy. Processes fail gracefully and supervisors automatically restart them. No more cascading failures bringing down your entire app.

Distributed from day one

The same code that runs on one machine scales across a cluster. Location-transparent messaging means you don't rewrite anything when you need to scale.

Observable out of the box

Built-in web dashboard and terminal UI show you exactly what's happening. See process states, message throughput, and errors in real-time without any configuration.

See what problems noex solves

Node.js is great, but...

Common challenges that slow down development and cause production issues

Race Conditions

Shared mutable state leads to unpredictable bugs

Error Cascades

One failure can crash your entire application

Scaling Complexity

Distributed systems are hard to get right

noex solves these problems

Core Concepts

Battle-tested patterns from Erlang/OTP, reimagined for TypeScript

GenServer

Stateful processes without race conditions. Sequential message processing guarantees consistency.

Supervisor

Automatic restart on failures. Let it crash philosophy with smart recovery strategies.

Registry

Named process lookup. Find processes by name instead of managing references.

Advanced Patterns

Higher-level abstractions for common concurrency scenarios

GenStateMachine

Explicit finite state machines with typed states and transitions. Perfect for workflows and protocols.

WorkflowsProtocolsGame logic

Agent

Simple state container when you need just state, not a full process. Functional updates with get/update.

CountersAccumulatorsSimple state

Task

Supervised async operations with concurrency control. Run fire-and-forget jobs or await results.

Batch processingParallel I/OJob queues

Application Framework

Complete lifecycle management for production-ready applications

Kubernetes-ready Docker-friendly Production tested

Lifecycle Management

Consistent startup sequence with health checks. Start, stop, and monitor your application with a unified API.

Signal Handling

Automatic SIGINT/SIGTERM handling for Docker and Kubernetes. Your processes clean up properly.

Graceful Shutdown

prepStop hooks give services time to drain. Configurable timeouts prevent hanging shutdowns.

application.ts
const MyApp = Application.create({
  async start(config) {
    const supervisor = await Supervisor.start({
      strategy: 'one_for_one',
      children: [
        { id: 'db', start: () => DatabasePool.start(config.dbUrl) },
        { id: 'api', start: () => ApiServer.start(config.port) }
      ]
    });
    return supervisor;
  },

  async prepStop(supervisor) {
    // Stop accepting new requests
  },

  async stop(supervisor) {
    // Final cleanup
  }
});

await Application.start(MyApp, {
  name: 'my-service',
  config: { port: 3000, dbUrl: 'postgres://...' }
});
// Handles SIGINT/SIGTERM automatically

Built-in Services

Production-ready utilities that work seamlessly with your processes

Cache

In-memory caching with TTL and automatic cleanup

Session storageAPI responsesComputed data

EventBus

Publish/subscribe messaging between processes

Decoupled communicationEvent sourcingReal-time updates

RateLimiter

Token bucket rate limiting for APIs and resources

API protectionResource throttlingDoS prevention

ETS

Erlang Term Storage - fast in-memory key-value store with pattern matching

Fast lookupsSession dataShared state

Observability Built-in

Monitor your processes in real-time with zero configuration

noex dashboard — Terminal
┌─ Process Tree ────────────────┐ ┌─ Stats Table ─────────────────────────────────────┐
supervisor:main │ │ ID │ Status │ Msgs │ Mem │ Up │
│ ├─ counter (running) │ │─────────────────┼─────────┼──────┼────────┼───────│
│ ├─ cache (running) │ │ counter │ running1.2k2.4 MB │ 01:23 │
│ ├─ rate-limiter (running) │ │ cache │ running │ 847 │ 12 MB │ 01:23 │
│ └─ event-bus (running) │ │ rate-limiter │ running │ 156 │ 1.1 MB │ 01:23 │
│ │ │ event-bus │ running2.1k3.2 MB │ 01:23 │
└───────────────────────────────┘ └───────────────────────────────────────────────────┘
┌─ Memory ──────────────────────┐ ┌─ Event Log ───────────────────────────────────────┐
│ │ [12:34:56] GenServer started: counter │
67% / 256MB │ │ [12:34:57] GenServer started: cache │
│ │ │ [12:34:58] Supervisor: main (4 children) │
└───────────────────────────────┘ └───────────────────────────────────────────────────┘
[q]uit [r]efresh [?]help [1-3]layout │ Processes: 4 │ Up: 00:01:23
npx @hamicek/noex dashboard

Web Dashboard

Web-based interface for remote monitoring

Terminal UI

Interactive terminal dashboard with keyboard navigation

Process Metrics

Real-time CPU, memory, and message throughput

Structured Logs

Timestamped events with severity levels

Real-time updates Zero configuration Cluster support

Distributed by Design

Scale across machines with location transparency. Your processes work the same whether on one machine or hundreds.

Local Node Remote Nodes
Auto-discovery No single point of failure Seamless failover

P2P Clustering

Nodes automatically discover each other using mDNS or seed nodes. No central coordinator required.

Global Registry

Register and find processes across all nodes by name. One API for local and remote processes.

Location Transparency

Send messages to processes regardless of their physical location. The runtime handles routing.

cluster.ts
// Register a process globally
await GlobalRegistry.register('user:session:123', pid);

// Find it from any node in the cluster
const pid = await GlobalRegistry.whereis('user:session:123');

// Send a message - works across nodes
await GenServer.call(pid, { type: 'get_data' });

Same API for local and remote processes

Try It Out

Explore real-world examples and see noex in action

counter.ts
import { GenServer, Supervisor, Registry } from class=class="hl-string">"hl-string">'@hamicek/noex';

class Counter extends GenServer {
  init() {
    return 0;
  }

  handleCall(msg: { type: class=class="hl-string">"hl-string">'get' }) {
    return this.state;
  }

  handleCast(msg: { type: class=class="hl-string">"hl-string">'increment' } | { type: class=class="hl-string">"hl-string">'decrement' }) {
    if (msg.type === class=class="hl-string">"hl-string">'increment') {
      this.state++;
    } else {
      this.state--;
    }
  }
}

class=class="hl-string">"hl-comment">// Start the counter
const counter = await Counter.start();

class=class="hl-string">"hl-comment">// Interact with it
await counter.cast({ type: class=class="hl-string">"hl-string">'increment' });
await counter.cast({ type: class=class="hl-string">"hl-string">'increment' });
const value = await counter.call({ type: class=class="hl-string">"hl-string">'get' });

console.log(class=class="hl-string">"hl-string">'Counter value:', value);
Terminal

Ready to build resilient systems?

Get started in minutes with a simple npm install

Install with npm
$ npm install @hamicek/noex

Quick Start

  1. 1
    Install the package npm install @hamicek/noex
  2. 2
    Import and use import { GenServer } from '@hamicek/noex'
  3. 3
    Run your process await Counter.start()
counter.ts
import { GenServer } from class=class="hl-string">"hl-string">'@hamicek/noex';

class Counter extends GenServer {
  init() { return 0; }

  handleCast(msg: class=class="hl-string">"hl-string">'inc') {
    this.state++;
  }
}

const counter = await Counter.start();

Support the Project

Love noex? Help us keep building amazing tools

Bitcoin
Bitcoin QR Code