Skip to content

Getting Started

This guide walks you through setting up Transit from scratch. By the end, you’ll be calling functions across JS, Rust, Java, and Python.

Before you begin, make sure you have:

Tool Why you need it How to check
Node.js >= 20 Runs your JS code node --version
bun Package manager (npm also works) bun --version
Rust For the scanner and Rust functions rustc --version
Java JDK 21+ For Java functions java --version
Python 3.10+ For Python functions python3 --version

To install transit, use:

Terminal window
bun install -g transit-cli # you can use npm too

If you only want to use Rust (no Java or Python), you only need Node.js, bun, and Rust.

Terminal window
mkdir my-transit-app
cd my-transit-app
bun init -y
bun install transit-cli

Transit can scan your project and create a config file for you:

Terminal window
transit init

transit init scans your source files for transit.rust(), transit.java(), and transit.python() calls, detects which languages you are using, and creates transit.config.json. It is idempotent — safe to run multiple times.

If you prefer to set things up manually, skip this step.

Let’s start with Rust since it has the best performance.

Create a file at rust/src/lib.rs:

rust/src/lib.rs
use napi_derive::napi;
#[napi]
pub fn greet(name: String) -> String {
format!("Hello from Rust, {}!", name)
}
#[napi]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

Create rust/Cargo.toml:

[package]
name = "my-rust-module"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
napi = { version = "3", features = ["serde-json"] }
napi-derive = "3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[build-dependencies]
napi-build = "3"

Create rust/build.rs (required by napi-rs):

fn main() {
napi_build::setup();
}

Build it:

Terminal window
cd rust
cargo build --release
cp target/release/libmy_rust_module.so index.node
cd ..

Create index.js:

import { transit } from "transit"
import { resolve } from "node:path"
const __dirname = import.meta.dirname
// Point Transit at the Rust directory
const rs = transit.rust(resolve(__dirname, "./rust"))
// Call the functions
const greeting = await rs.greet("World")
console.log(greeting) // "Hello from Rust, World!"
const sum = await rs.add(10, 20)
console.log(sum) // 30

Run it:

Terminal window
node index.js

You should see:

Hello from Rust, World!
30

Create java/src/main/java/com/example/App.java:

package com.example;
import transit.java.TransitServer;
public class App {
public String processJob(String argsJson) {
return "{\"output\": \"Java says: job received\"}";
}
public String getVersion(String argsJson) {
return "{\"version\": \"1.0.0\"}";
}
public static void main(String[] args) throws Exception {
TransitServer server = new TransitServer();
App app = new App();
server.registerFunction("processJob", app::processJob);
server.registerFunction("getVersion", app::getVersion);
server.start();
}
}

You need the Transit Java runtime (which provides TransitServer). Copy TransitServer.java into your project, or use the version from the Transit package:

