What We Learned Building a Rust Runtime for TypeScript
3 days ago
- #Backend Development
- #Rust
- #TypeScript
- Encore's TypeScript support involved creating a new runtime in Rust instead of extending the Go runtime or using TypeScript directly, to avoid reimplementing infrastructure for each new language and to overcome Node.js's single-threaded limitations.
- The Rust runtime handles all infrastructure concerns like HTTP lifecycle, database connections, pub/sub, and tracing, leaving TypeScript for business logic only, which improves performance by leveraging multi-threading.
- A prototype using Go as a sidecar with Node.js was discarded due to high latency from IPC overhead and operational complexity from managing multiple processes.
- The runtime is integrated into the Node.js process via Rust NAPI bindings, allowing direct interaction and avoiding serialization, with enhancements to handle JavaScript promises and cancellation guards.
- Application metadata (generated at compile time) and runtime configuration (generated at deploy time) are separated, enabling the same application to run in different environments without code changes.
- An embedded gateway using Pingora handles routing, auth, and more within the same process, allowing user-defined TypeScript auth handlers to execute directly without IPC.
- Pub/sub and object storage systems abstract cloud provider differences through trait objects, supporting NSQ, GCP, and AWS with consistent interfaces.
- A custom binary trace protocol with efficient serialization and time anchoring provides detailed, sampled tracing without overhead, though an OpenTelemetry adapter is planned.
- A TypeScript parser built on SWC extracts infrastructure declarations from code to generate metadata, enabling features like architecture diagrams and API docs.
- Key learnings include investing in structured error types, prioritizing OpenTelemetry support, using more snapshot testing, and exploring a unified runtime for Go and Rust.
- Benchmarks show Encore.ts outperforms frameworks like Express.js with significantly higher throughput and lower latency, especially with validation enabled.