Skip to content

Getting Started

bunway lets you write Bun HTTP handlers with familiar Express-style ergonomics while staying 100% Bun-native. If you know Express, you already know bunway.

Coming from Express?

bunway uses the exact same (req, res, next) signature you're used to. Check out the Express Migration Guide for a side-by-side comparison.

Installation

bash
bun add bunway

bunway is Bun-only. Please do not attempt to use it from Node.js.

Hello world

Spin up a minimal server to see bunway in action.

ts
import bunway, { cors, json, errorHandler } from "bunway";

const app = bunway();

app.use(cors({ origin: true }));
app.use(json());
app.use(errorHandler({ logger: console.error }));

app.get("/", (req, res) => res.text("Hello from bunway"));

app.listen({ port: 7070 }, () => {
  console.log("bunway listening on http://localhost:7070");
});

Express patterns, Bun speed

Notice the familiar (req, res) signature? bunway mirrors Express patterns so you can start building immediately.

Requests & responses

Every handler receives (req, res, next):

  • req (BunRequest) - Express-like request object with params, query, body, locals, and more
  • res (BunResponse) - Express-like response object with json(), send(), status(), and more
  • next (NextFunction) - Call to continue the middleware chain
ts
app.post("/echo", async (req, res) => {
  const body = await req.parseBody();
  return res.json({ received: body });
});
bash
curl -X POST http://localhost:7070/echo \
  -H "Content-Type: application/json" \
  -d '{"hello":"bunway"}'

Auto body parsing

By default, bunway parses JSON and URL-encoded bodies automatically. The parsed payload is available on req.body right away.

Want the raw Fetch API? Return a Response directly and bunway will still apply middleware headers.

Sub-routers

ts
import { Router } from "bunway";

const api = new Router();
api.get("/users", (req, res) => res.json({ users: [] }));

const app = bunway();
app.use("/api", api);

Middleware inheritance

Mounted routers inherit global middleware (CORS, logging, error handling), so /api feels cohesive without extra wiring.

Middleware

bunway ships with Express-compatible middleware:

ts
import bunway, { cors, json, helmet, session, logger } from "bunway";

const app = bunway();

app.use(logger("dev"));
app.use(cors({ origin: true }));
app.use(helmet());
app.use(json());
app.use(session({ secret: "keyboard cat" }));

See the Middleware Overview for the full list.

Testing with Bun

Use Bun's built-in test runner:

ts
import { describe, it, expect } from "bun:test";
import bunway from "bunway";

describe("health", () => {
  it("returns OK", async () => {
    const app = bunway();
    app.get("/health", (req, res) => res.text("OK"));
    const res = await app.handle(new Request("http://localhost/health"));
    expect(await res.text()).toBe("OK");
  });
});

Run tests via bun test.

Testing tips

The Bun test runner resets global state between files. Keep fixtures explicit and prefer per-test setup/teardown to avoid surprises.

Stopping the Server

ts
await app.close();

For HTTPS, signal handling, and testing patterns, see the Server Lifecycle guide.

Next steps

Project scaffolding

Looking for repo layout, scripts, and contributor workflow? See the repository README for setup details beyond runtime usage.