rembrembdocs

Express Plugin

Automatically expose your Express dev server via OutRay tunnel

The @outray/express plugin automatically creates an OutRay tunnel when your Express development server starts, giving you a public URL to share your local development environment.

npm install @outray/express
pnpm add @outray/express
yarn add @outray/express

Add the plugin to your Express application:

// index.js
import express from 'express';
import outray from '@outray/express';

const app = express();

// Apply OutRay middleware
outray(app);

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

When you run your Express server in development mode, you'll see your tunnel URL:

Server running on port 3000
  ➜  Tunnel:  https://abc123.outray.dev

The plugin accepts an options object to customize its behavior:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app, {
  subdomain: 'my-api',
  apiKey: process.env.OUTRAY_API_KEY,
});

app.listen(3000);

Options

OptionTypeDefaultDescription
subdomainstringRequest a specific subdomain for your tunnel URL. Requires authentication.
customDomainstringUse a custom domain. Must be configured in the OutRay dashboard first.
apiKeystringprocess.env.OUTRAY_API_KEYAPI key for authentication.
serverUrlstringwss://api.outray.dev/OutRay server WebSocket URL. Only change this for self-hosted instances.
enabledbooleanprocess.env.OUTRAY_ENABLED !== "false"Enable or disable the tunnel.
silentbooleanfalseSuppress tunnel status logs.
onTunnelReady(url: string) => voidCallback fired when tunnel is successfully established.
onError(error: Error) => voidCallback fired when tunnel encounters an error.
onClose() => voidCallback fired when tunnel connection is closed.
onReconnecting() => voidCallback fired when tunnel is attempting to reconnect.

The plugin respects the following environment variables:

VariableDescription
NODE_ENVSet to development to enable the tunnel (production is disabled by default)
OUTRAY_API_KEYAPI key for authentication (fallback for apiKey option)
OUTRAY_SUBDOMAINSubdomain to use (fallback for subdomain option)
OUTRAY_SERVER_URLServer URL (fallback for serverUrl option)
OUTRAY_ENABLEDSet to "false" to disable the tunnel

Custom Subdomain

Reserve a consistent subdomain for your API:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app, {
  subdomain: 'my-api',
  apiKey: process.env.OUTRAY_API_KEY,
});

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.listen(3000);

Custom Domain

Use your own domain for the tunnel:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app, {
  customDomain: 'api.example.com',
  apiKey: process.env.OUTRAY_API_KEY,
});

app.listen(3000);

Conditional Enabling

Only enable the tunnel in certain environments:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app, {
  enabled: process.env.EXPOSE_TUNNEL === 'true',
});

app.listen(3000);

With Callbacks

React to tunnel events in your application:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app, {
  onTunnelReady: (url) => {
    console.log(`API accessible at: ${url}`);
    // Send to Slack, update config, etc.
  },
  onError: (error) => {
    console.error('Tunnel error:', error.message);
  },
  onReconnecting: () => {
    console.log('Connection lost, reconnecting...');
  },
});

app.listen(3000);

Silent Mode

Disable all tunnel logs:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app, {
  silent: true,
  onTunnelReady: (url) => {
    // Handle the URL silently
  },
});

app.listen(3000);

Dynamic Port

Works perfectly with dynamically assigned ports:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app);

// Use port 0 for a random available port
const server = app.listen(0, () => {
  const port = server.address().port;
  console.log(`Server running on port ${port}`);
});

Perfect for exposing your local API for webhook testing:

import express from 'express';
import outray from '@outray/express';

const app = express();

outray(app, {
  subdomain: 'webhook-test',
  onTunnelReady: (url) => {
    console.log(`Webhook endpoint: ${url}/webhooks`);
  },
});

app.use(express.json());

app.post('/webhooks', (req, res) => {
  console.log('Received webhook:', req.body);
  res.json({ received: true });
});

app.listen(3000);

The plugin includes full TypeScript definitions:

import express, { Application } from 'express';
import outray, { OutrayPluginOptions } from '@outray/express';

const app: Application = express();

const options: OutrayPluginOptions = {
  subdomain: 'my-api',
  apiKey: process.env.OUTRAY_API_KEY,
  onTunnelReady: (url: string) => {
    console.log(`Tunnel ready: ${url}`);
  },
};

outray(app, options);

app.listen(3000);

The OutRay plugin doesn't interfere with your middleware stack. You can place it anywhere:

import express from 'express';
import outray from '@outray/express';
import cors from 'cors';
import helmet from 'helmet';

const app = express();

// Apply OutRay before or after other middleware
outray(app);

app.use(cors());
app.use(helmet());
app.use(express.json());

app.listen(3000);

The plugin supports Express versions 4.x and 5.x.

The plugin hooks into Express's app.listen() method to:

  1. Detect when your server starts listening
  2. Capture the port number (even for dynamic ports)
  3. Establish a WebSocket tunnel to OutRay servers
  4. Proxy all incoming HTTP requests to your local server

Tunnel not starting

Authentication errors

Connection issues

Not seeing tunnel URL

Make sure you're running in development mode:

NODE_ENV=development node app.js

Or add it to your package.json:

{
  "scripts": {
    "dev": "NODE_ENV=development node app.js"
  }
}