Node.js SDK

Integrate sandboxes into your Node.js web applications.

Getting Started with the StateSet Sandbox Node.js SDK

This guide walks you through integrating the StateSet Sandbox SDK into your Node.js or TypeScript web application to spin up isolated sandbox environments for code execution.

Prerequisites

  • Node.js 18.0.0 or later
  • A StateSet Sandbox API key (sign up here)
  • npm or yarn package manager

Installation

bash
npm install @stateset/sandbox-sdk

Or with yarn:

bash
yarn add @stateset/sandbox-sdk

Quick Start

Here's a minimal example to create a sandbox and execute a command:

typescript
import { StateSetSandbox } from '@stateset/sandbox-sdk';

// Initialize the client
const sandbox = new StateSetSandbox({
  baseUrl: 'https://api.sandbox.stateset.app',
  authToken: process.env.STATESET_API_KEY!,
});

// Create a sandbox and run code
async function runCode() {
  // Create a new sandbox
  const { sandbox_id } = await sandbox.create({
    cpus: '1',
    memory: '1Gi',
    timeout_seconds: 300,
  });

  console.log(`Sandbox created: ${sandbox_id}`);

  // Execute a command
  const result = await sandbox.execute(sandbox_id, {
    command: 'echo "Hello from the sandbox!"',
  });

  console.log('Output:', result.stdout);
  console.log('Exit code:', result.exit_code);

  // Clean up
  await sandbox.stop(sandbox_id);
}

runCode();

Configuration Options

When initializing StateSetSandbox, you can configure:

typescript
const sandbox = new StateSetSandbox({
  // Required: Your API endpoint
  baseUrl: 'https://api.sandbox.stateset.app',

  // Required: API key or JWT token
  authToken: 'sk_live_...',

  // Optional: Request timeout (default: 30000ms)
  timeout: 60000,

  // Optional: API version (default: 'v1')
  apiVersion: 'v1',
});

Creating Sandboxes

Basic Sandbox

typescript
const { sandbox_id, status, expires_at } = await sandbox.create();

Customized Sandbox

typescript
const { sandbox_id, startup_metrics } = await sandbox.create({
  // CPU allocation (e.g., "0.5", "1", "2")
  cpus: '2',

  // Memory allocation (e.g., "512Mi", "1Gi", "4Gi")
  memory: '2Gi',

  // Auto-termination timeout in seconds
  timeout_seconds: 600,

  // Environment variables
  env: {
    NODE_ENV: 'production',
    API_URL: 'https://api.example.com',
  },

  // Isolation level: 'container' | 'gvisor' | 'microvm'
  isolation: 'gvisor',
});

console.log(`Startup time: ${startup_metrics?.total_ms}ms`);

Executing Commands

Simple Execution

typescript
const result = await sandbox.execute(sandbox_id, {
  command: 'node --version',
});

console.log(result.stdout);  // v20.x.x
console.log(result.exit_code);  // 0

With Working Directory and Environment

typescript
const result = await sandbox.execute(sandbox_id, {
  command: ['npm', 'install'],
  working_dir: '/workspace/my-project',
  env: {
    NPM_CONFIG_REGISTRY: 'https://registry.npmjs.org',
  },
});

Streaming Execution

For long-running commands, use streaming to get real-time output:

typescript
await sandbox.executeStream(
  sandbox_id,
  { command: 'npm test' },
  {
    onStdout: (data) => process.stdout.write(data),
    onStderr: (data) => process.stderr.write(data),
    onExit: (code) => console.log(`\nProcess exited with code: ${code}`),
    onError: (error) => console.error('Error:', error.message),
  }
);

File Operations

Writing Files

typescript
// Write a single file
await sandbox.writeFile(sandbox_id, '/workspace/main.js', `
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'Hello World!' });
});

app.listen(3000);
`);

// Write multiple files at once (content must be base64 encoded)
await sandbox.writeFiles(sandbox_id, [
  {
    path: '/workspace/package.json',
    content: Buffer.from(JSON.stringify({
      name: 'my-app',
      dependencies: { express: '^4.18.0' }
    })).toString('base64'),
  },
  {
    path: '/workspace/README.md',
    content: Buffer.from('# My App').toString('base64'),
  },
]);

