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