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:
- Migrate to React/JSX -- bext's SSR engine renders React components natively.
- 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-Controlheaders from Express) - Prometheus metrics at
/__bext/metrics
You can incrementally migrate routes from Express to native bext handling over time.
Migration Checklist
- Inventory your Express middleware -- map each to a bext feature (see table above)
- Decide on full replacement vs. reverse proxy
- Install bext and create
bext.config.toml - If replacing: move route handlers to file-system routes or
[[route_rules]] - If proxying: set
upstreamand start Express on a local port - Configure TLS, CORS, rate limiting, and security headers in
bext.config.toml - Run your test suite against the bext server
- Remove Express dependencies from production
package.json(if fully replaced) - Deploy and monitor metrics at
/__bext/metrics
The reverse-proxy path is the lowest-risk first step. You get Auto-TLS, HTTP/2, brotli compression, and WAF in minutes, then migrate individual routes to native bext handling incrementally — without a big-bang rewrite.
Related
- Frameworks Overview — supported frameworks and which gives the most performance
- Caching — ISR cache that replaces manual
Cache-Controllogic in Express middleware - WebSockets — migrating
wsWebSocket servers to the bext realtime hub - Plugins — porting Express middleware that doesn't map to a built-in bext feature
- Security — bext WAF and rate limiting replacing
helmetandexpress-rate-limit - Configuration Reference — full
bext.config.tomlschema
Links
- expressjs.com — official Express.js documentation