Reading Files

typescript
// Read file content (automatically decoded from base64)
const content = await sandbox.readFileContent(sandbox_id, '/workspace/main.js');
console.log(content);

// Read file with metadata
const file = await sandbox.readFile(sandbox_id, '/workspace/main.js');
console.log(`Size: ${file.size} bytes`);
console.log(`Content (base64): ${file.content}`);

Web Application Integration

Express.js Example

Here's how to integrate the sandbox SDK into an Express.js backend:

typescript
import express from 'express';
import { StateSetSandbox } from '@stateset/sandbox-sdk';

const app = express();
app.use(express.json());

const sandboxClient = new StateSetSandbox({
  baseUrl: process.env.STATESET_API_URL!,
  authToken: process.env.STATESET_API_KEY!,
});

// Endpoint to run user code safely
app.post('/api/run-code', async (req, res) => {
  const { code, language } = req.body;
  let sandboxId: string | null = null;

  try {
    // Create a sandbox
    const sandbox = await sandboxClient.create({
      cpus: '0.5',
      memory: '512Mi',
      timeout_seconds: 30,
      isolation: 'gvisor', // Enhanced security
    });
    sandboxId = sandbox.sandbox_id;

    // Write the user's code
    const filename = language === 'python' ? 'main.py' : 'main.js';
    await sandboxClient.writeFile(sandboxId, `/workspace/${filename}`, code);

    // Execute the code
    const command = language === 'python'
      ? `python3 /workspace/${filename}`
      : `node /workspace/${filename}`;

    const result = await sandboxClient.execute(sandboxId, {
      command,
      working_dir: '/workspace',
    });

    res.json({
      success: result.exit_code === 0,
      output: result.stdout,
      error: result.stderr,
      exitCode: result.exit_code,
    });

  } catch (error) {
    res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error',
    });
  } finally {
    // Always clean up the sandbox
    if (sandboxId) {
      await sandboxClient.stop(sandboxId).catch(console.error);
    }
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Next.js API Route Example

typescript
// app/api/execute/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { StateSetSandbox } from '@stateset/sandbox-sdk';

const sandboxClient = new StateSetSandbox({
  baseUrl: process.env.STATESET_API_URL!,
  authToken: process.env.STATESET_API_KEY!,
});

export async function POST(request: NextRequest) {
  const { code } = await request.json();
  let sandboxId: string | null = null;

  try {
    // Create sandbox
    const sandbox = await sandboxClient.create({
      cpus: '1',
      memory: '1Gi',
      timeout_seconds: 60,
    });
    sandboxId = sandbox.sandbox_id;

    // Write and execute code
    await sandboxClient.writeFile(sandboxId, '/workspace/script.js', code);

    const result = await sandboxClient.execute(sandboxId, {
      command: 'node /workspace/script.js',
    });

    return NextResponse.json({
      output: result.stdout,
      error: result.stderr,
      exitCode: result.exit_code,
    });

  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : 'Execution failed' },
      { status: 500 }
    );
  } finally {
    if (sandboxId) {
      await sandboxClient.stop(sandboxId).catch(() => {});
    }
  }
}

Streaming with Server-Sent Events

For real-time output in your web app:

