ReferenceRuntime Adapters
@nextrush/adapter-serverless

Serverless

Deploy a NextRush app to AWS Lambda, Google Cloud Functions, or Azure Functions in one line.

Run a NextRush app as a serverless function. The adapter separates the execution model (per-invocation, stateless, timeout→504, warm-instance reuse) from each provider's event format — you import one Tier-1 handler and never touch the mapper plumbing underneath.

Source & internals

Package@nextrush/adapter-serverless
StatusBeta
Support tierInternal — non--node adapter until GA, may change without a major (ADR-0005)
Included in nextrush?No — standalone install
RuntimeAWS Lambda, Google Cloud Functions, Azure Functions — built on @nextrush/adapter-edge's fetch engine

Cloudflare Workers uses a different package

Cloudflare's Tier-1 handler (createCloudflareHandler) ships in @nextrush/adapter-edge, not here — it shares the edge execution model, not the serverless one.

Installation

$ pnpm add @nextrush/adapter-serverless

Quick start

import { createApp } from '@nextrush/core';
import { createLambdaHandler } from '@nextrush/adapter-serverless';

const app = createApp();

app.use(async (ctx) => {
  ctx.json({ message: 'Hello from Lambda!' });
});

export const handler = createLambdaHandler(app);

Tier-1 handlers (what you actually import)

FunctionPlatformEvent shapes handled
createLambdaHandler(app, options?)AWS LambdaFunction URL / API Gateway HTTP API (v2) and REST API (v1) — auto-detected per invocation, no explicit provider needed
createLambdaStreamingHandler(app, options?)AWS Lambda response streaming (awslambda.streamifyResponse)Lambda Function URL streaming
createGoogleHandler(app, options?)Google Cloud FunctionsHTTP-triggered functions — a (req, res) drop-in for functions.http()
createAzureHandler(app, options?)Azure FunctionsHTTP-triggered functions (v4 model) — a (req) => response drop-in for app.http()
// Google Cloud Functions
import { createGoogleHandler } from '@nextrush/adapter-serverless';
import * as functions from '@google-cloud/functions-framework';

functions.http('api', createGoogleHandler(app));
// Azure Functions (v4 model)
import { createAzureHandler } from '@nextrush/adapter-serverless';
import { app as functions } from '@azure/functions';

functions.http('api', { handler: createAzureHandler(app) });

See Google Cloud Functions deployment and Azure Functions deployment for the full deploy commands and platform-specific configuration these two drop-ins need.

These two signatures changed in 1.0.0-beta.1

Before 1.0.0-beta.1, createGoogleHandler and createAzureHandler took a normalized event struct that you built from the platform's req yourself. That behavior is preserved unchanged under the new names createGoogleEventHandler / createAzureEventHandler — rename your call to keep an existing hand-written bridge working, or delete the bridge and pass the drop-in straight to the platform. Reach for the *EventHandler names for fixture testing, a custom bridge, or a host whose request object is not the standard SDK shape.

Knowing which platform you are on

ctx.runtime is 'edge' on every provider here — this adapter runs on @nextrush/adapter-edge's fetch engine, whose runtime detector has no AWS/GCP/Azure branch. A check for ctx.runtime === 'node' typechecks and never matches.

ctx.platform is the field that names the provider: 'lambda', 'gcf', or 'azure'. Each Tier-1 handler sets it explicitly, so it is declared rather than guessed.

import { createLambdaHandler } from '@nextrush/adapter-serverless';

app.use(async (ctx) => {
  ctx.json({ platform: ctx.platform, runtime: ctx.runtime });
});

export const handler = createLambdaHandler(app);

Each Tier-1 handler accepts ServerlessHandlerOptions:

interface ServerlessHandlerOptions {
  /** Per-invocation timeout (ms). Exceeding it produces a 504 result instead of hanging the invocation. */
  timeout?: number;
}
export const handler = createLambdaHandler(app, { timeout: 5000 });

Advanced: building a custom provider (Tier 3)

See Event mapping for how the built-in mappers above translate a platform event into the same Request/Response every adapter uses — the mental model for what you're implementing below.

Application developers should stop at the Tier-1 handlers above. Implement EventMapper only to add a platform NextRush doesn't ship (Oracle, Fly.io, OpenFaaS, an internal platform):

import { createServerlessAdapter } from '@nextrush/adapter-serverless';
import type { EventMapper } from '@nextrush/adapter-serverless';

const myMapper: EventMapper<MyEvent, MyResult> = {
  name: 'my-platform',
  toRequest(event) {
    /* build a Web Request from the platform event */
  },
  fromResponse(response, event) {
    /* map the Response back to the platform's expected result */
  },
};

const adapter = createServerlessAdapter({ mappers: [myMapper] });
export const handler = adapter.createHandler(app);

mappers is an immutable, per-adapter registry — there is no global mutable mapper registry. When more than one mapper is supplied, an explicit provider name wins; each mapper's optional detect() only runs as a fallback when no provider is named.

Request Paths and Dot Segments

rawPath is parsed into a URL before dispatch

The Lambda adapter builds its Request from the event's rawPath, and that construction runs the URL parser — so a dot segment reaches the application as the resolved path rather than as the bytes the client sent, and the framework's 400 rejection cannot fire.

Authorise path rules against the path the application actually routed on (ctx.path), and reject unwanted raw targets at API Gateway (WAF, resource policy, routing rules) if you need them rejected at all. See Request Paths and Dot Segments.


Troubleshooting

Timeout returns a 504 result, not a thrown error

Exceeding timeout produces a 504-shaped result via the platform's normal response path — it does not reject the returned promise. Check the response status your platform's logs show, not a caught exception.

Every mapper guards the one field it cannot translate without and throws a named [nextrush/serverless] error naming the likely cause (a wrong payload format, or an incomplete hand-written bridge on the *EventHandler path) instead of a raw TypeError from framework internals. On GCF, a request whose rawBody is absent while body is already parsed logs a [nextrush/serverless] warning and omits the body rather than stringifying an object into it.

Because this adapter runs on @nextrush/adapter-edge's engine, that package's development-mode diagnostics apply here too: the ctx.waitUntil() no-op warning (serverless invocations supply no execution context), the boot-reuse warning when createApp() is called inside the exported handler, and the timeout-attribution log accompanying every 504. See @nextrush/adapter-edge for the exact messages.

Was this helpful?

On this page