This library is based on https://github.com/tjanczuk/edge all credit for original work goes to Tomasz Janczuk.
Edge.js allows you to run Node.js and .NET code in one process on Windows, macOS, and Linux
You can call .NET functions from Node.js and Node.js functions from .NET.
Edge.js takes care of marshaling data between CLR and V8. Edge.js also reconciles threading models of single-threaded V8 and multi-threaded CLR.
Edge.js ensures correct lifetime of objects on V8 and CLR heaps.
The CLR code can be pre-compiled or specified as C#, F#, Python (IronPython), or PowerShell source: Edge.js can compile CLR scripts at runtime.
Edge can be extended to support other CLR languages or DSLs.
Edge.js provides an asynchronous, in-process mechanism for interoperability between Node.js and .NET. You can use this mechanism to:
- script Node.js from a .NET application on Windows using .NET Framework
- script C# from a Node.js application on Windows, macOS, and Linux using .NET Framework/.NET Core
- use CLR multi-threading from Node.js for CPU intensive work more...
- write native extensions to Node.js in C# instead of C/C++
- integrate existing .NET components into Node.js applications
- access MS SQL from Node.js using ADO.NET
- script F# from Node.js
- script Powershel from Node.js
- script Python (IronPython) from Node.js
Read more about the background and motivations of the project here.
- Support for new versions of Node.Js.
- Support for .NET Core 3.1 - 9.x on Windows/Linux/macOS.
- Fixes AccessViolationException when running Node.js code from C# PR #573.
- Fixes StackOverflowException PR #566 that occurs when underlying C# code throws complex exception.
- Fixes issues #469, #713
- Other PRs: PR #725, PR #640
- Multiple bug fixes and improvements to the original code.
NPM package edge-js
NuGet package EdgeJs
For use with Electron electron-edge-js
VS Code uses Electron shell, to write extensions for it using Edge.js use
electron-edge-js
Sample app that shows how to work with .NET Core using inline code and compiled C# libraries.
https://github.com/agracio/edge-js-quick-start
- Windows: Visual C++ Redistributable
- Windows supports 4 latest LTS or candidate LTS releases (even numbered).
- Windows supports up to 1 "Current" (odd numbered) release and drops it when superseeded by new LTS candidate.
- macOS comes precompiled with same releases as Windows. When using Node.js version that is not pre-compiled
edge-jsbinaries will be compiled duringnpm installusingnode-gyp. - Linux will always compile
edge-jsbinaries duringnpm installusingnode-gyp.
| Version | x86 | x64 | arm64 |
|---|---|---|---|
| 18.x | ✔️ | ✔️ | ❌ |
| 20.x | ✔️ | ✔️ | ✔️ |
| 22.x | ✔️ | ✔️ | ✔️ |
| 24.x | ❌ | ✔️ | ✔️ |
| 25.x | ❌ | ✔️ | ✔️ |
| Version | x64 | arm64 |
|---|---|---|
| 18.x | ✔️ | ✔️ |
| 20.x | ✔️ | ✔️ |
| 22.x | ✔️ | ✔️ |
| 24.x | ✔️ | ✔️ |
| 25.x | ✔️ | ✔️ |
| Version | x64 | arm64 |
|---|---|---|
| 16.x - 25.x | ✔️ | ✔️ |
| Version | x64 | arm64 |
|---|---|---|
| 16.x - 25.x | ✔️ | ✔️ |
Other Linux architectures might work but have not been tested.
| Script CLR from Node.js | Script Node.js from CLR | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Mono is no longer actively supported. Existing code will remain In Edge.Js but focus will be on .NET Core.
* Edge.js does not have a flag to force it to run in Mono environment on Windows, it would only be possible to run if you have Windows without .NET Framework present.
* On Linux there is a known bug that will throw exception when executing C# code under certain conditions. Use one of the following options.
- Set
LD_PRELOADenv variable before running Edge.js with Mono
EXPORT LD_PRELOAD="libmono-2.0.so libmonosgen-2.0.so libstdc++.so.6"- Set
LD_PRELOADin your Node.js app entry point using Javascript
Object.assign(process.env, {
// Work around Mono problem: undefined symbol: mono_add_internal_call_with_flags
LD_PRELOAD: 'libmono-2.0.so libmonosgen-2.0.so libstdc++.so.6',
});Source: mono/mono#17079 (comment)
Edge.js uses this code in test setup:
Lines 19 to 24 in 8bab03a
When packaging your application using Webpack make sure that edge-js is specified as external module.
module.exports = {
target: 'node',
externals: {
'edge-js': 'commonjs2 edge-js',
'edge-cs': 'commonjs2 edge-cs',
},
node: {
__dirname: true,
__filename: true,
},
}next.config.js
const nextConfig = {
serverExternalPackages: ['edge-js'],
}
module.exports = nextConfignext.config.ts
const nextConfig: NextConfig = {
serverExternalPackages: ['edge-js'],
};
export default nextConfig;Provides simple access to SQL without the need to write separate C# code.
| Framework | Platform | NPM Package | Language code | Documentation |
|---|---|---|---|---|
| .NET 4.5 | Windows | edge-sql |
sql |
Script SQL in Node.js 🔗 |
| CoreCLR | Any | edge-sql |
sql |
Script SQL in Node.js 🔗 |
NOTE This functionality requires IronPython 3.4
| Framework | Platform | NPM Package | Language code | Documentation |
|---|---|---|---|---|
| .NET 4.5 | Windows | edge-py |
py |
Script Python in Node.js 🔗 |
| CoreCLR | Any | edge-py |
py |
Script Python in Node.js 🔗 |
NOTE CoreCLR requires dotnet 8
| Framework | Platform | NPM Package | Language code | Documentation |
|---|---|---|---|---|
| .NET 4.5 | Windows | edge-ps |
ps |
Script PowerShell in Node.js 🔗 |
| CoreCLR | Windows | edge-ps |
ps |
Script PowerShell in Node.js 🔗 |
| Framework | Platform | NPM Package | Language code | Documentation |
|---|---|---|---|---|
| .NET 4.5 | Windows | edge-fs |
fs |
Script F# in Node.js 🔗 |
| CoreCLR | Windows | edge-fs |
fs |
Script F# in Node.js 🔗 |
Scripting CLR from Node.js - full documentation
Scripting Node.js from CLR - full documentation
Scripting CLR from Node.js sample app https://github.com/agracio/edge-js-quick-start
- Inline C# code
- Using compiled dll
- Using CoreCLR
- Executing synchronously without function callback
- Using promises/async
- Scripting Node.js from CLR
- Docker
var edge = require('edge-js');
var helloWorld = edge.func(function () {/*
async (input) => {
return ".NET Welcomes " + input.ToString();
}
*/});
helloWorld('JavaScript', function (error, result) {
if (error) throw error;
console.log(result);
});var edge = require('edge-js');
var helloWorld = edge.func(`
async (input) => {
return ".NET Welcomes " + input.ToString();
}
`);
helloWorld('JavaScript', function (error, result) {
if (error) throw error;
console.log(result);
});var edge = require('edge-js');
var helloWorld = edge.func(function () {/*
async (dynamic input) => {
return "Welcome " + input.name + " " + input.surname;
}
*/});
helloWorld({name: 'John', surname: 'Smith'}, function (error, result) {
if (error) throw error;
console.log(result);
});var getPerson = edge.func({
source: function () {/*
using System.Threading.Tasks;
using System;
public class Person
{
public Person(string name, string email, int age)
{
Id = Guid.NewGuid();
Name = name;
Email = email;
Age = age;
}
public Guid Id {get;set;}
public string Name {get;set;}
public string Email {get;set;}
public int Age {get;set;}
}
public class Startup
{
public async Task<object> Invoke(dynamic input)
{
return new Person(input.name, input.email, input.age);
}
}
*/}
});
getPerson({name: 'John Smith', email: 'john.smith@myemailprovider', age: 35}, function(error, result) {
if (error) throw error;
console.log(result);
});When using inline C# class code must include
public class Startup
{
public async Task<object> Invoke(object|dynamic input)
{
// code
// return results
}
}// People.cs
using System;
namespace People
{
public class Person
{
public Person(string name, string email, int age)
{
Id = Guid.NewGuid();
Name = name;
Email = email;
Age = age;
}
public Guid Id {get;}
public string Name {get;}
public string Email {get;}
public int Age {get;}
}
}
// EdgeJsMethods.cs
using System.Threading.Tasks;
using People;
namespace EdgeJsMethods
{
class Methods
{
public async Task<object> GetPerson(dynamic input)
{
return await Task.Run(() => new Person(input.name, input.email, input.age));
}
}
}var edge = require('edge-js');
var getPerson = edge.func({
assemblyFile: myDll, // path to compiled .dll
typeName: 'EdgeJsMethods.Methods',
methodName: 'GetPerson'
});
getPerson({name: 'John Smith', email: 'john.smith@myemailprovider', age: 35}, function(error, result) {
if (error) throw error;
console.log(result);
});public async Task<object> MyMethod(object|dynamic input)
{
//return results sync/async;
}- If not set Edge.js will run as .NET 4.5 on Windows.
- On macOS and Linux Edge.js will default to Mono if it is installed otherwise will run as CoreCLR.
- Can be set using
jscode below or as an environment variableSET EDGE_USE_CORECLR=1orEXPORT EDGE_USE_CORECLR=1depending on your platform. - Must be set before
var edge = require('edge-js');
// set this variable before
// var edge = require('edge-js');
process.env.EDGE_USE_CORECLR=1
var edge = require('edge-js');
var helloWorld = edge.func(function () {/*
async (input) => {
return ".NET Welcomes " + input.ToString();
}
*/});
helloWorld('JavaScript', function (error, result) {
if (error) throw error;
console.log(result);
});If your C# implementation will complete synchronously you can call this function as any synchronous JavaScript function as follows:
var edge = require('edge-js');
var helloWorld = edge.func(function () {/*
async (input) => {
return ".NET Welcomes " + input.ToString();
}
*/});
var result = helloWorld('JavaScript', true);Calling C# asynchronous implementation as a synchronous JavaScript function will fail
var edge = require('edge-js');
var helloWorld = edge.func(function () {/*
async (input) => {
return await Task.Run(() => ".NET Welcomes " + input.ToString());
}
*/});
// sync call will throw exception
var result = helloWorld('JavaScript', true);var func = edge.func(function () {/*
async (dynamic input) => {
return "Welcome " + input.name + " " + input.surname;
}
*/});
function helloWorld(){
return new Promise((resolve, reject) =>{
func({name: 'John', surname: 'Smith'}, function (error, result) {
if(error) reject(error);
else resolve(result);
});
});
}using System;
using System.Threading.Tasks;
using EdgeJs;
class Program
{
public static async Task Start()
{
var func = Edge.Func(@"
return function (data, callback) {
callback(null, 'Node.js welcomes ' + data);
}
");
Console.WriteLine(await func(".NET"));
}
static void Main(string[] args)
{
Start().Wait();
}
}More examples in tests DoubleEdge.cs
Dockerfile: Dockerfile
Docker Hub image: agracio/ubuntu-node-netcore
- Based on Ununtu 24.04
- User directory
devvol
- Node.js 22
- dotnet 9
- git
- build tools
- sudo, curl, wget
- node-gyp
- Run interactive starting in
devvol, setEDGE_USE_CORECLR=1at container level - Git clone
edge-jsand enter cloned repo directory npm install- Run tests
docker run -w /devvol -e EDGE_USE_CORECLR=1 -it agracio/ubuntu-node-netcore:latest
git clone https://github.com/agracio/edge-js.git && cd edge-js
npm i
npm test
Ah, whatever problem you have. If you have this problem, this solves it.
--Scott Hanselman (@shanselman)
Read the Edge.js introduction on InfoQ.
Listen to the Edge.js podcast on Herdingcode.
Scripting CLR from Node.js
What you need
Windows
Linux
OSX
How to: C# hello, world
How to: integrate C# code into Node.js code
How to: specify additional CLR assembly references in C# code
How to: marshal data between C# and Node.js
How to: call Node.js from C#
How to: export C# function to Node.js
How to: script Python in a Node.js application
How to: script PowerShell in a Node.js application
How to: script F# in a Node.js application
How to: script Lisp in a Node.js application
How to: script T-SQL in a Node.js application
How to: support for other CLR languages
How to: exceptions
How to: app.config
How to: debugging
Performance
Building on Windows
Building on OSX
Building on Linux
Running tests
Scripting Node.js from CLR
What you need
How to: Node.js hello, world
How to: integrate Node.js into CLR code
How to: use Node.js built-in modules
How to: use external Node.js modules
How to: handle Node.js events in .NET
How to: expose Node.js state to .NET
How to: use Node.js in ASP.NET application
How to: debug Node.js code running in a CLR application
Building Edge.js NuGet package
Running tests of scripting Node.js in C#
Use cases and other resources
Contribution and derived work
If you are writing a Node.js application, this section explains how you include and run CLR code in your app. It works on Windows, MacOS, and Linux.
Edge.js runs on Windows, Linux, and OSX and requires supported version of Node.js 8.x, 7.x, 6.x, as well as .NET Framework 4.5 (Windows), Mono 4.2.4 (OSX, Linux), or .NET Core 1.0.0 Preview 2 (Windows, OSX, Linux).
NOTE there is a known issue with Mono after 4.2.4 that will be addressed in Mono 4.6.
- Supported Node.js version
- .NET 4.6.2 and/or .NET Core
- Visual C++ Redistributable
- to use Python, you also need IronPython 3.4 or later
- to use F#, read Dave Thomas blog post
If you have both desktop CLR and .NET Core installed, read using .NET Core for how to configure Edge to use one or the other.
- Supported Node.js version
- Mono and/or .NET Core or Mono
- Follow Linux setup instructions
- Supported Node.js version
- Mono and/or .NET Core
- Follow OSX setup instructions
Follow setup instructions for your platform.
Install edge:
npm install edge-js
In your server.js:
var edge = require('edge-js');
var helloWorld = edge.func(function () {/*
async (input) => {
return ".NET Welcomes " + input.ToString();
}
*/});
helloWorld('JavaScript', function (error, result) {
if (error) throw error;
console.log(result);
});Run and enjoy:
$>node server.js
.NET welcomes JavaScript
If you want to use .NET Core as your runtime and are running in a dual runtime environment (i.e. Windows with .NET 4.5 installed as well or Linux with Mono installed), you will need to tell edge to use .NET Core by setting the EDGE_USE_CORECLR environment variable:
$>EDGE_USE_CORECLR=1 node server.js
.NET Welcomes JavaScript
Edge provides several ways to integrate C# code into a Node.js application. Regardless of the way you choose, the entry point into the .NET code is normalized to a Func<object,Task<object>> delegate. This allows Node.js code to call .NET asynchronously and avoid blocking the Node.js event loop.
Edge provides a function that accepts a reference to C# code in one of the supported representations, and returns a Node.js function which acts as a JavaScript proxy to the Func<object,Task<object>> .NET delegate:
var edge = require('edge-js');
var myFunction = edge.func(...);The function proxy can then be called from Node.js like any asynchronous function:
myFunction('Some input', function (error, result) {
//...
});Alternatively, if you know the C# implementation will complete synchronously given the circumstances, you can call this function as any synchronous JavaScript function as follows:
var result = myFunction('Some input', true);The true parameter instead of a callback indicates that Node.js expects the C# implementation to complete synchronously. If the CLR function implementation does not complete synchronously, the call above will result in an exception.
One representation of CLR code that Edge.js accepts is C# source code. You can embed C# literal representing a .NET async lambda expression implementing the Func<object, Task<object>> delegate directly inside Node.js code:
var add7 = edge.func('async (input) => { return (int)input + 7; }');In another representation, you can embed multi-line C# source code by providing a function with a body containing a multi-line comment. Edge extracts the C# code from the function body using regular expressions:
var add7 = edge.func(function() {/*
async (input) => {
return (int)input + 7;
}
*/});Or if you use ES6 you can use template strings to define a multiline string:
var add7 = edge.func(`
async (input) => {
return (int)input + 7;
}
`);If your C# code is more involved than a simple lambda, you can specify entire class definition. By convention, the class must be named Startup and it must have an Invoke method that matches the Func<object,Task<object>> delegate signature. This method is useful if you need to factor your code into multiple methods:
var add7 = edge.func(function() {/*
using System.Threading.Tasks;
public class Startup
{
public async Task<object> Invoke(object input)
{
int v = (int)input;
return Helper.AddSeven(v);
}
}
static class Helper
{
public static int AddSeven(int v)
{
return v + 7;
}
}
*/});If your C# code grows substantially, it is useful to keep it in a separate file. You can save it to a file with *.csx or *.cs extension, and then reference from your Node.js application:
var add7 = edge.func(require('path').join(__dirname, 'add7.csx'));If you integrate C# code into your Node.js application by specifying C# source using one of the methods above, edge will compile the code on the fly. If you prefer to pre-compile your C# sources to a CLR assembly, or if your C# component is already pre-compiled, you can reference a CLR assembly from your Node.js code. In the most generic form, you can specify the assembly file name, the type name, and the method name when creating a Node.js proxy to a .NET method:
var clrMethod = edge.func({
assemblyFile: 'My.Edge.Samples.dll',
typeName: 'Samples.FooBar.MyType',
methodName: 'MyMethod' // This must be Func<object,Task<object>>
});If you don't specify methodName, Invoke is assumed. If you don't specify typeName, a type name is constructed by assuming the class called Startup in the namespace equal to the assembly file name (without the .dll). In the example above, if typeName was not specified, it would default to My.Edge.Samples.Startup.
The assemblyFile is relative to the working directory. If you want to locate your assembly in a fixed location relative to your Node.js application, it is useful to construct the assemblyFile using __dirname. If you are using .NET Core, assemblyFile can also be a project name or NuGet package name that is specified in your project.json or .deps.json dependency manifest.
You can also create Node.js proxies to .NET functions specifying just the assembly name as a parameter:
var clrMethod = edge.func('My.Edge.Samples.dll');In that case the default typeName of My.Edge.Samples.Startup and methodName of Invoke is assumed as explained above.
When you provide C# source code and let edge compile it for you at runtime, edge will by default reference only mscorlib.dll and System.dll assemblies. If you're using .NET Core, we automatically reference the most recent versions of the System. Runtime, System.Threading.Tasks, and the compiler language packages, like Microsoft.CSharp. In applications that require additional assemblies you can specify them in C# code using a special hash pattern, similar to Roslyn. For example, to use ADO.NET you must reference System.Data.dll:
var add7 = edge.func(function() {/*
#r "System.Data.dll"
using System.Data;
using System.Threading.Tasks;
public class Startup
{
public async Task<object> Invoke(object input)
{
// ...
}
}
*/});If you prefer, instead of using comments you can specify references by providing options to the edge.func call:
var add7 = edge.func({
source: function() {/*
using System.Data;
using System.Threading.Tasks;
public class Startup
{
public async Task<object> Invoke(object input)
{
// ...
}
}
*/},
references: [ 'System.Data.dll' ]
});If you are using .NET Core and are using the .NET Core SDK and CLI, you must have a project.json file (specification here) that specifies the dependencies for the application. This list of dependencies must also include the