ReferenceRuntime Adapters
@nextrush/adapter-deno

Deno

Deno HTTP adapter for NextRush — connects your application to Deno.serve().

Connect your NextRush application to Deno's native Deno.serve() API. This adapter bridges NextRush's middleware system to Deno's HTTP server.

Source & internals

Package@nextrush/adapter-deno
StatusStable
Support tierPublic — stable, semver-guarded
Included in nextrush?No — standalone install
RuntimeDeno >=2.0 only

Installation

Import directly in Deno (no npm required):

import { createApp } from 'npm:@nextrush/core';
import { serve } from 'npm:@nextrush/adapter-deno';

Or with import maps in deno.json:

{
  "imports": {
    "@nextrush/core": "npm:@nextrush/core",
    "@nextrush/adapter-deno": "npm:@nextrush/adapter-deno"
  }
}

Deno Permissions Required

Deno requires explicit network permission to run an HTTP server. Use --allow-net at minimum. See Deno Permissions for details.


Quick Start

import { createApp } from '@nextrush/core';
import { listen } from '@nextrush/adapter-deno';

const app = createApp();

app.use(async (ctx) => {
  ctx.json({ runtime: 'deno', message: 'Hello World!' });
});

listen(app, 8080);
// Output: 🚀 NextRush listening on http://localhost:8080 (Deno)

Run with Deno:

deno run --allow-net src/main.ts

API Reference

serve(app, options?)

Start the HTTP server with full configuration control.

import { createApp } from '@nextrush/core';
import { serve } from '@nextrush/adapter-deno';

const app = createApp();

const server = serve(app, {
  port: 8080,
  host: '0.0.0.0',
  onListen: ({ port, hostname }) => {
    console.log(`Server running at http://${hostname}:${port}`);
  },
  onError: (error) => {
    console.error('Server error:', error);
  },
});

ServeOptions

ServeOptions

PropertyTypeDescription
portnumber= 8080Port to listen on
hoststring= '0.0.0.0'Host to bind to
onListen?(info: { port: number; host: string; hostname: string }) => voidCallback when server starts listening
onError?(error: Error) => voidCustom error handler for uncaught server errors
cert?stringTLS certificate in PEM format. Deprecated — use tls.cert instead; removed in a future minor version.
key?stringTLS private key in PEM format. Deprecated — use tls.key instead; removed in a future minor version.
tls?{ cert: string; key: string; ca?: string }TLS configuration (canonical shape — matches @nextrush/adapter-bun). Deno.serve() negotiates HTTP/1.1 vs HTTP/2 via ALPN automatically once this is present. Prefer this over the deprecated flat cert/key fields.
shutdownTimeoutnumber= 30000Grace period in ms for draining in-flight requests during shutdown
gracefulShutdown?boolean | GracefulShutdownOptionsOpt-in: wire SIGTERM/SIGINT to the same close() drain logic. `true` installs handlers for the default signal set; `{ signals, timeout }` overrides them. Omitted by default — no signal handler is installed and process behavior is unchanged.

ServerInstance

The returned server instance provides control methods:

interface ServerInstance {
  server: DenoServer; // Underlying Deno server
  port: number; // Actual port
  hostname: string; // Actual hostname

  close(): Promise<void>; // Graceful shutdown
  address(): { port; hostname };
  finished: Promise<void>; // Resolves when server stops
}

listen(app, port?)

Quick-start shorthand with default logging.

import { createApp } from '@nextrush/core';
import { listen } from '@nextrush/adapter-deno';

const app = createApp();
const server = listen(app, 8080);
// Output: 🚀 NextRush listening on http://localhost:8080 (Deno)

createHandler(app)

Create a raw handler for advanced use cases.

import { createApp } from '@nextrush/core';
import { createHandler } from '@nextrush/adapter-deno';

const app = createApp();
const handler = createHandler(app);

// Use directly with Deno.serve
Deno.serve({ handler, port: 8080 });

TLS/HTTPS & HTTP/2

Enable HTTPS with TLS certificates. Deno.serve() negotiates HTTP/1.1 vs HTTP/2 via ALPN automatically once TLS is configured — there is no separate protocol option.

import { serve } from '@nextrush/adapter-deno';

const server = serve(app, {
  port: 443,
  tls: {
    cert: await Deno.readTextFile('./cert.pem'),
    key: await Deno.readTextFile('./key.pem'),
  },
});

Migrating from the flat cert/key fields

The top-level cert/key options are deprecated in favor of tls (matching @nextrush/adapter-bun's shape) and will be removed in a future minor version:

// Before (deprecated)
serve(app, { cert, key });

// After
serve(app, { tls: { cert, key } });

Deno Permissions

NextRush requires network permission to run:

# Minimum required
deno run --allow-net app.ts

# With file access (for static files, etc.)
deno run --allow-net --allow-read app.ts

# Development (more permissive)
deno run --allow-all app.ts

Server Lifecycle

Waiting for Server Finish

Deno adapter exposes a finished promise:

const server = serve(app, { port: 8080 });

// Wait for server to complete (useful in scripts)
await server.finished;

Graceful Shutdown

Handle server shutdown properly:

const server = serve(app, { port: 8080 });

Deno.addSignalListener('SIGTERM', async () => {
  console.log('Shutting down...');
  await server.close(); // Closes app + server
  Deno.exit(0);
});

Or wire it declaratively with gracefulShutdown instead of listening for signals yourself:

const server = await serve(app, {
  port: 8080,
  gracefulShutdown: true, // installs SIGTERM + SIGINT, using shutdownTimeout as the drain window
});

// Or override the signal set / drain timeout explicitly:
const server2 = await serve(app, {
  port: 8080,
  gracefulShutdown: { signals: ['SIGTERM'], timeout: 5_000 },
});

Omitting gracefulShutdown installs no signal handler — process behavior is unchanged. The handler is removed once close() completes, so repeated serve()/close() cycles never accumulate duplicate listeners.


Client IP Access

The adapter extracts client IP from Deno's connection info:

app.use(async (ctx) => {
  // Available via context
  const ip = ctx.ip; // From remoteAddr
  ctx.json({ clientIp: ip });
});

Request Paths and Dot Segments

Dot segments are rejected here — this platform preserves the target

Deno.serve() hands the request target to the application unparsed, so a target containing a dot segment — /files/../admin, or the encoded /files/%2e%2e/admin — is rejected with 400 before any middleware runs, and ctx.path holds the bytes the client sent.

That guarantee ends at this process's edge: a proxy or platform in front that resolves the segment first makes it unreachable. Authorise path rules against ctx.path, and reject unwanted raw targets in front of Deno. See Request Paths and Dot Segments.


Deno Deploy

For Deno Deploy, use createHandler:

import { createApp } from '@nextrush/core';
import { createHandler } from '@nextrush/adapter-deno';

const app = createApp();

app.use(async (ctx) => {
  ctx.json({
    message: 'Hello from Deno Deploy!',
    region: Deno.env.get('DENO_REGION'),
  });
});

Deno.serve(createHandler(app));

TypeScript

Full TypeScript support with Deno's native TS:

import type { Application } from '@nextrush/core';
import type { ServeOptions, ServerInstance } from '@nextrush/adapter-deno';
import { serve, listen, createHandler } from '@nextrush/adapter-deno';

const options: ServeOptions = {
  port: 8080,
  hostname: '0.0.0.0',
};

const server: ServerInstance = serve(app, options);

Was this helpful?

On this page