@aws-sdk v3
Use modular AWS SDK v3 clients in Node.js Lambdas and containers - import only what you need, configure middleware once, reuse clients across invocations.
Search across all documentation pages
Use modular AWS SDK v3 clients in Node.js Lambdas and containers - import only what you need, configure middleware once, reuse clients across invocations.
Quick-reference recipe card - copy-paste ready.
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: process.env.AWS_REGION });
export async function getObjectText(bucket: string, key: string) {
const res = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
return await res.Body?.transformToString();
}When to reach for this: Any AWS service call from Node 24 or Lambda nodejs24.x. Do not add aws-sdk v2.
// src/aws/clients.ts
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand, PutCommand } from "@aws-sdk/lib-dynamodb";
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
import { NodeHttpHandler } from "@smithy/node-http-handler";
const requestHandler = new NodeHttpHandler({
connectionTimeout: 3_000,
requestTimeout: 10_000,
});
const ddbDoc = DynamoDBDocumentClient.from(
new DynamoDBClient({ maxAttempts: 3, requestHandler }),
{ marshallOptions: { removeUndefinedValues: true } }
);
const ssm = new SSMClient({ maxAttempts: 3 });
export async function getOrder(id: string) {
const res = await ddbDoc.send(
new GetCommand({ TableName: process.env.TABLE_NAME!, Key: { id } })
);
return res.Item;
}
export async function putOrder(item: Record<string, unknown>) {
await ddbDoc.send(
new PutCommand({ TableName: process.env.TABLE_NAME!, Item: item })
);
}
let cachedApiKey: string | undefined;
export async function getApiKey() {
if (cachedApiKey) return cachedApiKey;
const res = await ssm.send(
new GetParameterCommand({ Name: "/prod/api/KEY", WithDecryption: true })
);
cachedApiKey = res.Parameter?.Value;
return cachedApiKey!;
}// src/handler.ts
import type { APIGatewayProxyHandlerV2 } from "aws-lambda";
import { getOrder } from "./aws/clients.js";
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
const id = event.pathParameters?.id!;
const order = await getOrder(id);
return {
statusCode: order ? 200 : 404,
body: JSON.stringify(order ?? { error: "not found" }),
};
};What this demonstrates:
@aws-sdk/client-dynamodb, @aws-sdk/client-ssm)NodeHttpHandler timeouts and init-scoped client reuse| Aspect | AWS SDK v2 | AWS SDK v3 |
|---|---|---|
| Import | import AWS from "aws-sdk" | @aws-sdk/client-s3 |
| Bundle size | Entire SDK | Per-service tree-shakeable |
| API | .promise() | client.send(new Command()) |
| Middleware | Limited | Smithy middleware stack |
Every operation is a command class:
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.QUEUE_URL!,
MessageBody: JSON.stringify({ orderId: "42" }),
}));Commands are immutable; safe to construct per request.
import { S3Client } from "@aws-sdk/client-s3";
const s3 = new S3Client({});
s3.middlewareStack.add(
(next) => async (args) => {
const start = Date.now();
const result = await next(args);
console.log(JSON.stringify({ awsCall: args.request?.hostname, ms: Date.now() - start }));
return result;
},
{ step: "finalizeRequest", name: "logLatency" }
);Use middleware for cross-cutting logging, not business logic.
Lambda Node.js 18+ runtimes include AWS SDK for JavaScript v3. You can mark @aws-sdk/* as external in esbuild to shrink deployment packages. Pin SDK versions in CI if you rely on runtime-bundled SDK behavior.
On Lambda, credentials come from the execution role automatically. Local dev uses shared credentials file or SSO:
aws sso login --profile dev
AWS_PROFILE=dev npm run invoke:local@aws-sdk/client-s3 and entire lib-dynamodb unused - still better than v2, but audit imports. Fix: one client module per domain.maxAttempts on flaky networks - transient 503s fail invocations. Fix: maxAttempts: 3 default is often enough; tune per service.GetObject into memory - OOM on big files. Fix: stream Body to S3 upload or disk.PermanentRedirect on S3. Fix: set region on client or AWS_REGION env.| Alternative | Use When | Don't Use When |
|---|---|---|
| @aws-sdk v3 modular | Default for all AWS calls | Never for new code |
| AWS SDK v2 | Legacy maintenance only | New Lambdas |
| AWS Data API | Aurora Serverless SQL without VPC | Need full PG features |
| AWS CDK L2 constructs | Infra provisioning | Application-level S3/GetObject calls |
For Lambda, you can externalize SDK if using a managed runtime that includes it. For containers, install only the clients you need in the image.
Use paginator helpers: import { paginateListObjectsV2 } from "@aws-sdk/client-s3" or loop on NextToken in commands.
Yes. Command input types are generated from Smithy models.
Create an STS client, assume role, pass returned credentials to service clients via credentials config for cross-account access.
Same pattern: @aws-sdk/client-secrets-manager + GetSecretValueCommand, cached at init. See Secrets Managers.
Identical code paths. IAM task role replaces Lambda execution role for credentials.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026