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-sdkOr with yarn:
bash
yarn add @stateset/sandbox-sdkQuick 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); // 0With 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 beforeComplete 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
- GitHub Issues: github.com/stateset/stateset-sandbox/issues
- Documentation: docs.sandbox.stateset.app
- Email: support@stateset.com