esem-bridge
v0.1.5
Published
Import Python modules in JavaScript and call them directly.
Maintainers
Readme
esem
Import Python in JavaScript. No APIs needed.
import { python } from "esem-bridge";
const { predict } = await python("./model.py");
const result = await predict({ age: 22, country: "NG" });
console.log(result); // { score: 87, risk: "low" }No FastAPI. No Express endpoint. No subprocess boilerplate. No JSON over HTTP.
Just import and call.
The problem
Building with Python and JavaScript together is annoying.
Python is better for AI, ML, data processing, and scientific computing. JavaScript is better for frontends, Node.js backends, and real-time apps. But connecting both usually means:
- wrapping Python in FastAPI
- running a Python server separately
- creating HTTP endpoints
- serializing/deserializing manually
- deploying and monitoring two services
That's too much infrastructure for what should be a function call.
Install
npm install esem-bridgeRequires Node.js 18+ and Python 3.8+.
Usage
Option 1 — python() helper (recommended)
import { python } from "esem-bridge";
const tools = await python("./tools.py");
// Call functions
const result = await tools.add(2, 3); // 5
const msg = await tools.greet("Crane"); // "Hello, Crane!"
// Destructure
const { add, greet } = await python("./tools.py");Option 2 — python: import syntax
import tools from "python:./tools.py";
const result = await tools.add(2, 3);Requires running with the loader hook:
node --experimental-loader esem-bridge/loader yourfile.jsOr use the CLI after installing esem-bridge:
npx esem run yourfile.jsTo run the CLI without installing first:
npx --package esem-bridge esem run yourfile.jsExamples
AI/ML in a Node.js app
# model.py
def predict_score(user):
# your ML logic here
return { "score": 87, "risk": "low" }import { python } from "esem-bridge";
const { predict_score } = await python("./model.py");
const result = await predict_score({ age: 22, country: "NG" });
console.log(result); // { score: 87, risk: "low" }Next.js API route calling Python logic
// app/api/price/route.js
import { python } from "esem-bridge";
export async function POST(req) {
const body = await req.json();
const { calculatePrice } = await python("./pricing.py");
const price = await calculatePrice(body);
return Response.json({ price });
}Using Python classes
# calculator.py
class Calculator:
def __init__(self, precision=2):
self.precision = precision
def add(self, a, b):
return round(a + b, self.precision)const { Calculator } = await python("./calculator.py");
const calc = await Calculator(2); // instantiate with precision=2
const result = await calc.add(1.234, 2.345); // 3.58Object attributes can be read and written from JavaScript:
# user.py
class User:
def __init__(self):
self.name = "John"const { User } = await python("./user.py");
const user = await User();
console.log(await user.name); // "John"
user.name = "Mayowa";
console.log(await user.name); // "Mayowa"Using async Python functions
# async_tools.py
import asyncio
async def fetch_score(user_id):
await asyncio.sleep(0.1)
return { "user_id": user_id, "score": 87 }const { fetch_score } = await python("./async_tools.py");
const result = await fetch_score("user_123");
console.log(result); // { user_id: "user_123", score: 87 }Using installed Python packages
const { python } = await import("esem-bridge");
const np = await python("numpy"); // pip-installed packages work tooReading module constants
# config.py
VERSION = "1.0.0"
SETTINGS = {
"debug": True,
"ports": [3000, 3001],
}Module values are loaded lazily, so await them when reading:
const config = await python("./config.py");
console.log(await config.VERSION); // "1.0.0"
console.log(await config.SETTINGS); // { debug: true, ports: [3000, 3001] }Destructured module values are promises too:
const { VERSION } = await python("./config.py");
console.log(await VERSION);Error handling
Python errors cross the bridge cleanly:
# tools.py
def parse_data(raw):
if not raw:
raise ValueError("Input cannot be empty")
return process(raw)import { python, PythonError } from "esem-bridge";
const { parse_data } = await python("./tools.py");
try {
await parse_data(null);
} catch (err) {
console.log(err.message); // "Input cannot be empty"
console.log(err.pythonTraceback); // full Python traceback
console.log(err.name); // "PythonError(ValueError)"
}Type mappings
| Python | JavaScript |
|--------------|----------------|
| None | null |
| bool | boolean |
| int | number |
| float | number |
| str | string |
| list | Array |
| dict | object |
| class inst. | proxy object |
CLI
npx esem run index.js # run with python: import support
npx esem --version
npx esem --helpConfiguration
Set ESEM_PYTHON to use a specific Python binary (e.g. inside a venv):
ESEM_PYTHON=.venv/bin/python npx esem run index.jsWorker lifecycle
You do not need to call shutdown() in short scripts. esem automatically lets
Node.js exit after your Python calls finish.
In a long-running application, the Python worker remains available for later calls. You can still release it early when you know it is no longer needed:
import { shutdown } from "esem-bridge";
shutdown();How it works
esem spawns a Python worker process when you first call python(). The same worker is reused while your Node.js application is running, so there is no cold start on every call. When no Python calls are active, the worker does not prevent Node.js from exiting naturally.
Communication is JSON-RPC over stdin/stdout. When you call a proxied function, a message goes to the worker, Python executes it, and the result comes back serialized. The round-trip is microseconds on local processes.
JS runtime
→ python() call
→ bridge spawns Python worker (once)
→ JSON-RPC over stdin/stdout
→ Python loads module, runs function
→ result serialized back to JS
→ await resolvesAnything your Python code prints goes to stderr, prefixed with [python], so it never interferes with the bridge.
Object instances (classes) live in Python. JS holds a reference ID. When you call methods on the proxy, they route to the real Python object.
What's next
- Python importing JavaScript
- TypeScript type generation from Python type hints
- NumPy array optimization (zero-copy)
- Pandas DataFrame support
- Bun support
- VS Code extension
License
MIT
