skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
dchuk/claude-code-tauri-skills137 installs

running-nodejs-sidecar-in-tauri

Guides users through running Node.js as a sidecar process in Tauri applications, enabling JavaScript backend functionality without requiring end-user Node.js installations.

How do I install this agent skill?

npx skills add https://github.com/dchuk/claude-code-tauri-skills --skill running-nodejs-sidecar-in-tauri
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    This skill provides a comprehensive guide for integrating Node.js sidecars into Tauri applications. It follows official patterns for the Tauri Shell plugin and includes specific security recommendations, such as using argument validators and binding local servers to localhost. No malicious patterns or security risks were identified.

  • Socketpass

    No alerts

  • Snykpass

    Risk: LOW · No issues

  • Runlayerfail

    1/1 file flagged

What does this agent skill do?

Running Node.js as a Sidecar in Tauri

Package and run Node.js applications as sidecar processes in Tauri desktop applications, leveraging the Node.js ecosystem without requiring users to install Node.js.

Why Use a Node.js Sidecar

  • Bundle existing Node.js tools and libraries with your Tauri application
  • No external Node.js runtime dependency for end users
  • Leverage npm packages that have no Rust equivalent
  • Isolate Node.js logic from the main Tauri process
  • Cross-platform support (Windows, macOS, Linux)

Prerequisites

  1. Existing Tauri v2 application
  2. Shell plugin installed and configured
  3. Node.js and npm on the development machine
  4. Rust toolchain (1.84.0+ recommended)

Install the Shell Plugin

npm install @tauri-apps/plugin-shell
cargo add tauri-plugin-shell --manifest-path src-tauri/Cargo.toml

Register in src-tauri/src/lib.rs:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Project Structure

my-tauri-app/
├── package.json
├── src-tauri/
│   ├── binaries/
│   │   └── my-sidecar-<target-triple>[.exe]
│   ├── capabilities/default.json
│   ├── tauri.conf.json
│   └── src/lib.rs
├── sidecar/
│   ├── package.json
│   ├── index.js
│   └── rename.js
└── src/

Step-by-Step Setup

1. Create the Sidecar Directory

mkdir sidecar && cd sidecar
npm init -y
npm add @yao-pkg/pkg --save-dev

2. Write Sidecar Logic

Create sidecar/index.js:

const command = process.argv[2];
const args = process.argv.slice(3);

switch (command) {
  case 'hello':
    console.log(`Hello ${args[0] || 'World'}!`);
    break;
  case 'add':
    const [a, b] = args.map(Number);
    if (isNaN(a) || isNaN(b)) {
      console.error('Error: Both arguments must be numbers');
      process.exit(1);
    }
    console.log(JSON.stringify({ result: a + b }));
    break;
  default:
    console.error(`Unknown command: ${command}`);
    process.exit(1);
}

3. Create the Rename Script

Create sidecar/rename.js:

import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';

const ext = process.platform === 'win32' ? '.exe' : '';

let targetTriple;
try {
  targetTriple = execSync('rustc --print host-tuple').toString().trim();
} catch {
  const rustInfo = execSync('rustc -vV').toString();
  const match = rustInfo.match(/host: (.+)/);
  targetTriple = match ? match[1] : null;
  if (!targetTriple) {
    console.error('Could not determine Rust target triple');
    process.exit(1);
  }
}

const destDir = path.join('..', 'src-tauri', 'binaries');
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });

fs.renameSync(`my-sidecar${ext}`, path.join(destDir, `my-sidecar-${targetTriple}${ext}`));

4. Configure Build Scripts

Update sidecar/package.json:

{
  "name": "my-sidecar",
  "type": "module",
  "scripts": {
    "build": "pkg index.js --output my-sidecar --targets node18",
    "postbuild": "node rename.js"
  },
  "devDependencies": {
    "@yao-pkg/pkg": "^5.0.0"
  }
}

5. Configure Tauri

Add to src-tauri/tauri.conf.json:

{
  "bundle": {
    "externalBin": ["binaries/my-sidecar"]
  }
}

6. Configure Permissions

Update src-tauri/capabilities/default.json:

{
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    {
      "identifier": "shell:allow-execute",
      "allow": [{
        "args": true,
        "name": "binaries/my-sidecar",
        "sidecar": true
      }]
    }
  ]
}

Restrict arguments for security:

{
  "identifier": "shell:allow-execute",
  "allow": [
    {
      "args": ["hello", { "validator": "\\w+" }],
      "name": "binaries/my-sidecar",
      "sidecar": true
    }
  ]
}

Communication Patterns

Frontend to Sidecar (TypeScript)

import { Command } from '@tauri-apps/plugin-shell';

async function sayHello(name: string): Promise<string> {
  const command = Command.sidecar('binaries/my-sidecar', ['hello', name]);
  const output = await command.execute();
  if (output.code !== 0) throw new Error(output.stderr);
  return output.stdout.trim();
}

