Lambda Web Functions vs API Gateway Should You Migrate?

A UK-first decision framework with whole-architecture costs, an Express pilot and measurable rollback gates.

20 October 2026 · 13 min read · Cloudline Consulting

Three engineers reviewing paper diagrams around an office meeting table.
Photo: NASA Goddard Photo and Video (BY 2.0)

Your Node.js API already works behind API Gateway and Lambda. Now AWS offers a direct HTTP model, and removing a service sounds attractive. But a smaller architecture diagram does not automatically mean a cheaper or safer API. Lambda Web Functions launched on 12 October 2026 with Ireland among its regions, not London. Start with that constraint, then work through authentication, shared concurrency and complete running costs before committing engineering time to a migration.

In this article
  1. Start with the regional gate, not the launch announcement
  2. Use a keep, pilot or migrate decision matrix
  3. Price the whole API path for three traffic patterns
  4. Move Express without rewriting your business logic
  5. Prove isolation and performance before moving customers
  6. Roll out one route and keep two rollback paths

Start with the regional gate, not the launch announcement

Office buildings beside the River Thames in London.
European availability is not the same as a London deployment. Photo: Adrian Snood (BY 2.0)

The first question in Lambda Web Functions vs API Gateway is whether the new model can run where your contracts require. Launch excitement is not a reason to replace API Gateway.

Use Cloudline’s Five-Gate API Migration Test: region, identity, isolation, connections and economics. A failed mandatory gate means keep; an unresolved measurable gate means pilot. Passing every gate makes migration a candidate, not an obligation.

The 12 October 2026 launch announcement names five launch regions: Northern Virginia, Ohio, Oregon, Tokyo and Ireland. Ireland is eu-west-1; London is eu-west-2. The announcement establishes the launch footprint: a sample configuration containing London does not establish availability there.

For a UK-only execution requirement, Ireland is a no-go. Serving UK customers does not itself establish that requirement. Check your architecture and contractual commitments; this is not a blanket claim about GDPR.

Next, establish what changes operationally. The Web Functions developer guide describes a different execution model, not merely another endpoint:

DimensionWeb FunctionsEvent Functions
Application interfacePort-listening HTTP serverEvent handler
Per-environment executionConcurrent HTTP requestsOne invocation at a time
Compute billing basisActive CPU plus consumed memory over the environment lifetimeConfigured memory multiplied by invocation duration
Runtime modelNode.js-only at launchExisting event-driven Lambda model

Existing Lambda functions were renamed Event Functions, without requiring redeployment. Classic Lambda Function URLs expose HTTP endpoints for that event-driven model. Lambda Web Adapter bridges HTTP applications into Lambda’s invocation model; it is not Web Functions.

That distinction matters for Lambda Web Functions pricing and migration: concurrent requests can share process state and connection pools. Identity, isolation, connections and economics therefore need separate evidence, not assumptions carried over from today’s handlers. If you are reviewing the baseline, revisit Node.js and Lambda workload design before comparing deployment models.

Use a keep, pilot or migrate decision matrix

Three engineers discussing a diagram on an office whiteboard.
Make the missing gateway responsibilities explicit. Photo: Plutor (BY 2.0)
DecisionKeepPilotMigrate candidate
Regional requirementsLondon execution is mandatory.Ireland only where requirements permit.Execution region and data flows approved.
AuthenticationGateway policies have no funded replacement.Application JWT validation.Issuer, audience, expiry, scopes and tenant authorisation pass contract tests.
Concurrency safetyUnsafe request-global state remains.Request-local state and AsyncLocalStorage tested under overlapping requests.No cross-request leakage under load.
Database connectionsNo explicit connection budget.Bounded pool; measure queueing.Budget includes deployment overlap; queueing meets latency targets.
Custom domains and WAFGateway controls remain necessary.CloudFront, origin authentication and optional WAF.Domain, policy and bypass tests pass.
Workload economicsNo demonstrated whole-path saving.Replay representative traffic; include operational costs.Measured cost and latency justify ownership.

Web endpoints offer ApplicationManaged or IamAuth, not an automatic transplant of gateway authorisers. Authentication plumbing is not tenant authorisation.

Budget connections before increasing concurrency: 20 environments × pool size 5 = 100 potential connections. Reserve additional capacity while old and new deployments overlap.

