Getting Started
Welcome! This guide walks you through using Transit step by step. Transit lets your JavaScript code call functions written in Rust, Python, and Java — like they are all one language.
What is Transit?
Section titled “What is Transit?”Imagine you have three friends who speak different languages: one speaks Rust (fast at math), one speaks Python (great at data tasks), and one speaks Java (handles big jobs). Transit is a translator that lets you — the JavaScript speaker — talk to all three at once, seamlessly.
You write your code in JavaScript. When you need something fast, you call a Rust function. When you need something simple, you call a Python function. Transit handles all the communication behind the scenes.
Before You Start
Section titled “Before You Start”You will need some tools installed on your computer. Here is how to check:
| Tool | What it does | How to check if you have it |
|---|---|---|
| Node.js (version 20 or higher) | Runs JavaScript | Open a terminal and type node --version |
| bun or npm | Installs packages | Type bun --version or npm --version |
| Rust | Only if you want Rust functions | Type rustc --version |
| Python 3.10+ | Only if you want Python functions | Type python3 --version |
| Java JDK 21+ | Only if you want Java functions | Type java --version |
| C/C++ compiler (GCC, Clang, or MSVC) | Only if you want C/C++ functions | Type gcc --version or g++ --version |
If you only want Rust and Python, you can skip Java and C/C++ entirely. Transit only loads what you ask it to.
Step 0: Initialize Your Project (Optional)
Section titled “Step 0: Initialize Your Project (Optional)”Transit can scan your project and create a config file for you:
mkdir my-transit-appcd my-transit-appbun init -ybun install transitnpx transit inittransit init scans your project for transit.rust(), transit.java(), transit.python(), transit.c(), and transit.cpp() 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 and continue below.
Step 1: Create Your Project
Section titled “Step 1: Create Your Project”If you did not run transit init, set up manually:
mkdir my-transit-appcd my-transit-appbun init -ybun install transitStep 2: Your First Rust Function
Section titled “Step 2: Your First Rust Function”Rust is great for fast, safe code. Let us create a simple Rust function.
Create the Rust project
Section titled “Create the Rust project”First, make the folders and files:
mkdir -p rust/srcNow create the file rust/Cargo.toml (this tells Rust how to build your code). Put this inside:
[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 = "2"Create rust/build.rs (required by napi-rs):
fn main() { napi_build::setup();}Important: The
crate-type = ["cdylib", "lib"]andbuild.rsare required for the Rust bridge to work. Without them, your functions will be discovered by the scanner but will fail at runtime because no native addon is created.
Write the Rust function
Section titled “Write the Rust function”Create the file rust/src/lib.rs and put this inside:
use napi_derive::napi;
// This function takes a name and returns a greeting#[napi]pub fn greet(name: String) -> String { format!("Hello from Rust, {}!", name)}
// This function adds two numbers#[napi]pub fn add(a: i32, b: i32) -> i32 { a + b}What this does:
#[napi]tells Transit “please export this function so JavaScript can call it” — this is required, not justpub fnpub fnmeans “this is a public function”- The function takes inputs and returns a result, just like any normal function
Note: Without
#[napi], the scanner will find yourpub fnfunctions, but the Rust bridge will not be able to call them at runtime. Always use#[napi].
Build the Rust code
Section titled “Build the Rust code”cd rustcargo build --releaseThis compiles your Rust code. It will take a minute the first time.
When it finishes, you need to copy the result so Transit can find it:
cp target/release/libmy_rust_module.so index.nodecd ..Important: On Mac, use cp target/release/libmy_rust_module.dylib index.node instead.
Call it from JavaScript
Section titled “Call it from JavaScript”Create the file index.js in your project root:
import { transit } from "transit";import { resolve } from "node:path";
const __dirname = import.meta.dirname;
// Tell Transit where your Rust code livesconst rs = transit.rust(resolve(__dirname, "./rust"));
// Call the Rust functions — it feels like calling any JavaScript function!const greeting = await rs.greet("World");console.log(greeting); // "Hello from Rust, World!"
const sum = await rs.add(10, 20);console.log(sum); // 30What this does:
transit.rust(...)scans your Rust directory and finds the functions you exportedrs.greet("World")calls your Rust function from JavaScript — Transit handles all the communicationawaitis needed because the first call loads the Rust code (subsequent calls are instant)
Run it!
Section titled “Run it!”node index.jsYou should see:
Hello from Rust, World!30Congratulations! You just called Rust from JavaScript!
Step 3: Add Python Functions
Section titled “Step 3: Add Python Functions”Python is great for data processing and simple scripts. Let us add some Python functions.
Copy the Transit server into your project
Section titled “Copy the Transit server into your project”Transit automatically copies transit_server.py from the Transit package into your Python directory on first use. You can also copy it manually if needed:
mkdir -p pythoncp node_modules/@sabeeirsharrma/py-runtime/transit_server.py python/Note: Transit handles this automatically — the manual copy step is optional.
Create the Python file
Section titled “Create the Python file”Create the file python/service.py (or any name you like — Transit will find it):
import jsonfrom 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()What this does:
- Each function takes a JSON string (not a dictionary!) and returns a JSON string
json.loads(args_json)parses the string into a dictionary you can work withregister_function("processData", process_data)tells Transit “when JavaScript callsprocessData, run myprocess_datafunction”- The names do not have to match — you can call the JavaScript name anything you want
server.start()starts a tiny server that listens for JavaScript requests
Common mistake: Writing
def process_data(args):and callingargs.get("items")directly. This will fail becauseargsis a JSON string, not a dictionary. Always usejson.loads(args)first.
Call from JavaScript
Section titled “Call from JavaScript”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 py = transit.python(resolve(__dirname, "./python"));
// Call Rustconsole.log(await rs.greet("Transit"));
// Call Pythonconst result = await py.processData({ items: [1, 2, 3] });console.log(result); // {"output": "Python processed 3 items", "processed": true}Run it:
node index.jsNote: The first time you call a Python function, Transit starts a Python process in the background. This takes a moment. After that, all calls are fast.
Custom entry point
Section titled “Custom entry point”If your Python file is not named service.py, main.py, app.py, server.py, or transit_service.py, tell Transit which file to use:
const py = transit.python(resolve(__dirname, "./python"), { serverScript: "filters.py" });Transit looks for entry points in this order: transit_service.py, service.py, main.py, app.py, server.py, filters.py.
Step 4: Add Java Functions
Section titled “Step 4: Add Java Functions”Java is good for complex business logic. Let us add some Java.
Create the Java file
Section titled “Create the Java file”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 processed the job\"}"; }
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(); }}What this does:
- Each Java method takes a
String(JSON) and returns aString(JSON) server.registerFunction("processJob", app::processJob)links the JavaScript name to the Java method- The
mainmethod starts the Java server
Build the Java code
Section titled “Build the Java code”You need to compile the Java code. This requires the Java Development Kit (JDK). Transit automatically copies the Java runtime sources (TransitServer.java, TransitService.java, BinaryProtocol.java) from the npm package into your project on first use:
mkdir -p java/build
# Build the Transit Java runtime (auto-copied by Transit, or copy manually)javac -d java/build java/lib/transit/java/*.java
# Build your appjavac -cp java/build -d java/build java/src/main/java/com/example/*.javaCall from JavaScript
Section titled “Call from JavaScript”Update 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"));const py = transit.python(resolve(__dirname, "./python"));
// Call all three languages — they all feel the same from JavaScript!console.log(await rs.greet("World"));console.log(await jv.processJob({}));console.log(await py.processData({ items: [1, 2, 3] }));Custom classpath
Section titled “Custom classpath”If Transit cannot find your compiled Java classes, specify the classpath and main class explicitly:
const jv = transit.java(resolve(__dirname, "./java"), { classpath: resolve(__dirname, "./java/build"), mainClass: "com.example.App"});Step 5: See What Transit Found
Section titled “Step 5: See What Transit Found”Add this to your index.js:
transit.info();This prints every function Transit discovered:
rust (./rust): 2 functions - greet [tier 1] (pub fn greet(name: String) -> String) - add [tier 1] (pub fn add(a: i32, b: i32) -> i32)python (./python): 2 functions - processData [tier 1] (def process_data(args_json)) - getStats [tier 1] (def get_stats(args_json))java (./java/src/main/java): 2 functions - processJob [tier 1] (public String processJob(String argsJson)) - getVersion [tier 1] (public String getVersion(String argsJson))Dev Mode vs Build Mode
Section titled “Dev Mode vs Build Mode”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.
How Function Discovery Works
Section titled “How Function Discovery Works”You do not need to do anything special to export functions. Transit finds them automatically:
- Rust: Any function with
#[napi]andpub fnis found - Python: Any top-level
deffunction (not starting with_) is found - Java: Any
publicmethod is found - C: Any function matching the generated
transit_c_glue.gen.hsignatures is found - C++: Any function matching the generated
transit_cpp_glue.gen.hsignatures is found - JavaScript: Any
export functionis found
If you have two files with the same function name, use this syntax:
await rs["lib"]["process"](data) // calls lib.rs's processawait rs["utils"]["process"](data) // calls utils.rs's processFile Layout
Section titled “File Layout”Here is what a typical Transit project looks like:
my-project/ rust/ src/lib.rs # Your Rust functions Cargo.toml build.rs # Required for napi-rs python/ transit_server.py # Copied from Transit package service.py # Your Python functions java/ src/main/java/... # Your Java functions build/ # Compiled classes c/ src/addon.c # Your C functions binding.gyp # node-gyp build config cpp/ src/addon.cpp # Your C++ functions binding.gyp # node-gyp build config index.js # Your JavaScript entry point transit.config.json # Optional: transit init creates thisTroubleshooting
Section titled “Troubleshooting”“Scanner not available” or “native addon not built”
The scanner is the tool that finds your functions. Build it first:
cd node_modules/@sabeeirsharrma/scannercargo build --releasecp target/release/libtransit_scanner.so index.nodecd ../..On Mac: cp target/release/libtransit_scanner.dylib index.node
Bun users: If you see this error with Bun, you may need to create a symlink:
ln -sf transit-scanner.node node_modules/@sabeeirsharrma/scanner/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. Transit auto-copies it from the Transit package, but you can also copy it manually:
cp node_modules/@sabeeirsharrma/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
Check that your functions match these rules:
- Rust: must have
#[napi]annotation AND bepub fn(not justpub fnalone) - Python: must be
defat the top level (not inside a class unless you wantClassName.methodstyle) - Java: must be
publicmethods - JavaScript: must be
export function
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.
“Port already in use”
If you see this error, another Transit process might be running. Kill it:
pkill -f "transit_service.py"Next Steps
Section titled “Next Steps”- How Export Tiers Work — learn about the three levels of function visibility
- API Reference — full list of Transit functions
- Binary Protocol — how Transit communicates between languages (advanced, for contributors)
- Architecture — how Transit is built (advanced, for contributors)