Why We Chose Rust for Our Backend
Pastetory's backend started as a Node.js Lambda function. It worked fine during prototyping, but as we moved towards production we hit limitations that made us reconsider. We rewrote the entire API in Rust — and the results speak for themselves.
The Numbers
Here's what our production Rust Lambda achieves on the smallest available tier (128MB, ARM64):
- Cold start: 136ms initialisation
- GET /version: 1–2ms
- GET /bundles (fetch user data): 130–250ms
- POST /snippets (text): 600–1,400ms
- Presigned upload (images/files): under 200ms
- Memory usage: 31–33MB typical
- Binary size: 14MB (5.7MB zipped)
These numbers are on the cheapest Lambda configuration available. We don't need to over-provision memory to get acceptable performance — Rust is simply that efficient.
Why Not Node.js?
Node.js served us well during development. It's fast to iterate with, the ecosystem is huge, and Lambda has first-class support. But we ran into three problems:
Memory pressure with images. Our original architecture processed images in the Lambda function — decoding base64, generating thumbnails, uploading to S3. This pushed memory usage to 100MB and response times to 7 seconds for image snippets. In Rust, we redesigned the architecture to use presigned uploads, but the memory discipline of Rust meant we could handle all metadata operations in under 33MB.
Cold start tax. Node.js Lambdas with a few dependencies can take 300–800ms to cold start. For a real-time sync tool where responsiveness matters, that's noticeable. Rust's 136ms cold start means even the first request of a session feels instant.
Confidence in correctness. Rust's type system and ownership model mean entire categories of bugs — null pointer exceptions, race conditions, memory leaks — simply can't happen. For a service handling encrypted user data, that confidence matters.
The Developer Experience
Rust's reputation for a steep learning curve is deserved, but the tooling has improved enormously. We use cargo-lambda for local development and deployment, which provides a seamless workflow:
- Write code with full IDE support (rust-analyzer)
cargo lambda build --release --arm64produces the binary- Zip and deploy with a single AWS CLI command
Compile times are the main trade-off. A full release build takes around 30 seconds, compared to near-instant restarts with Node.js. But for a production API that changes weekly rather than hourly, this is a non-issue.
ARM64 on Lambda
We run on ARM64 (Graviton2) rather than x86_64. AWS charges 20% less for ARM Lambda invocations, and Rust compiles natively for ARM with no performance penalty. It's free savings with zero effort.
The Architecture That Rust Enables
Because Rust is so memory-efficient, we can keep our Lambda at 128MB and still handle complex operations like:
- Deserializing and re-serializing gzipped JSON bundles from S3
- Generating presigned URLs for both AWS S3 and Cloudflare R2
- Managing user storage calculations across multiple storage backends
- Handling authentication token validation inline
With Node.js, we'd likely need 256MB or 512MB to do this comfortably, doubling or quadrupling our compute costs.
Would We Recommend Rust for Every Lambda?
No. If you're building a simple CRUD API or prototyping quickly, Node.js or Python will get you there faster. But if you're building a service where performance, memory efficiency, and long-term reliability matter — and you're willing to invest in the learning curve — Rust on Lambda is exceptional.
For Pastetory, it means we can serve thousands of users on the cheapest infrastructure tier available, with response times that feel instant. That's the kind of foundation you want for a product you plan to run for years.