Outbound VPC connectivity uses an egress network connector; it does not create private ingress. It supports HomeRegion endpoints, not multi-region endpoints. A London database needs a deliberate regional networking decision: existing Lambda VPC settings do not transfer unchanged.

The gateway-free custom-domain design requires CloudFront: client → CloudFront and optional WAF → IAM-protected Web Function → database. Configure OAC with origin type lambda and signing behaviour always-amz-auth. Forward the viewer’s bearer token separately; restrict the resource policy to the intended distribution. CloudFront authenticating the origin is not end-user authorisation.

Compare the gateway product you actually run. REST API usage plans, API keys and direct WAF support are not HTTP API features. The buyer’s question in Lambda Web Functions vs API Gateway is which ingress responsibilities your team wants to own, not merely which endpoint to replace.

Removing API Gateway does not remove the responsibilities it was carrying.

Share this quote

Price the whole API path for three traffic patterns

A calculator beside an open laptop and a paper notebook.
Compare the bill for the complete request path. Photo: D Coetzee (CC0 1.0)

Lambda Web Functions pricing needs a whole-path calculation. Removing API Gateway does not remove delivery, logging or network costs. Follow workload-led cost optimisation, not headline request prices.

A dated control, not an Ireland quote

The 20 October 2026 Northern Virginia Arm control uses these Lambda rates: Web requests $0.30/million, active CPU $0.0000276944/vCPU-second and consumed memory $0.0000036667/GB-second. Event Functions cost $0.20/million requests and $0.0000133334/GB-second. Web memory accrues throughout environment lifetime, including gaps between requests.

The US API Gateway initial-tier inputs are $1/million for HTTP API and $3.50/million for REST API. Neither is an Ireland rate.

Cweb = requests + active-vCPU-seconds
     + sum(consumed-memory GB-seconds)
     + front door + logs + transfer + dependency-network changes

Cevent = gateway requests + Lambda requests + billed GB-seconds
       + the same applicable extras

Three hypothetical 30-day workloads

These are model inputs, not performance benchmarks. Different CPU allocations make equal elapsed durations an assumption to test.

WorkloadRequest measurementsHTTP + EventREST + EventWeb
Sporadic: 100,000200 ms elapsed; 10 ms CPU; 512 MB Event; 0.25 GB Web$0.51$0.76$0.32–$2.69
Steady I/O: 10 million200 ms elapsed; 10 ms CPU; 512 MB Event; 0.25 GB Web$50.89$75.89$33.71
CPU-heavy: 10 million200 ms elapsed and CPU; 1,769 MB Event; 0.5 GB Web$83.63$108.63$88.70

Steady cases assume one Web environment for 2,592,000 seconds. Sporadic lifetime ranges from one aggregate environment-hour to the full month: no idle-retention promise. Each additional full-month environment adds $2.38 at 0.25 GB, or $4.75 at 0.5 GB.

Assumptions beside these totals: exhausted free allowances, no caching, negligible request bodies, no WAF, existing DNS, no new NAT or proxy, and no incremental paid metrics.

All totals include $2.556/million shared marginal overhead: European CloudFront HTTPS $1.20, 10 GB response transfer × $0.085, and US-example logs: 1 GB ingestion × $0.50 plus 0.2 GB retained × $0.03/month. This is a mixed-location control, not a deployable Ireland estimate.

Calculate Ireland separately

For eu-west-1, supply explicit regional rates for every meter below. Record currency, rate date and architecture alongside them. US arithmetic does not establish UK migration economics.

This Python calculator accepts regional rates without US defaults. Request rates and free allowances use millions; other meters use seconds or GB-seconds. Set environment_gbs to the sum across all environments; Event allocation is MB ÷ 1,024.

def price(n, elapsed, cpu, event_gb, environment_gbs,
          card, extras, free=None):
    # card: region, currency, rate_date, architecture, rates
    for field in ("region", "currency", "rate_date", "architecture"):
        assert card[field]
    rates, free = card["rates"], free or {}
    def bill(key, quantity):
        return max(0, quantity - free.get(key, 0)) * rates[key]
    web = (bill("web_requests", n / 1e6)
           + bill("active_cpu", n * cpu)
           + bill("web_memory", environment_gbs))
    event = (bill("event_requests", n / 1e6)
             + bill("event_gbs", n * elapsed * event_gb))
    return {
        path: base + sum(extras[path].values())
        for path, base in {
            "web": web,
            "http": event + bill("http_requests", n / 1e6),
            "rest": event + bill("rest_requests", n / 1e6)
        }.items()
    }

