Coming from Express? β
If you've built Express apps, you already know bunWay. We designed it that way.
Zero Learning Curve
bunWay uses the same patterns, same middleware names, and same API conventions you already know from Express. The only difference? It runs on Bunβand it's faster.
Side-by-Side Comparison β
Express (Node.js) β
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const session = require('express-session');
const morgan = require('morgan');
const app = express();
app.use(cors());
app.use(helmet());
app.use(morgan('dev'));
app.use(express.json());
app.use(session({ secret: 'keyboard cat' }));
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id });
});
app.listen(3000);bunWay (Bun) β
import { bunway, cors, helmet, logger, json, session } from 'bunway';
const app = bunway();
app.use(cors());
app.use(helmet());
app.use(logger('dev'));
app.use(json());
app.use(session({ secret: 'keyboard cat' }));
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id });
});
app.listen(3000);The difference? One import statement. Everything else is the same.
Middleware Mapping β
Every Express middleware you know has a bunWay equivalentβbuilt right in:
| Express | bunWay | What it does |
|---|---|---|
express.json() | json() | Parse JSON request bodies |
express.urlencoded() | urlencoded() | Parse URL-encoded form data |
express.text() | text() | Parse text request bodies |
express.raw() | raw() | Parse raw binary bodies (webhooks) |
express.static() | serveStatic() | Serve static files |
cors | cors() | Handle CORS headers |
helmet | helmet() | Set security headers |
morgan | logger() | Request logging |
express-session | session() | Session management |
passport | passport() | Authentication |
csurf | csrf() | CSRF protection |
compression | compression() | Gzip/deflate compression |
express-rate-limit | rateLimit() | Rate limiting |
cookie-parser | cookieParser() | Parse cookies |
multer | upload() | File uploads (multipart) |
No more hunting through npm. No more version conflicts. It's all built-in.
API Comparison β
Request Object β
| Express | bunWay | Notes |
|---|---|---|
req.params | req.params | Identical |
req.query | req.query | Identical |
req.body | req.body | Identical |
req.cookies | req.cookies | Identical |
req.path | req.path | Identical |
req.method | req.method | Identical |
req.get('header') | req.get('header') | Identical |
req.ip | req.ip | Identical |
req.session | req.session | With session middleware |
req.protocol | req.protocol | Identical (respects X-Forwarded-Proto with trust proxy) |
req.secure | req.secure | Identical |
req.fresh / req.stale | req.fresh / req.stale | ETag + Last-Modified cache validation |
req.range(size) | req.range(size) | Range header parsing for partial content |
req.param(name) | req.param(name) | Checks params β body β query |
req.acceptsCharsets() | req.acceptsCharsets() | Identical |
req.acceptsEncodings() | req.acceptsEncodings() | Identical |
req.acceptsLanguages() | req.acceptsLanguages() | Identical |
Response Object β
| Express | bunWay | Notes |
|---|---|---|
res.json() | res.json() | Identical |
res.send() | res.send() | Identical |
res.status() | res.status() | Identical |
res.set() | res.set() | Identical |
res.cookie() | res.cookie() | Identical |
res.redirect() | res.redirect() | Identical |
res.sendStatus() | res.sendStatus() | Identical |
res.sendFile() range | res.sendFile() range | Automatic 206 Partial Content with Accept-Ranges: bytes |
res.jsonp(data) | res.jsonp(data) | JSONP with configurable callback name |
res.send() chaining | res.send() chaining | Returns this for chaining |
res.download(path, fn, cb) | res.download(path, fn, cb) | Callback support for error handling |
res.attachment(file) | res.attachment(file) | Auto-detects Content-Type |
res.end(data, enc, cb) | res.end(data, enc, cb) | Encoding and callback support |
Routing β
| Express | bunWay | Notes |
|---|---|---|
app.get() | app.get() | Identical |
app.post() | app.post() | Identical |
app.put() | app.put() | Identical |
app.delete() | app.delete() | Identical |
app.use() | app.use() | Identical |
app.route('/path') | app.route('/path') | Chainable route definitions |
express.Router() | bunway.Router() | Same pattern |
Router({ mergeParams: true }) | Router({ mergeParams: true }) | Inherit parent route params |
req.res / res.req | req.res / res.req | Cross-references set during dispatch |
res.app | res.app | Access app instance from response |
app.use([paths], handler) | app.use([paths], handler) | Array path mounting |
https.createServer(opts, app) | app.listen({ tls: opts }) | Native TLS support |
server.close(callback) | app.close(callback) | Graceful shutdown |
Migration Examples β
Body Parsing β
Express:
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));bunWay:
app.use(json({ limit: '10mb' }));
app.use(urlencoded({ extended: true }));Session Management β
Express:
const session = require('express-session');
app.use(session({
secret: 'my-secret',
resave: false,
saveUninitialized: true,
cookie: { maxAge: 86400000 }
}));bunWay:
import { session } from 'bunway';
app.use(session({
secret: 'my-secret',
resave: false,
saveUninitialized: true,
cookie: { maxAge: 86400000 }
}));Static Files β
Express:
app.use(express.static('public'));
app.use('/assets', express.static('assets', { maxAge: '1d' }));bunWay:
app.use(serveStatic('public'));
app.use('/assets', serveStatic('assets', { maxAge: 86400000 }));Request Logging β
Express:
const morgan = require('morgan');
app.use(morgan('combined'));
app.use(morgan(':method :url :status :response-time ms'));bunWay:
import { logger } from 'bunway';
app.use(logger('combined'));
app.use(logger(':method :url :status :response-time ms'));Same format strings. Same output.
Security Headers β
Express:
const helmet = require('helmet');
app.use(helmet());
app.use(helmet({
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false
}));bunWay:
import { helmet } from 'bunway';
app.use(helmet());
app.use(helmet({
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false
}));Rate Limiting β
Express:
const rateLimit = require('express-rate-limit');
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
}));bunWay:
import { rateLimit } from 'bunway';
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
}));Sub-Routers β
Express:
const router = express.Router();
router.get('/profile', (req, res) => res.json({ user: 'me' }));
app.use('/api', router);bunWay:
const router = bunway.Router();
router.get('/profile', (req, res) => res.json({ user: 'me' }));
app.use('/api', router);Why Switch? β
Speed β
Bun is significantly faster than Node.js. Your Express-style code runs faster without any changes.
Simplicity β
- No
node_moduleswith thousands of packages - No version conflicts between middleware
- Native TypeScriptβno build step needed
Batteries Included β
Stop hunting for middleware on npm. Everything you need is built-in:
- Body parsing
- CORS
- Sessions
- Security headers
- Logging
- Rate limiting
- Static files
- Compression
- CSRF protection
Zero Learning Curve β
You already know how to use bunWay. Same patterns. Same API. Just faster.
Quick Start β
bun add bunwayimport { bunway, json, cors, helmet, logger } from 'bunway';
const app = bunway();
app.use(cors());
app.use(helmet());
app.use(logger('dev'));
app.use(json());
app.get('/', (req, res) => {
res.json({ message: 'Hello from bunWay!' });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});That's it. You're running on Bun.
Content Negotiation (v1.0.8+) β
bunWay now implements RFC 7231 quality-value parsing for all req.accepts*() methods. This means headers like Accept: text/html;q=0.9, application/json are parsed correctly with proper priority ordering β identical to Express's accepts package.
req.is() β MIME Type Matching β
bunWay's req.is() now supports MIME type wildcards:
// These all work like Express:
req.is("json") // matches "application/json"
req.is("text/*") // matches "text/html", "text/plain", etc.
req.is("application/*") // matches "application/json", "application/xml", etc.res.send() β Auto Content-Type Detection β
res.send() now auto-detects Content-Type like Express:
res.send("Hello") // β Content-Type: text/html; charset=utf-8
res.send({ ok: true }) // β Content-Type: application/json (delegates to json())
res.send(buffer) // β Content-Type: application/octet-streamRegex Routes β
bunWay now supports regex route patterns:
app.get(/\/fly$/, (req, res) => { ... });
app.get(/\/users\/(?<id>\d+)/, (req, res) => {
res.json({ id: req.params.id }); // Named capture groups become params
});Catch-all Routes β
app.all("*", (req, res) => {
res.status(404).json({ error: "Not Found" });
});Sub-App Mounting β
const admin = bunway();
app.use("/admin", admin);
console.log(admin.mountpath); // "/admin"
console.log(admin.path()); // "/admin"req.param() β Unified Parameter Lookup β
Like Express's deprecated req.param(), bunWay checks params, body, then query:
// GET /users/42?role=admin (body: { name: "Jo" })
req.param("id"); // "42" (from params)
req.param("name"); // "Jo" (from body)
req.param("role"); // "admin" (from query)req.acceptsCharsets(), req.acceptsEncodings(), req.acceptsLanguages() β
Content negotiation helpers that mirror Express:
req.acceptsCharsets("utf-8", "iso-8859-1"); // "utf-8"
req.acceptsEncodings("gzip", "deflate"); // "gzip"
req.acceptsLanguages("en", "fr"); // "en"res.send() / res.json() β Chainable β
res.send() and res.json() now return this for chaining:
res.status(200).send("ok");
res.status(201).json({ created: true });res.download() β Callback Support β
res.download() now accepts an optional callback for error handling:
res.download("/files/report.pdf", "report.pdf", (err) => {
if (err) console.error("Download failed:", err);
});res.attachment() β Content-Type Auto-Detection β
res.attachment() sets Content-Disposition and auto-detects Content-Type:
res.attachment("photo.png");
// Content-Disposition: attachment; filename="photo.png"
// Content-Type: image/pngres.end() β Encoding & Callback β
res.end() now supports encoding and callback arguments like Express:
res.end("done", "utf-8", () => {
console.log("Response sent");
});What's Different? β
Just two things:
- Import style: ES modules only (no
require()) - Runtime: Bun, not Node.js
That's it. The handler signature is identical: (req, res, next).
Ready to migrate? Check out the Getting Started guide or browse the Middleware documentation.