typescript
// Backend: Express.js SSE endpoint
app.get('/api/run-stream', async (req, res) => {
  const { code } = req.query;

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  let sandboxId: string | null = null;

  try {
    const sandbox = await sandboxClient.create({
      cpus: '1',
      memory: '1Gi',
      timeout_seconds: 120,
    });
    sandboxId = sandbox.sandbox_id;

    await sandboxClient.writeFile(sandboxId, '/workspace/script.js', code as string);

    await sandboxClient.executeStream(
      sandboxId,
      { command: 'node /workspace/script.js' },
      {
        onStdout: (data) => {
          res.write(`data: ${JSON.stringify({ type: 'stdout', data })}\n\n`);
        },
        onStderr: (data) => {
          res.write(`data: ${JSON.stringify({ type: 'stderr', data })}\n\n`);
        },
        onExit: (code) => {
          res.write(`data: ${JSON.stringify({ type: 'exit', code })}\n\n`);
          res.end();
        },
      }
    );
  } catch (error) {
    res.write(`data: ${JSON.stringify({ type: 'error', message: error instanceof Error ? error.message : 'Unknown error' })}\n\n`);
    res.end();
  } finally {
    if (sandboxId) {
      await sandboxClient.stop(sandboxId).catch(() => {});
    }
  }
});
typescript
// Frontend: React component with SSE
function CodeRunner({ code }: { code: string }) {
  const [output, setOutput] = useState<string[]>([]);
  const [isRunning, setIsRunning] = useState(false);

  const runCode = () => {
    setIsRunning(true);
    setOutput([]);

    const eventSource = new EventSource(
      `/api/run-stream?code=${encodeURIComponent(code)}`
    );

    eventSource.onmessage = (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'stdout' || data.type === 'stderr') {
        setOutput(prev => [...prev, data.data]);
      } else if (data.type === 'exit') {
        setIsRunning(false);
        eventSource.close();
      } else if (data.type === 'error') {
        setOutput(prev => [...prev, `Error: ${data.message}`]);
        setIsRunning(false);
        eventSource.close();
      }
    };

    eventSource.onerror = () => {
      setIsRunning(false);
      eventSource.close();
    };
  };

  return (
    <div>
      <button onClick={runCode} disabled={isRunning}>
        {isRunning ? 'Running...' : 'Run Code'}
      </button>
      <pre>{output.join('')}</pre>
    </div>
  );
}

Error Handling

The SDK provides typed errors for better handling:

typescript
import {
  StateSetSandbox,
  SandboxApiError,
  SandboxTimeoutError,
  SandboxNetworkError
} from '@stateset/sandbox-sdk';

try {
  const result = await sandbox.execute(sandboxId, { command: 'npm test' });
} catch (error) {
  if (error instanceof SandboxApiError) {
    // API returned an error response
    console.error(`API Error [${error.code}]: ${error.message}`);
    console.error('Status:', error.statusCode);
    console.error('Request ID:', error.requestId);
  } else if (error instanceof SandboxTimeoutError) {
    // Request timed out
    console.error(`Timeout after ${error.timeout}ms`);
  } else if (error instanceof SandboxNetworkError) {
    // Network connectivity issue
    console.error('Network error:', error.message);
  }
}

Managing Secrets

Store and inject secrets securely:

typescript
// Create an organization secret
await sandbox.createSecret({
  name: 'OPENAI_API_KEY',
  value: 'sk-...',
  scope: 'sandbox',
});

// Inject secrets into a running sandbox
await sandbox.injectSecrets(sandboxId, {
  secrets: ['OPENAI_API_KEY', 'DATABASE_URL'],
  as_env_vars: true, // Make available as environment variables
});

API Keys and Usage

Managing API Keys

typescript
// List existing keys
const { api_keys } = await sandbox.listApiKeys();

// Create a new key
const { key, api_key } = await sandbox.createApiKey({
  name: 'Production Key',
  scopes: ['sandbox:create', 'sandbox:read', 'sandbox:write'],
  expires_in_days: 90,
});
console.log('New API Key:', key); // Only shown once!

// Revoke a key
await sandbox.revokeApiKey(api_key.id);

Monitoring Usage

typescript
// Get current billing period usage
const usage = await sandbox.getCurrentUsage();
console.log(`CPU Hours: ${usage.cpu_hours}`);
console.log(`Memory GB-Hours: ${usage.memory_gb_hours}`);
console.log(`Estimated Cost: $${(usage.estimated_cost_cents / 100).toFixed(2)}`);

// Get historical usage
const history = await sandbox.getUsageHistory('daily', {
  start_date: '2024-01-01',
  end_date: '2024-01-31',
});

Best Practices

1. Always Clean Up Sandboxes