Run free-tier-adjusted variants through free, and WAF-enabled variants through extras. Each path’s extras must include applicable front-door charges, application/system logs, gateway access logs, transfer and database/network changes. Do not count origin-to-CloudFront traffic again as internet delivery.

A cheaper function is not necessarily a cheaper API.

Share this quote

Move Express without rewriting your business logic

An engineer working at a laptop on an office desk.
Change the hosting boundary, not the business rules. Photo: Black_Claw (BY 2.0)

Keep one Express application and two entry points. A Lambda Web Functions migration should change transport, not business rules. Rollback becomes risky when fixes reach one implementation but not the other.

Separate the application from its listener

Export your existing application from app.js. Leave routes, middleware and business services there. Keep the existing Event Function adapter and its dependency available.

// app.js: minimal illustration; retain your existing routes here.
const express = require('express');
const app = express();

app.get('/healthz', (_req, res) => res.json({ ok: true }));

module.exports = app;

// event.js: existing Event Function entry point.
// This example assumes your project already uses this adapter.
exports.handler = require('@codegenie/serverless-express')({
  app: require('./app')
});

// index.js: Web Function entry point.
const app = require('./app');
const [host, port] = (
  process.env.AWS_LAMBDA_HTTP_ENDPOINT || '127.0.0.1:3000'
).split(':');

app.listen(Number(port), host);

Deploy a separate synthetic pilot

Use Node.js 24, deployment credentials, appropriate IAM permissions and an AWS CLI exposing lambda-web. This pilot contains no customer data. Its /probe route is instrumentation, not part of the production API.

mkdir cl-api-pilot && cd cl-api-pilot
npm init -y
npm install express
git add package.json package-lock.json
git commit -m "Pin pilot dependencies"
npm ci --omit=dev

Save this complete, standalone pilot as index.js:

const express = require('express');
const { randomUUID } = require('node:crypto');

const app = express();
const instance = randomUUID();
let active = 0;

app.get('/healthz', (_req, res) => res.json({ ok: true }));

app.get('/probe', async (req, res) => {
  const marker = String(req.query.marker || '');
  active++;
  const overlapping = active;
  try {
    await new Promise(resolve => setTimeout(resolve, 100));
    res.json({ marker, instance, overlapping });
  } finally {
    active--;
  }
});

const [host, port] = (
  process.env.AWS_LAMBDA_HTTP_ENDPOINT || '127.0.0.1:3000'
).split(':');

app.listen(Number(port), host);

active is deliberately process-wide instrumentation; marker is request-local. Lambda supplies the listen address through AWS_LAMBDA_HTTP_ENDPOINT. The deployment entry file is index.js, unless AWS_LAMBDA_NODEJS_ENTRYPOINT overrides it.

node index.js
# In another terminal:
curl http://127.0.0.1:3000/healthz

aws lambda-web deploy --name cl-api-pilot --code . --region eu-west-1 --auth-type IamAuth \
  --scaling-config '{"maxEnvironments":2}' \
  --throttle-config '{"rateLimit":100}'

The helper creates a dev endpoint with LatestRevision behaviour. These caps are pilot settings, not production sizing.

Download the synthetic pilot file bundle (JSON). Extract it into an empty directory, run sh setup.sh, then commit the generated lockfile:

node -e 'const fs=require("node:fs"); for(const [name,text] of Object.entries(JSON.parse(fs.readFileSync("pilot-bundle.json","utf8")))) fs.writeFileSync(name,text)'

Keep revision and edge configuration together

Retain the full configuration when creating each subsequent revision; do not submit only changed fields. This example fixes runtime, concurrency, role, timeout and environment explicitly:

{
  "runtime": "nodejs24.x",
  "maxConcurrencyPerEnvironment": 16,
  "role": "arn:aws:iam::123456789012:role/cl-api-pilot-execution",
  "timeout": 10,
  "environment": {
    "variables": {
      "NODE_ENV": "production",
      "AWS_LAMBDA_NODEJS_ENTRYPOINT": "index.js"
    }
  }
}

