DEV Community

Cover image for Migrating from TypeScript/Node.js to Rust? Here's how to map your npm packages
Sagar Kashyap
Sagar Kashyap

Posted on

Migrating from TypeScript/Node.js to Rust? Here's how to map your npm packages

Rewriting heavy TypeScript/Node.js services in Rust is one of the most effective ways to lower your cloud infrastructure bill and eliminate CPU bottlenecks.

Whether you're porting an Express/Fastify API to Axum, moving a background worker from Node to Rust, or writing high-performance CLI tools, Rust delivers predictable low latency and negligible memory usage.

However, once you set up your Cargo.toml, you face the ecosystem gap. npm has over 2 million packages, while Crates.io has a distinct, highly modular set of libraries designed for type-safety and memory control.

Here is the cheat sheet for mapping common TypeScript/Node.js packages to their idiomatic Rust equivalents, followed by a way to automate this directly inside VS Code.


📊 TypeScript/Node.js ➡️ Rust Crate Mapping Cheat Sheet

npm Package Rust Crate Equivalent Why & How to Use It
express / fastify axum or actix-web Use Axum for a clean, modular router built on top of Tokio and Tower. Use Actix-web for battle-tested, high-throughput web APIs.
axios / node-fetch reqwest The gold standard HTTP client in Rust. Supports async, connection pooling, and automatic JSON deserialization.
zod serde + validator / garde zod handles parsing and runtime validation. In Rust, serde handles the JSON structure parsing, while crates like validator or garde perform runtime checks (like email formats, regex, or range boundaries).
prisma / typeorm sqlx or diesel SQLx gives you compile-time checked raw SQL queries without an ORM layer. Diesel is a full-featured, compile-time safe ORM.
winston / pino tracing The standard telemetry and logging ecosystem in Rust. Supports structured logs, span contexts, and async event tracking.
jest / vitest cargo test (built-in) Rust features built-in testing commands out of the box. For property-based testing, pair it with the proptest crate.
dotenv dotenvy A actively maintained port of dotenv for Rust to load .env files into environment variables.
bullmq / agenda apalis or sidekiq-rs For distributed queue processing. Alternatively, simple concurrency can be handled directly with tokio::spawn and channels.

🔍 In-Depth Mappings & Code Examples

1. HTTP Routing: Express/Fastify ➡️ Axum

In TypeScript with Fastify/Express:

import express from 'express';
const app = express();

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

In Rust with Axum:

use axum::{routing::get, Json, Router};
use serde::Serialize;

#[derive(Serialize)]
struct HealthResponse {
    status: String,
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/health", get(health_check));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn health_check() -> Json<HealthResponse> {
    Json(HealthResponse {
        status: "ok".to_string(),
    })
}
Enter fullscreen mode Exit fullscreen mode

2. Validation: Zod ➡️ Serde + Validator

In TypeScript, zod parses and validates incoming payloads:

import { z } from 'zod';

const UserSchema = z.object({
  email: z.string().email(),
  age: z.number().min(18),
});
Enter fullscreen mode Exit fullscreen mode

In Rust, Serde deserializes the JSON structure, while validator enforces runtime constraints on the struct fields:

use serde::Deserialize;
use validator::Validate;

#[derive(Debug, Deserialize, Validate)]
struct UserPayload {
    #[validate(email)]
    email: String,
    #[validate(range(min = 18))]
    age: u32,
}

// Inside your request handler:
// payload.validate().map_err(...)
Enter fullscreen mode Exit fullscreen mode

3. Database Queries: Prisma ➡️ SQLx

Instead of complex ORM abstractions that generate inefficient SQL queries, Rust developers love SQLx because it checks your SQL statements against your actual database at compile time:

use sqlx::PgPool;

#[derive(sqlx::FromRow)]
struct User {
    id: i64,
    email: String,
}

async fn get_user(pool: &PgPool, user_id: i64) -> Result<User, sqlx::Error> {
    let user = sqlx::query_as!(
        User,
        "SELECT id, email FROM users WHERE id = $1",
        user_id
    )
    .fetch_one(pool)
    .await?;

    Ok(user)
}
Enter fullscreen mode Exit fullscreen mode

🤖 Automate package mapping in VS Code

Instead of searching crates.io manually every time you encounter a Node package during a migration, you can automate this using PackagePal.

It is a free VS Code extension that maps dependencies on hover:

  1. Open your TypeScript/JavaScript codebase.
  2. Set your target language to Rust in the status bar.
  3. Hover over any import (e.g., import axios from 'axios').
  4. View the top 3 equivalent Rust crates, usage code snippets, companion package suggestions, and official documentation links.

It supports 13 languages and runs on a private BYOK model (using your own free Gemini API key stored securely in VS Code).


What package mapping did you find hardest?

If you've ported TypeScript codebases to Rust, what npm package was the trickiest to replace? Drop a comment below! 👇

If you found this cheat sheet helpful, check out *PackagePal on the VS Code Marketplace** and checkout the website Website!*

Top comments (0)