Node.js & Elysia Technical Debt Cleanup
Your Express app served you well. Now it's time for type safety, performance, and a modern runtime.
At Variant Systems, we pair the right technology with the right approach to ship products that work.
Why this combination
- Express middleware chains become opaque debugging nightmares at scale
- Untyped route handlers produce runtime errors that type systems would catch
- Node.js performance plateaus can be solved by migrating to Bun
- Legacy callback patterns and mixed async styles create maintenance headaches
Opaque Middleware Chains, Untyped Handlers, and Three Async Patterns at Once
Express was the right choice when you started. But the codebase has grown, and now you’re dealing with middleware order bugs that take hours to diagnose. Route handlers that accept any and return any. Error handling that varies by endpoint - some use try-catch, some use middleware, some just crash.
The async story is messy too. Older parts of the codebase use callbacks. Someone introduced Promises. Then async/await arrived but wasn’t applied consistently. You have three patterns for the same concept, and each has different error handling semantics.
Incremental Route Migration to Elysia with Type-Safe Validation
We migrate routes incrementally to Elysia. Both frameworks can run simultaneously behind a reverse proxy, so you don’t need a big-bang rewrite. New features go in Elysia; existing routes migrate during dedicated cleanup sprints.
Each migrated route gets proper validation schemas using Elysia’s built-in type system. Request bodies, query parameters, and response shapes are all typed and validated. The types propagate to your frontend via Eden Treaty, eliminating the need for separate API documentation that drifts out of sync.
Red Squiggles in the Editor Instead of 500 Errors in Production
Response times drop because Bun handles HTTP faster than Node.js. But the bigger win is developer velocity. Type errors that used to appear as 500 errors in production now appear as red squiggles in your editor. Route definitions are self-documenting - you can read the types and understand the API contract.
Debugging gets easier too. Elysia’s lifecycle hooks are explicit and ordered. No more wondering which middleware runs when. Error handling is centralized with typed error responses. Your on-call rotation becomes less stressful.
Bun’s Native HTTP, Sub-Millisecond Cold Starts, and 2-4x Throughput
The migration to Bun is not just a runtime swap. Bun’s native HTTP server bypasses the overhead of Node’s http module entirely, resulting in measurable latency reductions under concurrent load. In benchmarks against production-shaped workloads, we typically observe 2-4x throughput improvements for JSON-heavy API endpoints. Bun’s built-in SQLite driver and native fetch implementation also eliminate dependencies that previously required separate npm packages. Startup time drops from seconds to milliseconds, which matters for serverless deployments where cold starts directly affect user experience. These gains compound: faster response times improve client-side perceived performance, reduce timeout-related retry storms, and lower infrastructure costs because fewer instances serve the same traffic.
Strict Types, Biome Linting, and a Toolchain Developers Actually Run
The type system is the primary guard. Elysia’s strict typing means you can’t accidentally return the wrong shape or accept unvalidated input. The compiler catches what code review misses.
We also set up Biome for linting and formatting - faster than ESLint and Prettier combined, with sensible defaults. CI runs type checks, linting, and tests on every push. Bun’s built-in test runner replaces Jest with something faster and simpler. The toolchain is lean and fast, so developers actually run it.
What you get
Ideal for
- Express or Fastify apps with untyped route handlers
- Teams frustrated by middleware debugging in large codebases
- Products hitting Node.js performance limits
- Companies wanting end-to-end type safety without GraphQL