Getting StartedChoose Your Runtime

Bun

Install NextRush on Bun, run a hello-world server on Bun.serve(), and see exactly where the Bun adapter's import path diverges from Node's.

Runtime · Fast startup

Fast startup, native TypeScript. Bun runs your .ts files directly — no tsx, no separate transpile step — and starts significantly faster than Node. The createApp/createRouter API is identical; only the adapter import changes. The tradeoff is a younger runtime with a smaller production track record than Node.

Native TSFast startup~10 minBeginnerStable adapter

Where Bun fits

RuntimeBest forTradeoff
NodeProduction APIs, long-running servers, full ecosystemHeavier footprint than Bun or edge
Bun · this pageLocal dev speed, native TypeScript, fast cold startYounger ecosystem, some node:* gaps
DenoSecure by default, permission modelSmaller package ecosystem
EdgeGlobal low latency, Cloudflare/VercelNo long-running server, node:* limited
ServerlessPay-per-request, auto-scalingCold starts, no persistent state

Not sure which runtime fits? See the runtime decision guide.

What changes from Node

This is the first question everyone asks. Almost nothing:

SameDifferent
createApp()listen() import — comes from @nextrush/adapter-bun, not nextrush
createRouter()Runtime underneath — Bun.serve(), not node:http
Route registrationNo tsx needed — Bun runs .ts directly
Context APIHot reload — bun --hot run src/index.ts
Middleware

One import line. That's the real divergence.

"Partial" means the adapter is Stable but not yet verified against the real platform in CI (see the compatibility matrix) — not that it's missing features.

Before you begin

  • ✓ Bun installed (bun.sh)
  • ✓ A terminal
  • ✓ ~10 minutes

Confirm your Bun version

  • Why this matters: @nextrush/adapter-bun targets Bun's stable Bun.serve() surface.
bun --version
# needs to read 1.x.x or higher

If Bun isn't installed yet, follow the install instructions before continuing.

Install NextRush and the Bun adapter

  • Why this matters: unlike Node, the nextrush meta-package does not bundle the Bun adapter — you add @nextrush/adapter-bun alongside it.
$ pnpm add nextrush @nextrush/adapter-bun

Or scaffold instead:

bunx create-nextrush my-api --runtime bun

Behind the scenes:

  • createApp and createRouter still come from nextrush — runtime-independent
  • listen, serve, and createHandler come from @nextrush/adapter-bun — the meta-package's listen is wired to @nextrush/adapter-node specifically

This is the one place Bun's setup differs from Node's. import { listen } from 'nextrush' starts a Node server even when running under Bun (Bun's Node-compat layer makes that import resolve, but it never reaches Bun.serve()). Import listen/serve from @nextrush/adapter-bun explicitly to run on Bun's native server.

Run a hello-world server

Create src/index.ts:

src/index.ts
import { createApp, createRouter } from 'nextrush';
import { listen } from '@nextrush/adapter-bun';

const app = createApp();
const router = createRouter();

router.get('/', (ctx) => {
  ctx.json({ runtime: 'bun', status: 'ok' });
});

app.route('/', router);
await listen(app, 8080);

Run it:

bun run src/index.ts

Expected result — curl http://localhost:8080/ returns {"runtime": "bun", "status": "ok"}

Behind the scenes:

  • listen(app, 8080) from @nextrush/adapter-bun calls Bun.serve() directly
  • Each request goes to NextRush's router through the same Context API Node uses
  • ctx.runtime reads 'bun' because the adapter, not your handler, decides that value
  • Hot reload: bun --hot run src/index.ts restarts on file change — no NextRush setup needed

Why Bun diverges from Node

Your server is now running. Before moving on, it's worth understanding what's genuinely different — and what isn't.

Same: the Context API (ctx.json, ctx.params, ctx.query, ctx.set, …), createApp, createRouter, route registration, and middleware all behave identically to Node. Nothing in your route handlers is Bun-specific.

Different: the import path (Step 2) is the real divergence. One behavioral difference follows from a platform gap — Bun.serve() has no built-in request timeout the way node:http does, so @nextrush/adapter-bun adds its own AbortController-based timeout (defaults to 30 seconds, same as Node). The request body size limit is a deliberate match too: Bun.serve() reads the full body before NextRush sees it, so @nextrush/adapter-bun caps maxRequestBodySize at the same 1 MB default as @nextrush/adapter-node.

@nextrush/adapter-bun is a Stable, independently-versioned package — CI-tested against a real Bun runtime, not Node's Bun-compat layer. See the Bun adapter reference for its full surface.

Bun runtime working — you're ready to verify and move on.

Bun baseline complete

Confirm the server answers:

curl -i http://localhost:8080/
# → HTTP/1.1 200 OK
# → {"runtime":"bun","status":"ok"}

Your project now looks like:

my-api/
  package.json
  src/
    index.ts

Baseline complete — a real NextRush server on Bun. The only thing that changed from Node was one import line.

What you learned

  • nextrush gives you the runtime-independent half — createApp, createRouter, and route registration are identical on Bun and Node
  • listen/serve/createHandler come from @nextrush/adapter-bun, not the meta-package
  • ✓ The adapter ships independently and is Stable — CI-tested against real Bun, not Node's compat layer

The same app, every runtime

The code you just wrote runs on every runtime NextRush supports. Only the adapter import changes:

  1. createApp()
  2. Router
  3. Handler
  4. Bun adapter
  5. Running
// Node:      import { listen } from '@nextrush/adapter-node';
// Bun:       import { listen } from '@nextrush/adapter-bun';    // ← you are here
// Deno:      import { listen } from '@nextrush/adapter-deno';
// Edge:      import { serve } from '@nextrush/adapter-edge';
// Serverless import { handler } from '@nextrush/adapter-serverless';

The route handler, middleware, and Context API never change. You learned one runtime today — the same code runs on all of them.

Next steps

🚀 Build a Task API — recommended · ~20 min

Go past hello-world on the runtime you set up: routing, a JSON body, and a real 404. Start the tutorial →

Continue learning

Was this helpful?

On this page