Migrate from Express
There are two paths for migrating an Express.js application to bext: full replacement (translate Express patterns to native bext features) or reverse proxy (keep Express running behind bext). Choose based on your app's complexity.
Migration Strategy
| Approach | Best for | Effort |
|---|---|---|
| Full replacement | Static sites, REST APIs, SSR apps with standard middleware | Low-medium |
| Reverse proxy | Complex Express apps with deep middleware chains, custom protocols | Low |
| Hybrid | Replace static serving and TLS with bext, keep Express for API routes | Low |
Middleware Translation
Static file serving
Express:
app.use(express.static('public'));
app.use('/assets', express.static('dist/assets'));
bext.config.toml:
[server]
static_dir = "public"
[[route_rules]]
pattern = "/assets/**"
static_dir = "dist/assets"
bext serves static files with automatic Brotli/gzip compression, ETag headers, and Cache-Control. No middleware needed.
CORS
Express:
const cors = require('cors');
app.use(cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
}));
bext.config.toml:
[cors]
allowed_origins = ["https://app.example.com", "https://admin.example.com"]
allow_credentials = true
max_age = 86400
Security headers (Helmet)
Express:
const helmet = require('helmet');
app.use(helmet());
bext.config.toml:
[security.headers]
x_content_type_options = "nosniff"
x_frame_options = "DENY"
x_xss_protection = "1; mode=block"
referrer_policy = "strict-origin-when-cross-origin"
content_security_policy = "default-src 'self'; script-src 'self' 'unsafe-inline'"
strict_transport_security = "max-age=63072000; includeSubDomains; preload"
bext sets these headers on every response. No middleware required.
Rate limiting
Express:
const rateLimit = require('express-rate-limit');
app.use(rateLimit({ windowMs: 60000, max: 100 }));
bext.config.toml:
[rate_limit]
enabled = true
requests_per_minute = 100
distributed = true # share counters across instances via Redis
Body parsing
Express:
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
bext handles request body parsing automatically for SSR and plugin routes. For upstream proxy routes, the body is forwarded as-is. Configure size limits:
[server]
max_body_size_mb = 10
Compression
Express:
const compression = require('compression');
app.use(compression());
bext compresses responses with Brotli and gzip automatically. No configuration needed -- it is enabled by default and negotiated via Accept-Encoding.
Routing Differences
Express uses method-based chaining (app.get(), app.post()). bext uses file-system routing for pages and [[route_rules]] for configuration-driven rules:
Express:
app.get('/products/:id', (req, res) => { ... });
app.post('/api/orders', (req, res) => { ... });
bext: Place route handlers in the file system:
src/
pages/
products/
[id].tsx # GET /products/:id (SSR page)
api/
orders.ts # POST /api/orders (API route)
Or configure route rules:
[[route_rules]]
pattern = "/products/:id"
render = "isr"
ttl_ms = 60000
[[route_rules]]
pattern = "/api/**"
render = "ssr"
headers = { "cache-control" = "no-store" }
WebSocket Migration
Express + ws:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => { ... });
bext: Use the built-in realtime hub:
[realtime]
enabled = true
path = "/ws"
max_connections = 10_000
bext's realtime hub supports WebSocket and SSE with pub/sub channels, authentication hooks, and automatic reconnection. For custom WebSocket logic, use a QuickJS plugin or proxy to a dedicated WebSocket server.
Template Engine Support
If your Express app uses Pug, EJS, or Handlebars for server-side templates, you have two options:
1. Migrate to React/JSX -- bext's SSR engine renders React components natively. 2. Proxy mode -- Keep Express for template rendering, put bext in front for TLS, caching, and static assets.
Keeping Express Behind bext (Reverse Proxy)
The lowest-effort migration: run Express on a local port and use bext as the reverse proxy with TLS, caching, compression, and WAF:
[server]
listen = "0.0.0.0:443"
upstream = "http://127.0.0.1:3001" # your Express app
[tls]
auto_acme = true
acme_email = "admin@example.com"
[cache.isr]
max_entries = 5_000
default_ttl_ms = 30_000
[rate_limit]
enabled = true
requests_per_minute = 600
Start both:
# Terminal 1: Express
PORT=3001 node server.js
# Terminal 2: bext
bext-server run
With this setup you immediately get:
- Auto-TLS with Let's Encrypt
- HTTP/2 and HTTP/3
- Brotli + gzip compression
- WAF and rate limiting
- ISR caching (respects Cache-Control headers from Express)
- Prometheus metrics at /__bext/metrics
You can incrementally migrate routes from Express to native bext handling over time.
Migration Checklist
1. Inventory your Express middleware -- map each to a bext feature (see table above)
2. Decide on full replacement vs. reverse proxy
3. Install bext and create bext.config.toml
4. If replacing: move route handlers to file-system routes or [[route_rules]]
5. If proxying: set upstream and start Express on a local port
6. Configure TLS, CORS, rate limiting, and security headers in bext.config.toml
7. Run your test suite against the bext server
8. Remove Express dependencies from production package.json (if fully replaced)
9. Deploy and monitor metrics at /__bext/metrics