For CloudFront, disable caching for personalised responses. Preserve the bearer token separately before OAC signs the origin request; otherwise IAM signing replaces Authorization.

// CloudFront viewer-request function: associate with the API behaviour.
function handler(event) {
  var request = event.request;
  delete request.headers['x-app-authorization'];
  if (request.headers.authorization) {
    request.headers['x-app-authorization'] = {
      value: request.headers.authorization.value
    };
  }
  return request;
}

Use these CloudFormation fragments within the distribution configuration. PilotEndpoint, PilotOAC and BearerForwarder reference resources in the same stack.

{
  "PilotOAC": {
    "Type": "AWS::CloudFront::OriginAccessControl",
    "Properties": {
      "OriginAccessControlConfig": {
        "Name": "cl-api-pilot",
        "OriginAccessControlOriginType": "lambda",
        "SigningBehavior": "always",
        "SigningProtocol": "sigv4"
      }
    }
  },
  "Origin": {
    "Id": "pilot",
    "DomainName": { "Fn::GetAtt": ["PilotEndpoint", "DomainName"] },
    "OriginAccessControlId": { "Ref": "PilotOAC" },
    "CustomOriginConfig": {
      "OriginProtocolPolicy": "https-only",
      "HTTPSPort": 443,
      "OriginSSLProtocols": ["TLSv1.2"]
    }
  },
  "CacheBehaviour": {
    "TargetOriginId": "pilot",
    "ViewerProtocolPolicy": "https-only",
    "AllowedMethods": ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"],
    "CachedMethods": ["GET", "HEAD"],
    "CachePolicyId": "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
    "OriginRequestPolicyId": "b689b0a8-53d0-40ab-baf2-68738e2966ac",
    "FunctionAssociations": [{
      "EventType": "viewer-request",
      "FunctionARN": { "Fn::GetAtt": ["BearerForwarder", "FunctionARN"] }
    }]
  }
}

The policy IDs select CachingDisabled and AllViewerExceptHostHeader. Authenticate x-app-authorization in the application. Restrict origin access to the distribution; do not make the endpoint public.

{
  "Principal": { "Service": "cloudfront.amazonaws.com" },
  "Condition": {
    "StringEquals": {
      "AWS:SourceArn": {
        "Fn::Sub": "arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${ApiDistribution}"
      }
    }
  }
}

Merge that principal and condition into the endpoint’s invocation permission, retaining its required action and resource. Keep this a phased migration with a reversible boundary, not an excuse to rewrite the API.

Prove isolation and performance before moving customers

Two engineers seated at a desk with several monitors.
A passing deployment is only the start of acceptance. Photo: U.S. Geological Survey (CC0 1.0)

Concurrent async handlers share a Node.js process. Request-local state must stay request-local; synchronous CPU-heavy work blocks everybody’s event loop. A quiet staging environment will not expose either problem.

Make the isolation test fail deliberately

Save these Node.js 20+ scripts, or download isolation.mjs and fixture.mjs. The fixture reports process identity and in-flight requests. Keep it confined to the synthetic pilot.

// isolation.mjs
import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';

const base = process.env.BASE_URL;
assert.ok(base, 'Set BASE_URL');
let inconclusive = false;

for (let round = 1; round <= 10; round++) {
  const markers = Array.from({ length: 64 }, () => randomUUID());
  const results = await Promise.all(markers.map(async marker => {
    const response = await fetch(new URL('/probe', base), {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ marker }),
      signal: AbortSignal.timeout(15000)
    });
    assert.equal(response.status, 200);
    const body = await response.json();
    assert.equal(body.marker, marker, 'Identity mismatch');
    assert.equal(typeof body.instance, 'string');
    assert.ok(body.instance.length);
    assert.ok(Number.isInteger(body.overlapping));
    return body;
  }));

  const counts = new Map();
  for (const r of results) {
    counts.set(r.instance, (counts.get(r.instance) || 0) + 1);
  }
  const overlap = results.some(r =>
    counts.get(r.instance) > 1 && r.overlapping > 1
  );
  console.log({ round, overlap, instances: counts.size });
  if (!overlap) inconclusive = true;
}

if (inconclusive) {
  console.error('INCONCLUSIVE: at least one round lacked observed overlap');
  process.exitCode = 2;
} else {
  console.log('PASS: observed isolation only');
}
// fixture.mjs
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import { setTimeout as delay } from 'node:timers/promises';