Terminal window
# Build the Transit Java runtime
mkdir -p packages/transit-java-runtime/build
javac -d packages/transit-java-runtime/build \
node_modules/transit/packages/transit-java-runtime/src/main/java/transit/java/*.java
# Build your app
mkdir -p java/build
javac -cp packages/transit-java-runtime/build \
-d java/build \
java/src/main/java/com/example/*.java

Update your index.js:

import { transit } from "transit"
import { resolve } from "node:path"
const __dirname = import.meta.dirname
const rs = transit.rust(resolve(__dirname, "./rust"))
const jv = transit.java(resolve(__dirname, "./java/src/main/java"))
// Call Rust
console.log(await rs.greet("World"))
// Call Java
const jobResult = await jv.processJob({})
console.log(jobResult) // {"output": "Java says: job received"}
const version = await jv.getVersion({})
console.log(version) // {"version": "1.0.0"}

Transit needs a small Python server to handle communication with JavaScript. Copy it from the Transit package into your Python directory:

Terminal window
mkdir -p python
cp node_modules/transit/packages/transit-py-runtime/transit_server.py python/

Create python/service.py (or any name you like — Transit will find it):

import json
from transit_server import TransitServer, register_function
def process_data(args_json):
"""Process some data and return a result.
IMPORTANT: args_json is a JSON STRING, not a dictionary!
You must call json.loads() to parse it.
"""
args = json.loads(args_json)
items = args.get("items", [])
return json.dumps({
"output": f"Python processed {len(items)} items",
"processed": True
})
def get_stats(args_json):
"""Return some stats."""
return json.dumps({"status": "healthy", "language": "python"})
if __name__ == "__main__":
server = TransitServer()
register_function("processData", process_data)
register_function("getStats", get_stats)
server.start()
import { transit } from "transit"
import { resolve } from "node:path"
const __dirname = import.meta.dirname
const rs = transit.rust(resolve(__dirname, "./rust"))
const jv = transit.java(resolve(__dirname, "./java/src/main/java"))
const py = transit.python(resolve(__dirname, "./python"))
// Call all three languages
console.log(await rs.greet("Transit"))
console.log(await jv.processJob({}))
console.log(await py.processData({ items: [1, 2, 3] }))
transit.info()
// rust (./rust): 2 functions
// - greet [tier 1]
// - add [tier 1]
// java (./java/src/main/java): 2 functions
// - processJob [tier 1]
// - getVersion [tier 1]
// python (./python): 2 functions
// - processData [tier 1]
// - getStats [tier 1]

Transit has two modes:

Mode Command What happens
Dev mode node index.js or transit dev Uses a Proxy to dynamically dispatch function calls. Great for development — no build step needed.
Build mode transit build Generates typed TypeScript stubs and compiles native addons. Better for production — gives you type safety and faster startup.

In dev mode (the default), Transit uses a JavaScript Proxy to intercept function calls and route them to the right bridge. This means you can call functions immediately without any code generation.

In build mode, Transit generates explicit TypeScript functions for each discovered function. This gives you autocomplete, type checking, and slightly faster startup.

For most projects, dev mode is fine. Use build mode when you want type safety or are deploying to production.

Transit includes a CLI to help manage your project.

Terminal window
transit init

This scans your source files and creates a transit.config.json with your language directories.

Terminal window
transit dev

Watches for file changes and re-scans automatically. No restart needed when you add new functions.

Terminal window
transit build

Generates typed TypeScript stubs (transit.gen.ts) and compiles Rust/Java. Writes dist/transit-manifest.json.

Terminal window
transit start --entry index.js

Starts resident processes (Java, Python) and runs your app. Handles graceful shutdown on Ctrl+C.

You don’t need to register functions manually. Transit scans your code and finds them automatically:

Language What Transit finds
Rust #[napi] pub fn functions (Tier 1)
Java public methods with signature (String) -> String (Tier 1)
Python Top-level def functions (Tier 1)

If you have multiple files with functions of the same name, use the qualified form:

await rustTransit["lib"]["processJob"](job)
// or
await rustTransit.lib.processJob(job)

A typical Transit project looks like this:

my-project/
rust/
src/lib.rs # Your Rust functions
Cargo.toml
build.rs # Required for napi-rs
java/
src/main/java/... # Your Java functions
build/ # Compiled classes
python/
transit_server.py # Copied from Transit package
service.py # Your Python functions
index.js # Your JS entry point
transit.config.json # Generated by transit init

“Scanner not available” — Build the scanner first:

Terminal window
cd packages/transit-scanner
cargo build --release
cp target/release/libtransit_scanner.so index.node

“Python entry point not found” — Make sure your Python file is named one of: transit_service.py, service.py, main.py, app.py, server.py, or filters.py. Or specify a custom entry point:

const py = transit.python("./python", { serverScript: "my_file.py" })

Also make sure transit_server.py is in the same directory:

Terminal window
cp node_modules/transit/packages/transit-py-runtime/transit_server.py python/

“Java class not found” — Compile your Java code and specify the classpath:

const jv = transit.java("./java", {
classpath: resolve(__dirname, "./java/build"),
mainClass: "com.example.App"
})

Functions not showing up in transit.info() — Check that your functions match the expected signatures. Rust needs #[napi] and pub fn, Java needs public methods returning String, Python needs top-level def functions.

The first Python/Java call is slow — This is normal! The first call starts the Python/Java process. After that, all calls are fast because the process stays running.