typescript
async function runWithCleanup<T>(
  fn: (sandboxId: string) => Promise<T>
): Promise<T> {
  const { sandbox_id } = await sandbox.create();

  try {
    return await fn(sandbox_id);
  } finally {
    await sandbox.stop(sandbox_id).catch(() => {});
  }
}

// Usage
const result = await runWithCleanup(async (sandboxId) => {
  await sandbox.writeFile(sandboxId, '/workspace/app.js', code);
  return sandbox.execute(sandboxId, { command: 'node /workspace/app.js' });
});

2. Use Appropriate Timeouts

typescript
// Short-lived sandbox for quick operations
const quickSandbox = await sandbox.create({
  timeout_seconds: 30,
  cpus: '0.5',
  memory: '256Mi',
});

// Long-running sandbox for complex tasks
const longSandbox = await sandbox.create({
  timeout_seconds: 3600, // 1 hour
  cpus: '2',
  memory: '4Gi',
});

3. Use gVisor for Untrusted Code

typescript
const secureSandbox = await sandbox.create({
  isolation: 'gvisor', // Kernel-level isolation
  cpus: '1',
  memory: '1Gi',
});

4. Handle Sandbox Expiration

typescript
const { sandbox_id, expires_at } = await sandbox.create({
  timeout_seconds: 300,
});

const expiresIn = new Date(expires_at).getTime() - Date.now();
console.log(`Sandbox expires in ${Math.round(expiresIn / 1000)}s`);

// Set up a warning before expiration
setTimeout(() => {
  console.warn('Sandbox expiring soon!');
}, expiresIn - 30000); // 30 seconds before

Complete Example: Code Playground Backend

typescript
import express from 'express';
import { StateSetSandbox } from '@stateset/sandbox-sdk';

const app = express();
app.use(express.json());

const sandboxClient = new StateSetSandbox({
  baseUrl: process.env.STATESET_API_URL!,
  authToken: process.env.STATESET_API_KEY!,
  timeout: 60000,
});

interface RunRequest {
  code: string;
  language: 'javascript' | 'typescript' | 'python';
  dependencies?: string[];
}

app.post('/api/playground/run', async (req, res) => {
  const { code, language, dependencies = [] }: RunRequest = req.body;
  let sandboxId: string | null = null;

  try {
    // Create sandbox with appropriate resources
    const sandbox = await sandboxClient.create({
      cpus: '1',
      memory: '1Gi',
      timeout_seconds: 120,
      isolation: 'gvisor',
      env: {
        NODE_ENV: 'production',
      },
    });
    sandboxId = sandbox.sandbox_id;

    // Install dependencies if needed
    if (dependencies.length > 0) {
      const installCmd = language === 'python'
        ? `pip install ${dependencies.join(' ')}`
        : `npm install ${dependencies.join(' ')}`;

      await sandboxClient.execute(sandboxId, {
        command: installCmd,
        working_dir: '/workspace',
      });
    }

    // Write user code
    let filename: string;
    let runCommand: string;

    switch (language) {
      case 'typescript':
        filename = 'main.ts';
        runCommand = 'npx tsx /workspace/main.ts';
        break;
      case 'python':
        filename = 'main.py';
        runCommand = 'python3 /workspace/main.py';
        break;
      default:
        filename = 'main.js';
        runCommand = 'node /workspace/main.js';
    }

    await sandboxClient.writeFile(sandboxId, `/workspace/${filename}`, code);

    // Execute and capture output
    const result = await sandboxClient.execute(sandboxId, {
      command: runCommand,
      working_dir: '/workspace',
    });

    res.json({
      success: result.exit_code === 0,
      output: result.stdout,
      error: result.stderr,
      exitCode: result.exit_code,
      executionTime: sandbox.startup_metrics?.total_ms,
    });

  } catch (error) {
    console.error('Execution error:', error);
    res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error',
    });
  } finally {
    if (sandboxId) {
      await sandboxClient.stop(sandboxId).catch(console.error);
    }
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Playground API running on port ${PORT}`);
});

Support