How to run TypeScript on AWS Lambda with Node.js 22 or 24, bundle with esbuild, cut cold starts, choose ESM or CommonJS, and when an always-on TypeScript API on your own AWS account is a better fit.
AWS Lambda can run TypeScript, but only after you transpile it to JavaScript for a Node.js runtime. The work that trips teams up is the packaging: choosing a supported runtime, bundling with esbuild or webpack, picking ESM or CommonJS, and keeping cold starts under control as the dependency graph grows.
This guide covers a practical TypeScript Lambda setup for 2026, what cold starts actually respond to, when Lambda fits, and when an always-on TypeScript API with infrastructure from code fits better.
Lambda provides managed Node.js runtimes on Amazon Linux 2023. As of late 2026 the supported identifiers for production work are nodejs22.x and nodejs24.x. Node.js 26 is listed as a public preview and is not covered by the Lambda SLA. Node.js 20 (nodejs20.x) was deprecated on 30 April 2026, so new functions should target 22 or 24.
Set your TypeScript and esbuild target to the runtime you choose. A tsconfig aimed at ES2022 with "module": "ESNext" pairs well with an ESM Lambda on Node 22 or 24. Run tsc --noEmit for type checks, then let esbuild emit the JavaScript: esbuild does not type-check on its own, which the Lambda TypeScript guide calls out.
A minimal handler looks like this after you install the type package:
// src/handler.ts
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
export async function handler(
event: APIGatewayProxyEventV2,
): Promise<APIGatewayProxyResultV2> {
const name = event.queryStringParameters?.name ?? "world";
return {
statusCode: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify({ message: `hello ${name}` }),
};
}
# Bundle to a single ESM file for nodejs22.x
npx esbuild src/handler.ts \
--bundle \
--platform=node \
--target=node22 \
--format=esm \
--outfile=dist/handler.mjs \
--minify
Point the function handler at handler.handler and upload dist/ as a zip, or let SAM or CDK run the same esbuild step. Bundle only what you need: tree-shaking with esbuild usually beats shipping node_modules wholesale. Webpack still works, and teams with an existing webpack pipeline can keep it, but for new Lambdas esbuild is the default in AWS tooling and produces smaller cold-start-friendly output.
The Lambda console creates index.mjs for new Node functions, which is ESM. ESM lets the runtime load only what the import graph needs and plays better with tree-shaken esbuild output. CommonJS (require / module.exports) remains supported and is fine when a dependency is CJS-only. Mixing the two without care produces ERR_REQUIRE_ESM or dual-package hazard bugs, so pick one format per function and configure esbuild's --format to match.
A cold start is the time to create a new execution environment, load your bundle and run top-level code before the handler runs. Warm starts reuse that environment. For TypeScript the controllable costs are package size and import-time work:
NODE_EXTRA_CA_CERTS if an RDS or similar connection requires them.Provisioned concurrency removes cold starts for a reserved pool of environments and adds a steady cost. Most TypeScript APIs that need consistently low latency for every request outgrow the function model before they need that knob.
Lambda is a strong fit for spiky, short TypeScript work: webhooks, image or file transforms, SQS or EventBridge handlers that finish in seconds, and glue between AWS services. You pay per request and per GB-second, and idle costs nothing.
It is a weak fit for always-on TypeScript backends that hold Postgres connection pools, run multi-minute jobs, serve steady traffic all day, or need local parity with queues, cron and object storage next to the API. Those systems end up as many functions, many IAM roles and a separate local setup (SAM, LocalStack), and the 15-minute invocation ceiling forces work to be split further.
Encore is infrastructure from code for TypeScript and Go, with a platform that provisions and deploys into your own AWS or GCP account. You declare APIs, Postgres databases, Pub/Sub topics, object storage buckets and cron jobs in application code. On AWS those services run as containers (Fargate by default), not as Lambda functions. Encore does not run your handlers on Lambda.

Run in your terminal to get started locally.
// greet/encore.service.ts
import { Service } from "encore.dev/service";
export default new Service("greet");
// greet/greet.ts
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { CronJob } from "encore.dev/cron";
const db = new SQLDatabase("greetings", { migrations: "./migrations" });
export const hello = api(
{ expose: true, method: "GET", path: "/hello/:name" },
async ({ name }: { name: string }): Promise<{ message: string }> => {
await db.exec`INSERT INTO visits (name) VALUES (${name})`;
return { message: `hello ${name}` };
},
);
// Cron does not run locally; it is triggered in deployed environments.
export const prune = api({}, async (): Promise<{ ok: boolean }> => {
await db.exec`DELETE FROM visits WHERE created_at < now() - interval '30 days'`;
return { ok: true };
});
const _ = new CronJob("prune-visits", {
title: "Prune old visits",
schedule: "0 3 * * *",
endpoint: prune,
});
The migration next to the database declaration creates the table:
-- greet/migrations/1_create_visits.up.sql
CREATE TABLE visits (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
On deploy to your AWS account Encore provisions the Fargate service behind an Application Load Balancer, the RDS database, and an IAM role for the service, as described in the AWS infrastructure docs. The TypeScript API, database and cron docs cover the primitives above. Connecting the account is in deploying to your own cloud.
An AI coding agent can add an endpoint, a migration and a cron job in the same change and test the endpoint and migration locally, while the cron job only fires once deployed.
Best fit: TypeScript teams that want an always-on API with Postgres, queues, object storage and cron in their own AWS account, developed locally as ordinary services rather than per-event functions.
No. The Lambda Node.js runtime executes JavaScript. You transpile TypeScript with esbuild or tsc, then deploy the JavaScript output as a zip or container image. AWS SAM and the AWS CDK both use esbuild for this step.
Prefer nodejs22.x or nodejs24.x. Node.js 20 (nodejs20.x) reached deprecation on 30 April 2026, so new projects should not start there. Match your esbuild target to the runtime you pick.
Ship a small bundle: minify with esbuild, tree-shake unused imports, keep handlers free of heavy module-level work, and prefer ESM over a large CommonJS graph. Initialize AWS SDK clients outside the handler so warm invocations reuse them.
Use Encore when you want always-on TypeScript services with Postgres, queues, object storage and cron declared in code and provisioned in your own AWS account. Encore runs those services on containers such as Fargate, not as Lambda functions.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.