const instance = randomUUID();
let active = 0;
let currentMarker; // Deliberately unsafe when RACE=1.

createServer(async (req, res) => {
  res.setHeader('Cache-Control', 'no-store');
  if (req.url === '/healthz') {
    res.end('ok');
    return;
  }
  if (req.url !== '/probe' || req.method !== 'POST') {
    res.writeHead(404).end();
    return;
  }

  let counted = false;
  try {
    let raw = '';
    for await (const chunk of req) raw += chunk;
    const { marker } = JSON.parse(raw);
    currentMarker = marker;
    active++;
    counted = true;
    await delay(100);
    const body = {
      marker: process.env.RACE === '1' ? currentMarker : marker,
      instance,
      overlapping: active
    };
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(body));
  } catch {
    res.writeHead(400).end();
  } finally {
    if (counted) active--;
  }
}).listen(Number(process.env.PORT || 3000), '0.0.0.0');

Run locally first, using separate terminals. Restart the fixture with RACE=1; the shared currentMarker must cause a mismatch.

node fixture.mjs
BASE_URL=http://127.0.0.1:3000 node isolation.mjs

# Stop the fixture, then start the negative control:
RACE=1 node fixture.mjs
BASE_URL=http://127.0.0.1:3000 node isolation.mjs

Repeat against the synthetic CloudFront pilot using BASE_URL="$PILOT_URL" node isolation.mjs. A mismatch fails; missing overlap is inconclusive, never a pass. Remote results cover only observed instances, not whole-application safety.

For the real API, derive tenant identity from validated credentials, never a caller-supplied tenant header. Test two tenants across authorisation and cache boundaries, including cross-tenant reads and writes.

Measure useful work, not just health checks

Run against both baseline and candidate, changing BASE_URL:

npx autocannon -c 1 -d 60 "$BASE_URL/healthz"
npx autocannon -c 16 -d 120 "$BASE_URL/healthz"
npx autocannon -c 64 -d 120 "$BASE_URL/healthz"

These are concurrency tests, not fixed arrival-rate tests. Health checks represent neither database nor CPU work. Add authenticated representative read/write routes and a sustained soak.

Proposed acceptance gates are team policy, not AWS guarantees:

  • Zero identity leaks; no unexpected authentication success.
  • p95 within the agreed SLO and no more than 10% above baseline.
  • 5xx increase below 0.1 percentage points.
  • Database connections below their agreed budget.
  • Observed cost within the modelled range.

Capture cold and warm runs, 429s, event-loop delay, memory, pool wait and connection counts. Log build identifiers: platform Web metrics do not separate revisions. Treat operational visibility and incident readiness as Lambda Web Functions migration requirements.

Roll out one route and keep two rollback paths

An engineer writing in a notebook beside a laptop.
Keep the old request path until rollback has been rehearsed. Photo: blakespot (BY 2.0)

A Lambda Web Functions migration should start with one bounded route, not a DNS switch. Use your existing CloudFront front door, or deliberately introduce one. Retain the API Gateway origin and its working deployment.

Test through a separate pilot hostname first. Move one selected production path only after regional, authorisation, isolation, performance and cost gates pass. Never shadow writes or duplicate side effects. Keep schema changes backward-compatible while both architectures operate.

Architecture rollback: restore the origin

Before cutover, export DISTRIBUTION_ID, PATH_PATTERN and API_GATEWAY_ORIGIN_ID for your selected behaviour. Save the distribution configuration and ETag:

aws cloudfront get-distribution-config \
  --id "$DISTRIBUTION_ID" > before-cutover.json

Keep this rollback.sh. It fetches the current configuration, changes only the selected behaviour’s origin, and submits the current ETag. Freeze concurrent distribution changes during rollback.

#!/usr/bin/env bash
set -euo pipefail
: "${DISTRIBUTION_ID:?}" "${PATH_PATTERN:?}" "${API_GATEWAY_ORIGIN_ID:?}"
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

aws cloudfront get-distribution-config \
  --id "$DISTRIBUTION_ID" > "$work/current.json"

etag=$(jq -r '.ETag' "$work/current.json")

