Battle-tested patterns
GenServer, Supervisor, Registry - concepts that power WhatsApp, Discord, and millions of telecom systems worldwide. Now available in your TypeScript codebase.
|
Build fault-tolerant, scalable systems with TypeScript
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.
GenServer, Supervisor, Registry - concepts that power WhatsApp, Discord, and millions of telecom systems worldwide. Now available in your TypeScript codebase.
Embrace the 'let it crash' philosophy. Processes fail gracefully and supervisors automatically restart them. No more cascading failures bringing down your entire app.
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.
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
Common challenges that slow down development and cause production issues
Shared mutable state leads to unpredictable bugs
One failure can crash your entire application
Distributed systems are hard to get right
noex solves these problems
Battle-tested patterns from Erlang/OTP, reimagined for TypeScript
Stateful processes without race conditions. Sequential message processing guarantees consistency.
Automatic restart on failures. Let it crash philosophy with smart recovery strategies.
Named process lookup. Find processes by name instead of managing references.
Higher-level abstractions for common concurrency scenarios
Explicit finite state machines with typed states and transitions. Perfect for workflows and protocols.
Simple state container when you need just state, not a full process. Functional updates with get/update.
Supervised async operations with concurrency control. Run fire-and-forget jobs or await results.
Complete lifecycle management for production-ready applications
Consistent startup sequence with health checks. Start, stop, and monitor your application with a unified API.
Automatic SIGINT/SIGTERM handling for Docker and Kubernetes. Your processes clean up properly.
prepStop hooks give services time to drain. Configurable timeouts prevent hanging shutdowns.
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 automaticallyProduction-ready utilities that work seamlessly with your processes
In-memory caching with TTL and automatic cleanup
Publish/subscribe messaging between processes
Token bucket rate limiting for APIs and resources
Erlang Term Storage - fast in-memory key-value store with pattern matching
Monitor your processes in real-time with zero configuration
┌─ Process Tree ────────────────┐ ┌─ Stats Table ─────────────────────────────────────┐│ ▼ supervisor:main │ │ ID │ Status │ Msgs │ Mem │ Up ││ ├─ ● counter (running) │ │─────────────────┼─────────┼──────┼────────┼───────││ ├─ ● cache (running) │ │ counter │ running │ 1.2k │ 2.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 │ running │ 2.1k │ 3.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:23npx @hamicek/noex dashboardWeb-based interface for remote monitoring
Interactive terminal dashboard with keyboard navigation
Real-time CPU, memory, and message throughput
Timestamped events with severity levels
Scale across machines with location transparency. Your processes work the same whether on one machine or hundreds.
Nodes automatically discover each other using mDNS or seed nodes. No central coordinator required.
Register and find processes across all nodes by name. One API for local and remote processes.
Send messages to processes regardless of their physical location. The runtime handles routing.
// 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
Explore real-world examples and see noex in action
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); Get started in minutes with a simple npm install
$ npm install @hamicek/noex npm install @hamicek/noeximport { GenServer } from '@hamicek/noex'await Counter.start()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(); Love noex? Help us keep building amazing tools