OutRay

NestJS Plugin

Automatically expose your NestJS dev server via OutRay tunnel

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

Installation

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

Quick Start

Add the plugin to your NestJS application's main.ts:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Start the server
  await app.listen(3000);

  // Start the tunnel in development
  if (process.env.NODE_ENV !== 'production') {
    await outray(app);
  }
}
bootstrap();

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

[Nest] Application is running on: http://localhost:3000
  Tunnel:  https://abc123.outray.dev

Configuration

The plugin accepts an options object to customize its behavior:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

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

Options

OptionTypeDefaultDescription
portnumber | stringAuto-detectedThe local port your NestJS app is running on. Automatically detected from the app instance.
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.NODE_ENV !== 'production'Enable or disable the tunnel. Disabled by default in production.
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.

Environment Variables

The plugin respects the following environment variables:

VariableDescription
NODE_ENVSet to production to disable the tunnel automatically
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)

Examples

Custom Subdomain

Reserve a consistent subdomain for your API:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

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

Custom Domain

Use your own domain for the tunnel:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

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

Conditional Enabling

Only enable the tunnel in certain environments:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

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

With Callbacks

React to tunnel events in your application:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

  await 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...');
    },
  });
}
bootstrap();

Silent Mode

Disable all tunnel logs:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

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

Explicit Port

If automatic port detection doesn't work, you can specify the port explicitly:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const port = process.env.PORT || 3000;
  await app.listen(port);

  await outray(app, {
    port: port,
  });
}
bootstrap();

GraphQL API Integration

Perfect for exposing your GraphQL API for testing:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

  await outray(app, {
    subdomain: 'graphql-dev',
    onTunnelReady: (url) => {
      console.log(`GraphQL Playground: ${url}/graphql`);
    },
  });
}
bootstrap();

Webhook Testing

Perfect for testing webhooks from external services:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

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

TypeScript Support

The plugin includes full TypeScript definitions:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { outray, OutrayPluginOptions } from '@outray/nest';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);

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

  await outray(app, options);
}
bootstrap();

NestJS Compatibility

The plugin supports NestJS versions 8.x, 9.x, 10.x, and 11.x.

How It Works

The plugin:

  1. Detects the port from the NestJS application's HTTP server
  2. Establishes a WebSocket tunnel to OutRay servers
  3. Proxies all incoming HTTP requests to your local server
  4. Handles cleanup on process exit (SIGINT, SIGTERM)

Troubleshooting

Tunnel not starting

  • Ensure NODE_ENV is not set to production
  • Check that you've called await app.listen() before await outray(app)
  • Verify your server is listening on a TCP port

Port detection issues

If the plugin can't detect the port automatically:

  • Make sure you await app.listen(port) before calling outray(app)
  • Or pass the port explicitly: outray(app, { port: 3000 })

Authentication errors

  • Run outray login in your terminal to authenticate
  • Or set the OUTRAY_API_KEY environment variable

Connection issues

  • Check your internet connection
  • Verify the server URL is correct (default: wss://api.outray.dev/)
  • The plugin will automatically attempt to reconnect on connection loss

Not seeing tunnel URL

Make sure you're running in development mode:

NODE_ENV=development npm run start:dev

Or ensure enabled is not set to false.

On this page