jq -e --arg path "$PATH_PATTERN" \
  --arg origin "$API_GATEWAY_ORIGIN_ID" '
  .DistributionConfig
  | if ([.CacheBehaviors.Items[]?
          | select(.PathPattern == $path)] | length) != 1
    then error("Expected exactly one matching path behaviour")
    else .CacheBehaviors.Items |= map(
      if .PathPattern == $path
      then .TargetOriginId = $origin
      else . end)
    end
' "$work/current.json" > "$work/restore.json"

aws cloudfront update-distribution \
  --id "$DISTRIBUTION_ID" --if-match "$etag" \
  --distribution-config "file://$work/restore.json"

aws cloudfront wait distribution-deployed \
  --id "$DISTRIBUTION_ID"

Propagation is not instantaneous. After deployment, smoke-test the production path, including authorisation and error responses.

Revision rollback: restore the code

Web Function revision weights cannot send traffic back to API Gateway. They only select Web Function revisions:

aws lambda-web update-web-function-endpoint \
  --function-name cl-api-pilot --endpoint-name dev \
  --auto-deployment-mode Disabled \
  --revision-weights "[{\"revisionId\":\"$KNOWN_GOOD_REVISION\",\"weight\":100}]" \
  --region eu-west-1

aws lambda-web wait endpoint-updated \
  --function-name cl-api-pilot --endpoint-name dev \
  --region eu-west-1

Run endpoint smoke tests afterwards. In-flight requests can finish on the outgoing revision.

Retain API Gateway for non-Node runtimes, unmet regional requirements, irreplaceable gateway controls or unsafe shared state. Keep it when savings cannot repay migration effort, too.

For a scoped assessment, talk to a Cloudline cloud engineer. Request a route and authorisation inventory, regional go/no-go decision, workload cost model, connection budget, pilot acceptance report and rollback runbook. Bring request volumes, latency distributions, current bills and residency requirements.

Final thoughts

Keep the current API when a mandatory gate fails. Pilot when the architecture fits but the evidence is missing. Migrate only when the regional decision, security controls, isolation tests and measured economics agree. Cloudline Consulting can scope an API architecture and migration assessment around those decisions, with a cost model, connection budget and rollback runbook rather than a predetermined destination. Talk to a cloud engineer with your current architecture, traffic profile and operational requirements.

Frequently asked questions

Can Lambda Web Functions replace API Gateway?
They can replace the gateway in an HTTP request path because they provide their own HTTPS endpoint. That does not replace every gateway responsibility. Authentication, authorisation, validation and operational controls still need owners. AWS documents CloudFront as the front door for custom domains and WAF protection in the gateway-free design.
Are Lambda Web Functions cheaper than API Gateway and Lambda?
Not automatically. Web Functions bill requests, active CPU, consumed memory over environment lifetime and transfer. Removing gateway requests can help, but CloudFront, logging and idle memory affect the result. Compare measured workloads and complete architectures; AWS's published Northern Virginia examples are not Ireland pricing quotes.
What is the difference between Lambda Web Functions and Event Functions?
Web Functions run a port-listening Node.js HTTP server and handle concurrent requests within an environment. Event Functions use the existing handler model and remain appropriate for event-driven processing. Their billing differs too: Web Functions separate active CPU and consumed memory, while Event Functions charge invocation duration against configured memory.
Are Lambda Web Functions available in the UK?
The 12 October 2026 launch announcement lists Ireland, Northern Virginia, Ohio, Oregon and Tokyo. It does not list London. Ireland can serve UK users, but it is not a UK region. A requirement for UK-only execution therefore rules out the announced launch footprint, regardless of acceptable network latency.
Can I use a custom domain and AWS WAF with Web Functions?
Yes, through the documented CloudFront integration. Put CloudFront in front of an IamAuth endpoint, configure origin access control and restrict the function's resource policy to the intended distribution. Forward application credentials separately. An ApplicationManaged endpoint remains directly callable, so merely adding CloudFront does not prevent front-door bypass.
Can I migrate an existing Express application without rewriting it?
You can retain the Express application and start it as an HTTP server using the address supplied by AWS_LAMBDA_HTTP_ENDPOINT. The supported runtime is currently Node.js 24. However, removing an adapter does not establish production readiness: request-global state, authentication, database pools and deployment rollback still need explicit acceptance tests.

Sources and further reading

Found this useful? Share it or cite it: