When response times lag or memory usage spikes in Node.js-based backend systems, developers often consider rewriting the entire application in another language. However, various case studies and benchmarks highlighting recent technological trends suggest that a "precision targeting" strategy—selectively optimizing only the bottlenecks identified through profiling—is more rational than a full rewrite. Rust is gaining attention as a powerful complement to Node.js, which has the limitations of a single-threaded event loop, as it offers performance comparable to C/C++ while eliminating garbage collection (GC) delays through ownership-based memory management 12. This article examines performance and memory management optimization strategies to consider when applying Rust to existing Node.js projects, based on actual adoption cases and technical characteristics.
Limitations of Node.js and the Need for 'Precision Targeting'

Node.js's single-threaded architecture and the V8 engine's garbage collection deliver excellent performance in I/O-centric web servers. However, when CPU-intensive (CPU-bound) operations occur, the event loop can be blocked by specific computations, or GC operations can cause latency spikes, adversely affecting the processing of other requests 23. In an actual case, a team that considered a full rewrite to resolve timeout issues in a photo upload server confirmed through profiling that only a single computation-intensive function was the bottleneck 3. In such cases, separating only that section into native code is more efficient than rewriting the entire application. Indeed, processing JPEGs with Jimp, a pure JavaScript-based image processing library, takes approximately 291ms per image, whereas using sharp, which leverages the native library (libvips), processes it in about 11ms, resulting in a performance difference of more than 26 times 3.
A full rewrite is typically considered when there is a lack of memory control, limitations in multi-threaded environments, or when long-term memory stability is required. Airtable faced expansion bottlenecks due to Node.js's limited multi-threading capabilities and the lack of fine-grained control over memory layout and lifecycle management. Consequently, they spent three years rewriting their database in Rust and are currently handling production customer traffic 1. OpenAI's Codex CLI is also undergoing a gradual transition from TypeScript to Rust to minimize dependencies and optimize performance without GC 1. Therefore, the first step is to clearly distinguish through profiling whether the system bottleneck stems from a "single slow loop" or from "structural limitations of the language architecture."
Resource Efficiency of No GC and the Ownership Model

Rust's most powerful advantage over Node.js is its "absence of a garbage collector (GC)" and "compile-time, ownership-based memory management." Because GC does not operate at runtime during memory allocation and deallocation, latency spikes do not occur, allowing for high resource efficiency 2. According to Sharkbench benchmark data released in August 2025 (Ryzen 7 7800X3D environment), the Rust-based Axum framework processed 21,030 requests per second while using only 8.5MB of memory 2. In contrast, the Node.js-based Express consumed 82.5MB of memory to handle 5,766 requests per second 2. Node.js's V8 engine blocks code execution during GC passes, which can structurally disadvantage p99 latency management 2.
These differences are clearly evident in actual services. GitHub migrated its Copilot agent runtime from TypeScript (Node.js) to approximately 800,000 lines of Rust code 4. Excluding model inference and network latency, local tests showed performance improvements of 3–6 times in out-of-process (OOC) mode and 6–21 times in in-process (IPC) mode 4. Additionally, when handling 10 concurrent clients, memory usage was drastically reduced from 1,383MB to 126MB 4. In AWS's Aurora DSQL project, Rust was introduced to resolve issues where a Java/Kotlin-based control plane was limited to 6,000 requests per second due to GC latency, securing a throughput of approximately 30,000 requests per second (30,000 TPS), which is about 10 times higher 1. Discord also rewrote its Read States service in Rust due to periodic GC latency spikes caused by the Go language 3.
FFI Call Overhead and Practical Bottlenecks with Non-POD Types

When introducing Rust native code into a Node.js environment, simply swapping languages does not guarantee unconditional performance. One of the most important considerations is the design at the "FFI (Foreign Function Interface) boundary." The pure function call overhead of the FFI boundary itself is around 35–38ns, which is negligible 35. However, performance can degrade sharply the moment data types fall outside the C ABI (Application Binary Interface). The memory copy (marshaling) costs incurred when passing non-POD (Plain Old Data) types such as std::string or Rust's String across the boundary cause practical bottlenecks 5. Additionally, changing languages means link-time optimization (LTO) is not applied, which can cause inline optimization failures for important functions 5.
| Category | FFI Overhead Characteristics and Precautions | Optimization Strategy |
|---|---|---|
| The Call Itself | Very low, at around 35–38ns. How data is processed is more important than the number of boundary crossings 3 | Avoid unnecessarily frequent, small calls |
| Non-POD Types | High costs incurred due to memory copies when passing strings, objects, vectors, etc., across the boundary 5 | Use primitive types (C ABI), byte buffers |
| Inline Optimization | Possibility of inline failures due to lack of cross-language LTO 5 | Prepare data outside the boundary in advance and pass it all at once |
Rust and C++ (or Node.js) have different object models, so types cannot be shared directly except for extern "C" signatures 5. When transferring ownership via handles, Rust's Box::into_raw and Box::from_raw are used, but the compiler cannot enforce the contract of "destruction exactly once" 5. Rust's verification tool (Miri) and C/C++ Sanitizers only verify within their respective languages, leaving FFI boundary functions themselves in the blind spots of verification tools 5. Therefore, to prevent memory leaks or double frees in boundary code, dedicated fuzzing and contract testing must be performed 5.
Binding Tools and Zero-Copy Integration Strategies

