NodeAkt is a TypeScript-first distributed actor framework for Node.js, Bun, and Deno. It lets you build responsive, resilient, and elastic systems with typed actor messages, running as a single process or a cluster of nodes behind the same API.
- Simpler concurrency. Actors process one message at a time; you write plain TypeScript with no locks, channel plumbing, or shared-state bugs.
- Location transparency. Send a message to a local, remote, or clustered actor with the same API; the framework handles the wire.
- Resilience by design. Supervision trees and the "let it crash" model, inspired by Erlang/OTP, keep failures contained and recoverable.
- Production batteries included. Remoting, Clustering, scheduling, passivation, without re-rolling them yourself.
- Multi-core. Spawn actors across every machine core without touching workers or threads yourself. An actor's address works the same on any core, on Node.js, Bun, and Deno alike.
An actor is a small unit of computation that owns private state and a mailbox. It never shares memory; it communicates only by sending messages, and the runtime delivers those messages to it one at a time. That single rule is what makes actors easy to reason about: inside a handler there is no concurrency, so there are no locks, no races, and no shared-state bugs. You model a system as many actors that each do one thing and talk by message, and the framework runs them safely across cores and across machines. For more depth, watch Carl Hewitt, the father of the actor model, explain it in his own words:
The whole runtime, the multi-core layer and the network protocol included, has zero dependencies: installing it brings exactly one package. Every supported runtime is exercised in CI, multi-core placement included.
One package, zero dependencies, ESM only (import, not require). Pick your runtime:
npm install @tochemey/nodeakt
pnpm add @tochemey/nodeakt
yarn add @tochemey/nodeaktbun add @tochemey/nodeaktdeno add npm:@tochemey/nodeaktimport type { Actor, Context, ReceiveContext } from "@tochemey/nodeakt";
import { ActorSystem } from "@tochemey/nodeakt";
class Greet {
constructor(readonly name: string) {}
}
// An actor implements three lifecycle hooks: preStart, receive, and postStop.
class Greeter implements Actor {
preStart(_ctx: Context): void {}
receive(ctx: ReceiveContext): void {
if (ctx.message instanceof Greet) {
console.log(`Hello, ${ctx.message.name}!`);
}
}
postStop(_ctx: Context): void {}
}
const system = new ActorSystem("hello");
await system.start();
const greeter = await system.spawn("greeter", new Greeter());
system.noSender().tell(greeter, new Greet("Ada")); // Hello, Ada!
await system.stop();Head to Getting started to build on this, including ask/request for typed replies, supervision, and more.
- Documentation: getting started, the tour, and the full reference
- Examples: small programs that match those pages
- Benchmarks: tell and ask throughput, memory density, multi-core scaling
Ask questions and follow the work in Issues, or on Slack. Feedback on the tracking issue helps shape what we work on next.
See the contribution guide for setup, conventions, and what a change needs to merge