async function addNumbers(a: number, b: number): Promise<number> {
  const command = Command.sidecar('binaries/my-sidecar', ['add', String(a), String(b)]);
  const output = await command.execute();
  if (output.code !== 0) throw new Error(output.stderr);
  return JSON.parse(output.stdout).result;
}

Backend to Sidecar (Rust)

use tauri_plugin_shell::ShellExt;

#[tauri::command]
async fn call_sidecar(
    app: tauri::AppHandle,
    command: String,
    args: Vec<String>,
) -> Result<String, String> {
    let mut sidecar = app.shell().sidecar("my-sidecar").map_err(|e| e.to_string())?;
    sidecar = sidecar.arg(&command);
    for arg in args {
        sidecar = sidecar.arg(&arg);
    }
    let output = sidecar.output().await.map_err(|e| e.to_string())?;
    if output.status.success() {
        String::from_utf8(output.stdout).map_err(|e| e.to_string())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).to_string())
    }
}

Register the command:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .invoke_handler(tauri::generate_handler![call_sidecar])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Streaming Output

import { Command } from '@tauri-apps/plugin-shell';

async function runWithStreaming(args: string[]): Promise<void> {
  const command = Command.sidecar('binaries/my-sidecar', args);
  command.on('close', (data) => console.log(`Finished: ${data.code}`));
  command.on('error', (error) => console.error(`Error: ${error}`));
  command.stdout.on('data', (line) => console.log(`stdout: ${line}`));
  command.stderr.on('data', (line) => console.error(`stderr: ${line}`));
  await command.spawn();
}

Long-Running HTTP Sidecar

For persistent processes, use HTTP:

sidecar/index.js:

import http from 'http';

const PORT = process.env.SIDECAR_PORT || 3333;

const server = http.createServer((req, res) => {
  let body = '';
  req.on('data', (chunk) => (body += chunk));
  req.on('end', () => {
    res.setHeader('Content-Type', 'application/json');
    try {
      const data = body ? JSON.parse(body) : {};
      if (req.url === '/hello') {
        res.end(JSON.stringify({ message: `Hello ${data.name || 'World'}!` }));
      } else if (req.url === '/health') {
        res.end(JSON.stringify({ status: 'ok' }));
      } else {
        res.statusCode = 404;
        res.end(JSON.stringify({ error: 'Not found' }));
      }
    } catch (err) {
      res.statusCode = 400;
      res.end(JSON.stringify({ error: err.message }));
    }
  });
});

server.listen(PORT, '127.0.0.1', () => console.log(`Listening on ${PORT}`));
process.on('SIGTERM', () => server.close(() => process.exit(0)));

Frontend communication:

import { Command } from '@tauri-apps/plugin-shell';
import { fetch } from '@tauri-apps/plugin-http';

let sidecarProcess: any = null;
const PORT = 3333;

async function startSidecar(): Promise<void> {
  if (sidecarProcess) return;
  const command = Command.sidecar('binaries/my-sidecar', [], {
    env: { SIDECAR_PORT: String(PORT) },
  });
  sidecarProcess = await command.spawn();
  for (let i = 0; i < 10; i++) {
    try {
      const res = await fetch(`http://127.0.0.1:${PORT}/health`);
      if (res.ok) return;
    } catch {
      await new Promise((r) => setTimeout(r, 100));
    }
  }
  throw new Error('Sidecar failed to start');
}

async function callSidecar(endpoint: string, data?: object): Promise<any> {
  const res = await fetch(`http://127.0.0.1:${PORT}${endpoint}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: data ? JSON.stringify(data) : undefined,
  });
  return res.json();
}

async function stopSidecar(): Promise<void> {
  if (sidecarProcess) {
    await sidecarProcess.kill();
    sidecarProcess = null;
  }
}

Building for Production

Update root package.json:

{
  "scripts": {
    "build:sidecar": "cd sidecar && npm run build",
    "dev": "npm run build:sidecar && tauri dev",
    "build": "npm run build:sidecar && tauri build"
  }
}

Cross-platform targets:

Platformpkg TargetRust Triple
Windows x64node18-win-x64x86_64-pc-windows-msvc
macOS x64node18-macos-x64x86_64-apple-darwin
macOS ARMnode18-macos-arm64aarch64-apple-darwin
Linux x64node18-linux-x64x86_64-unknown-linux-gnu

Security

  1. Use validators instead of "args": true
  2. Bind HTTP servers to 127.0.0.1 only
  3. Validate input in both Tauri and sidecar
  4. Ensure sidecars terminate when the app closes

Troubleshooting

Binary not found: Check target triple matches:

ls -la src-tauri/binaries/
rustc --print host-tuple

Permission denied (Unix):

chmod +x src-tauri/binaries/my-sidecar-*

Silent crashes: Check stderr:

const output = await command.execute();
if (output.code !== 0) console.error(output.stderr);

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/dchuk/claude-code-tauri-skills/running-nodejs-sidecar-in-tauri">View running-nodejs-sidecar-in-tauri on skillZs</a>