Alexandr Chibilyaev explains why AACFlow chose Bun over Node.js โ native TypeScript, faster builds, lower memory, and the engineering philosophy behind the runtime that handles 10M+ agent executions every month.
Every platform makes a runtime decision early in its life. Most pick Node.js โ it's safe, it's established, it's what everyone uses. We picked Bun. And 10 million agent executions per month later, it was one of the best engineering decisions we've made.
This is the story of why we chose Bun, where it shines, where we still use Node.js, and what we learned along the way. Not a benchmark comparison. Not hype. Just the real engineering experience of running a production AI platform on a runtime that most people still consider "the new thing."
When you're building an AI agent platform, you run a lot of TypeScript. Your API server is TypeScript. Your agent execution engine is TypeScript. Your database migrations are TypeScript. Your build scripts are TypeScript. Your CI pipeline is TypeScript. Your internal tools are TypeScript.
In a Node.js world, every one of those TypeScript files requires a compilation step. You write server.ts. You compile it to server.js with tsc or tsup or esbuild or swc. You run the JavaScript output. Your scripts need or as a loader. Your tests need or as a transformer. Every boundary between "writing code" and "running code" is a build step. Every build step is slow. Every slow build step is friction.
Bun eliminates this. Bun runs TypeScript natively. You write server.ts. Bun executes server.ts. No build step. No tsconfig.json path mapping hacks. No ts-node startup penalty. No "why is my script failing? Oh, the compiled JS is out of date." You edit, you run. That's it.
This is the feature that made the decision. Not raw speed. Not package manager performance. Native TypeScript execution.
In Node.js, running TypeScript requires a loader. ts-node is the most common. It's also slow. On a cold start, ts-node type-checks your file before executing. That's 500ms to 2 seconds of startup time โ for a script that might take 50ms to run. tsx is faster because it uses esbuild for transpilation, but you're still paying a transpile cost on every execution. And tsx doesn't type-check, so you trade startup speed for catching type errors before they crash your script at runtime.
In Bun, TypeScript is first-class. The runtime parses .ts files directly, strips the type annotations, and executes. No external loader. No transpilation. No type-checking at startup (you type-check in your editor and CI โ where it belongs). The result:
1
# Node.js with tsx
2
$ time tsx scripts/seed-database.ts
3
real 0m1.847s # cold start with esbuild transpilation
4
5
# Bun
6
$ time bun scripts/seed-database.ts
7
real 0m0.142s # starts and runs immediately
That's a 13x difference on a simple script. For a script you run once, who cares. For a script you run 50 times during development โ or for an agent execution engine that spins up new contexts for every workflow run โ it changes how you work.
The real productivity win isn't the benchmark. It's the absence of "works on my machine" problems where the compiled JavaScript doesn't match the TypeScript source. It's the elimination of dist/ directories that get out of sync. It's the removal of build steps from development workflows, Docker builds, and CI pipelines. Every place you remove a build step, you remove a class of bugs.
npm install in a monorepo. We've all stared at it. We've all questioned our life choices while waiting for it. In the AACFlow monorepo, npm install took 45-60 seconds on a warm cache. bun install takes 3-5 seconds.
This isn't just about saving time. Fast installs change how you work. When install takes 3 seconds, you switch branches liberally. You don't avoid it. You don't build elaborate workarounds to avoid running install. You just switch branches and run install โ because it's faster to do it than to remember what you changed.
The bun.lock file is a binary lockfile. It's not human-readable like package-lock.json or yarn.lock. This was controversial. We initially disliked it. But in practice, you never edit a lockfile manually. You let the tool generate it. git diff on a binary file is useless, but you don't diff lockfiles โ you diff package.json and regenerate. The binary format is smaller, faster to parse, and eliminates a class of merge conflicts that plague text-based lockfiles in large teams.
Bun is fast. Everyone knows that. What matters for AACFlow specifically is where the speed helps.
Server startup. Our API server starts in ~200ms with Bun versus ~2 seconds with Node.js. For local development, this means bun --hot (hot reload) restarts fast enough that you don't lose your mental context. For production, it means faster recovery from restarts during deploys. In a Docker/Coolify self-hosting environment, faster startup means faster rollouts, faster rollbacks, and less downtime.
Script execution. We run a lot of scripts. Database migrations. Seed data generation. Connector sync initialization. Embedding generation pipelines. Analytics aggregation. Each of these is a TypeScript file that imports from our monorepo packages. In Node.js, every script pays the TypeScript compilation tax. In Bun, they start immediately. A seed script that takes 8 seconds in Node.js takes 1.2 seconds in Bun. When you're iterating on data models and re-running seeds 20 times in a session, that's the difference between staying in flow and getting distracted.
Memory footprint. Bun uses less memory than Node.js for equivalent workloads. Our API server idles at ~80 MB with Bun versus ~180 MB with Node.js. Under load during agent execution, the gap narrows but Bun stays consistently lower. For self-hosted deployments running on modest hardware (the Coolify user with a $20/month VPS), every megabyte matters. Lower memory means you can run AACFlow alongside your other services without upgrading your server.
WebSocket throughput. Real-time collaboration in AACFlow runs on Socket.IO with Redis adapter for multi-node scaling. The Socket.IO server itself benefits from Bun's faster event loop. We didn't benchmark this scientifically, but anecdotally: we've never had a Socket.IO performance issue that required us to investigate the runtime. The bottleneck is always Redis pub/sub or network latency, never the server process itself.
The API server. AACFlow's main application server โ Next.js API routes, middleware, authentication, webhook handlers โ runs on Bun. The Next.js development server still uses Node.js (Next.js has some internal dependencies on Node.js APIs that Bun doesn't fully polyfill), but the production build output runs on Bun.
The agent execution engine. This is the most performance-sensitive part of the platform. When a trigger fires and an agent workflow starts executing, the engine compiles the visual DAG into an execution plan, resolves dependencies between blocks, dispatches parallel branches, and streams results back via WebSocket. Every millisecond of overhead in the execution engine is multiplied by 10 million executions per month. Bun's faster startup and lower per-context overhead directly improves execution throughput.
Scripts and tooling. Database migrations, seed scripts, data export/import utilities, connector testing harnesses, CI pipeline scripts โ all TypeScript files run with Bun. No ts-node. No tsx. No esbuild in the middle. Just bun run scripts/migrate.ts.
Package management.bun install everywhere โ development, CI, Docker builds. The speed difference is dramatic enough that it changes the economics of what you're willing to do in CI.
Testing. We run Vitest with Bun. Vitest has first-class Bun support. Test execution is meaningfully faster than Node.js, especially for integration tests that spin up database connections. Faster tests mean developers run them more often, which means bugs are caught earlier.
Docker builds. Our Dockerfile uses Bun as the runtime. The bun install step in the Docker build is 10x faster than npm install. The resulting image is smaller because Bun doesn't carry the Node.js runtime weight. For self-hosted deployments where users are pulling Docker images on limited bandwidth, image size matters.
Bun isn't perfect. There are places where Node.js is still the right choice โ for now.
Next.js development server. Next.js under next dev uses Webpack or Turbopack with Node.js-specific internals. Running it on Bun produces subtle issues โ hot module replacement doesn't always work, some middleware patterns break, certain node: imports aren't polyfilled. We run next dev on Node.js. The production build (next build + next start) runs on Bun without issues. This is the main reason we haven't gone 100% Bun.
Some Next.js edge cases. Next.js middleware with specific node:crypto patterns. Image optimization (next/image) with Sharp. Incremental Static Regeneration (ISR) โ though we use it sparingly. These are edge cases that Next.js will eventually resolve as Bun's Node.js compatibility improves. For now, they're Node.js-only.
Native modules. A small number of npm packages ship native C++ addons compiled for Node.js. Bun supports most of them through its Node-API compatibility layer, but not all. We've encountered two packages that don't work on Bun. Both were replaceable with pure-JS alternatives. If you're deeply dependent on native Node.js modules (like node-canvas or bcrypt with custom compilation flags), Bun will cause friction.
The ecosystem tail. There are packages that rely on Node.js internals in ways Bun doesn't support. module.createRequire patterns. require.resolve with edge-case paths. Dynamic require() of TypeScript files. These are being fixed โ Bun's compatibility improves every release. But if your stack has deep, weird Node.js dependencies, migrating to Bun is a project, not a flag flip.
The pragmatic approach: use Bun for everything it handles well (which is 95% of our codebase). Keep Node.js for the 5% that doesn't work yet. Re-evaluate every Bun release. The 95% keeps growing.
We didn't start on Bun. AACFlow's first prototype ran on Node.js with tsx โ the standard setup for a TypeScript project in 2024.
The migration was gradual:
Phase 1: Package manager. We switched npm install to bun install first. Zero code changes. The package.json format is identical. The lockfile format changed, but lockfiles are generated artifacts. This was a one-line CI change that made every install 10x faster. Immediate win, zero risk.
Phase 2: Scripts. We started running our build scripts, migration scripts, and data seed scripts with bun instead of tsx. Again, zero code changes โ TypeScript files that worked with tsx worked with bun. The startup speed improvement was immediately noticeable. Developers started adding #!/usr/bin/env bun shebangs to scripts. The tsx dependency became optional.
Phase 3: API server. We switched the production API server from Node.js to Bun. This required testing โ we ran both side by side for a week, comparing response times, error rates, and memory usage. Bun was faster across the board. No regressions. We cut over.
Phase 4: Agent execution engine. The most sensitive migration. We ran shadow executions โ Bun and Node.js processing the same workflows in parallel, comparing outputs. 100% match rate over 50,000 executions. Cut over. 10 million executions per month later, zero runtime-related regressions.
Phase 5: Testing. Switched Vitest from Node.js to Bun runtime. Test suites ran 40% faster. No test behavior changes. Done.
The key to the migration: never bet the platform on a runtime change. Shadow first. Compare results. Cut over when confidence is high. Bun was stable enough that every phase was a one-way transition โ we never needed to roll back.
Numbers without context are meaningless. Here's what Bun handles in our production environment:
10M+ agent executions per month โ each one is a DAG compiled from the visual editor, blocks resolved from the registry, tool calls dispatched to LLM providers, results streamed back via WebSocket
API server handling ~500 requests/second at peak โ WebSocket connections, REST API calls, webhook deliveries, connector sync triggers
60K+ developers creating, editing, and running workflows in real-time collaborative sessions
PostgreSQL connection pool of 50 connections, never saturated โ Bun's async I/O handles database queries efficiently
Redis pub/sub for multiplayer sync, Socket.IO rooms, and distributed agent coordination
Bun handles this on modest hardware. Our production instances run on 4 vCPU / 8 GB RAM. The runtime is not the bottleneck. The bottlenecks are LLM API latency, embedding generation throughput, and PostgreSQL query complexity โ all external to the runtime.
The runtime decision isn't about benchmarks. It's about philosophy.
We chose Bun because it aligns with how we build software: reduce friction between writing code and running code. Native TypeScript execution removes a build step from every interaction with the codebase. Fast package installation removes the "should I switch branches or avoid it?" tax. Lower memory footprint means self-hosted users can run AACFlow on affordable hardware.
We didn't choose Bun because it's the new thing. We chose it because it made our development faster, our production leaner, and our infrastructure simpler. Every build step you remove is a bug you don't write. Every second of startup time you eliminate is a second of developer attention you preserve. Every megabyte of memory you save is a dollar of infrastructure cost you don't pay.
Is Bun right for every project? No. If your entire stack depends on packages with native Node.js addons, Bun will hurt. If you need 100% Node.js API compatibility for legacy code, stick with Node.js. If you're in a regulated environment that only certifies Node.js LTS releases, Bun isn't an option yet.
But if you're building a TypeScript-heavy application, running a lot of scripts, managing a monorepo, deploying to Docker, or just tired of waiting for npm install โ Bun is worth serious evaluation. It's not perfect. But it's been perfect enough for us to run 10 million agent executions every month without a single runtime-related incident.
If you're self-hosting AACFlow, Bun comes pre-configured in our Docker image. No setup required. It's the runtime that ships with the platform.
If you're developing with the AACFlow codebase, install Bun (curl -fsSL https://bun.sh/install | bash), clone the repo, run bun install, and you're running. No Node.js required for the development workflow except for next dev (and we're watching Bun's Next.js compatibility improve with every release).
If you're building your own AI agent platform and evaluating runtimes: try running your scripts with Bun. Try bun install. Try running your API server with Bun. If it works โ and for most modern TypeScript projects, it will โ you'll wonder why you spent years waiting for compilers.