When integrating Rust into existing Node.js projects, reducing friction in the build pipeline is key. In the past, node-gyp was primarily used for C/C++ native add-on development, but deployment was cumbersome because it required Python and compilation tools (such as make, gcc, etc.) to be installed on the user's machine 6. As of 2026, @napi-rs is actively used for Rust-based native add-on development, recording approximately 300,000 to 500,000 downloads per week 6. Performance-focused tools like swc and rolldown are based on this 6. @napi-rs automatically generates TypeScript types and allows for automation of cross-compilation for different targets and binary deployment in CI/CD pipelines, reducing the build burden 6.
When a lighter-weight approach is needed, the node:ffi module, experimentally built into Node.js v26.9, can be utilized 3. Using this module allows for direct calls to compiled libraries without complex C++ wrappers or node-gyp builds 3. However, FFI calls in node:ffi are inherently synchronous 3. While the native Rust code performs heavy calculations, Node.js's main event loop can be blocked, causing other requests to wait 3. A proven pattern to bypass this is a zero-copy architecture combining worker_threads pools with Transferable Buffers 3. The main thread instantly transfers ownership to the worker thread without copying memory buffers, and the worker thread executes native code via synchronous FFI 3. Additionally, resource cleanup mechanisms (using) must be explicitly applied to prevent native memory leaks 3.
Gradual Adoption and the Reality of AI-Based Migration

A complete rewrite of large-scale projects requires significant time and cost. According to a case study published by Xiaoyun Wu in 2025, rewriting specific core services in Rust resulted in a twofold performance improvement and a $300k cost reduction 7. When a full rewrite is burdensome, it is advantageous to adopt Rust gradually by utilizing the "Strangler Fig pattern," which replaces existing components one by one with Rust shims 4.
Recently, AI agents have been actively utilized to lower the difficulty of such large-scale migrations. When GitHub migrated 800,000 lines of TypeScript code to Rust (May–August 2025), the secret to achieving this in the short period of 14.5 weeks lay in AI's mechanical verification 4. Instead of having humans read millions of lines of code, review agents using different models (Claude, GPT, Grok, etc.) compared the original TypeScript with the new Rust code line by line, and the pipeline was constructed to verify the results with static analysis and E2E tests 4. At this time, the role of human senior engineers transforms from simple code writers into "macro orchestrators" who design the architecture, define API contracts, set guardrails for AI-generated outputs, and make final risk judgments 4.
A Paradigm Shift in Memory Safety and System Architecture

The core motivation for adopting Rust lies not merely in performance improvements. Microsoft identified through its own analysis that memory safety-related vulnerabilities account for over 70% of all security vulnerabilities (CVEs), and is consequently replacing cores for Windows firmware and text rendering from C++ to Rust 1. This demonstrates that "ensuring memory safety" has emerged as an essential factor from a long-term maintenance perspective. Even for projects started with Node.js, it may be wise to mix Rust when specific computations or domain logic require ultra-low latency and definitive resource isolation. However, the equation "Rust = universal solution" does not hold. One must carefully calculate the impact on the development workflow, including increased compilation times, the learning curve of the asynchronous programming model, and verification gaps at FFI boundaries 15.
In conclusion, the first step when refactoring existing Node.js projects is to identify actual bottlenecks through profiling. Precisely targeting the confirmed bottleneck sections and latency spike sections caused by GC through worker thread structures based on @napi-rs or node:ffi, and leveraging Rust's strengths at data boundaries where the benefits of the ownership model are maximized, is a practical route to designing advanced backend architectures from 2025 onwards.
References
- Rust Case Studies · sxlijin.github.io↩
- Rust vs Node.js : benchmark backend 2026 · aurabase.cloud↩
- Before You Rewrite Node.js in Rust, Try This First · youtube.com↩
- GitHub Copilot Runtime Rust Migration: Hype vs. Reality · milanjovanovic.tech↩
- [Design 11] 메모리 안전성 트레이드오프 - 42JerryKim · 42jerrykim.github.io↩
- ↩
- URLs, Rust Speed, and Tokens That Cost Less · linkedin.com↩




