AWS SDK v3 TypeScript Reference: S3, DynamoDB, SES, SQS, SSM & Lambda
AWS SDK v3 for JavaScript/TypeScript is modular — you only install the clients you need. The key patterns: use the @aws-sdk/client-* packages, pass credentials via environment variables (never hardcode), and use paginate* helpers for lists. The biggest gotcha from v2: commands are now classes, and responses don’t have a .promise() — just await the send() call directly.
1. Auth, Config & Client Setup
Credentials chain, region, client instantiation, and local development with profiles
// Install only what you need (v3 is modular):
// npm install @aws-sdk/client-s3 @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
import { S3Client } from "@aws-sdk/client-s3";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
// Credentials loaded automatically in this order:
// 1. Explicit credentials in code (never do this in production)
// 2. Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
// 3. ~/.aws/credentials + AWS_PROFILE (local dev)
// 4. IAM role / ECS task role / Lambda execution role (production)
// Standard client (uses default credential chain):
const s3 = new S3Client({ region: "us-east-1" });
// Override region from env:
const s3 = new S3Client({ region: process.env.AWS_REGION ?? "eu-west-1" });
// Local development with LocalStack:
const s3Local = new S3Client({
region: "us-east-1",
endpoint: "http://localhost:4566",
credentials: { accessKeyId: "test", secretAccessKey: "test" },
forcePathStyle: true, // required for LocalStack S3
});
// Multiple profiles in ~/.aws/credentials:
// [default] [staging] [prod]
// AWS_PROFILE=staging node script.js
// Or:
import { fromIni } from "@aws-sdk/credential-providers";
const client = new S3Client({ credentials: fromIni({ profile: "staging" }) });
2. S3 — Upload, Download, Presigned URLs
PutObject, GetObject, presigned URLs, multipart upload for large files
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand,
ListObjectsV2Command } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { Upload } from "@aws-sdk/lib-storage"; // multipart upload
const s3 = new S3Client({ region: "us-east-1" });
// Upload (small files):
await s3.send(new PutObjectCommand({
Bucket: "my-bucket",
Key: "uploads/photo.jpg",
Body: fileBuffer,
ContentType: "image/jpeg",
ServerSideEncryption: "AES256",
Metadata: { userId: "123", originalName: "photo.jpg" },
}));
// Multipart upload (recommended for files >5MB, required for >100MB):
const upload = new Upload({
client: s3,
params: { Bucket: "my-bucket", Key: "large-file.zip", Body: readableStream },
queueSize: 4, // parallel parts
partSize: 1024 * 1024 * 10, // 10MB per part
});
upload.on("httpUploadProgress", (progress) => {
console.log(Math.round((progress.loaded! / progress.total!) * 100) + "%");
});
await upload.done();
// Download:
const { Body, ContentLength } = await s3.send(new GetObjectCommand({
Bucket: "my-bucket", Key: "uploads/photo.jpg",
}));
const buffer = Buffer.from(await Body!.transformToByteArray());
// Or stream directly: Body!.pipe(response)
// Presigned URL (time-limited access without credentials):
const url = await getSignedUrl(s3, new GetObjectCommand({
Bucket: "my-bucket", Key: "uploads/photo.jpg",
}), { expiresIn: 3600 }); // 1 hour
// Presigned PUT (let clients upload directly to S3):
const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({
Bucket: "my-bucket", Key: "uploads/user-upload.jpg", ContentType: "image/jpeg",
}), { expiresIn: 300 });
// List with pagination:
import { paginateListObjectsV2 } from "@aws-sdk/client-s3";
for await (const page of paginateListObjectsV2({ client: s3 }, { Bucket: "my-bucket", Prefix: "uploads/" })) {
for (const obj of page.Contents ?? []) {
console.log(obj.Key, obj.Size);
}
}
3. DynamoDB — Document Client Patterns
DynamoDBDocumentClient (no marshalling), GetItem, PutItem, query, update expressions
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand, PutCommand, UpdateCommand,
DeleteCommand, QueryCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({ region: "us-east-1" }), {
marshallOptions: { removeUndefinedValues: true }, // strip undefined fields
});
// Get item (must provide full primary key):
const { Item } = await ddb.send(new GetCommand({
TableName: "Users",
Key: { pk: "USER#123", sk: "PROFILE" },
ConsistentRead: true,
}));
// Put item:
await ddb.send(new PutCommand({
TableName: "Users",
Item: { pk: "USER#123", sk: "PROFILE", name: "Alice", email: "alice@example.com" },
ConditionExpression: "attribute_not_exists(pk)", // fail if already exists
}));
// Update (only specified fields):
await ddb.send(new UpdateCommand({
TableName: "Users",
Key: { pk: "USER#123", sk: "PROFILE" },
UpdateExpression: "SET #n = :name, updatedAt = :ts ADD loginCount :one",
ExpressionAttributeNames: { "#n": "name" }, // #n avoids reserved word "name"
ExpressionAttributeValues: { ":name": "Alice Smith", ":ts": Date.now(), ":one": 1 },
}));
// Query (uses index — much cheaper than Scan):
const { Items } = await ddb.send(new QueryCommand({
TableName: "Users",
IndexName: "EmailIndex",
KeyConditionExpression: "email = :email",
ExpressionAttributeValues: { ":email": "alice@example.com" },
Limit: 1,
}));
// Transaction (all-or-nothing, up to 100 items):
await ddb.send(new TransactWriteCommand({
TransactItems: [
{ Put: { TableName: "Orders", Item: { pk: "ORDER#456", total: 99.99 } } },
{ Update: { TableName: "Inventory", Key: { pk: "ITEM#789" },
UpdateExpression: "ADD qty :neg", ExpressionAttributeValues: { ":neg": -1 } } },
],
}));
4. SES, SNS & SQS — Messaging Patterns
Send emails with SES v3, SNS notifications, and SQS queue polling
// SES v3 — send email:
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
const ses = new SESv2Client({ region: "us-east-1" });
await ses.send(new SendEmailCommand({
FromEmailAddress: "noreply@example.com",
Destination: { ToAddresses: ["user@example.com"] },
Content: {
Simple: {
Subject: { Data: "Welcome to ReleaseRun" },
Body: {
Html: { Data: "Welcome
Thanks for signing up!
" },
Text: { Data: "Welcome! Thanks for signing up." },
},
},
},
}));
// SQS — send + receive + delete:
import { SQSClient, SendMessageCommand, ReceiveMessageCommand, DeleteMessageCommand } from "@aws-sdk/client-sqs";
const sqs = new SQSClient({ region: "us-east-1" });
const QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789/my-queue";
// Send:
await sqs.send(new SendMessageCommand({
QueueUrl: QUEUE_URL,
MessageBody: JSON.stringify({ userId: "123", action: "send-welcome-email" }),
DelaySeconds: 0,
}));
// Poll + process + delete (always delete after successful processing):
const { Messages } = await sqs.send(new ReceiveMessageCommand({
QueueUrl: QUEUE_URL, MaxNumberOfMessages: 10, WaitTimeSeconds: 20,
}));
for (const msg of Messages ?? []) {
try {
const body = JSON.parse(msg.Body!);
await processMessage(body);
await sqs.send(new DeleteMessageCommand({
QueueUrl: QUEUE_URL, ReceiptHandle: msg.ReceiptHandle!,
}));
} catch (e) {
console.error("Failed to process message:", e);
// Don't delete — message returns to queue after VisibilityTimeout
}
}
5. Lambda, SSM & Error Handling
Lambda handler patterns, SSM Parameter Store for secrets, and SDK error handling
// SSM Parameter Store (better than env vars for secrets — rotatable, auditable):
import { SSMClient, GetParameterCommand, GetParametersByPathCommand } from "@aws-sdk/client-ssm";
const ssm = new SSMClient({ region: "us-east-1" });
// Get single secret:
const { Parameter } = await ssm.send(new GetParameterCommand({
Name: "/myapp/prod/database-url",
WithDecryption: true, // for SecureString parameters
}));
const dbUrl = Parameter!.Value!;
// Get all params under a path (e.g. at startup):
const params: Record = {};
for await (const page of paginateGetParametersByPath({ client: ssm }, {
Path: "/myapp/prod/",
WithDecryption: true,
Recursive: true,
})) {
for (const p of page.Parameters ?? []) {
const key = p.Name!.split("/").at(-1)!;
params[key] = p.Value!;
}
}
// Lambda handler with proper typing:
import type { APIGatewayProxyHandlerV2, SQSHandler } from "aws-lambda";
export const handler: APIGatewayProxyHandlerV2 = async (event) => {
const body = JSON.parse(event.body ?? "{}");
return { statusCode: 200, body: JSON.stringify({ ok: true }) };
};
// SDK error handling:
import { S3ServiceException } from "@aws-sdk/client-s3";
try {
await s3.send(new GetObjectCommand({ Bucket: "b", Key: "k" }));
} catch (e) {
if (e instanceof S3ServiceException) {
if (e.name === "NoSuchKey") console.log("Key not found");
if (e.$retryable) console.log("Transient error, retry");
console.log(e.$metadata.httpStatusCode); // HTTP status
}
throw e;
}
Track Node.js and cloud tooling at ReleaseRun. Related: TypeScript Reference | GitHub Actions Reference | Kubernetes Reference
🔍 Free tool: npm Package Health Checker — check AWS SDK v3 packages for known CVEs and active maintenance — @aws-sdk packages update frequently.
Founded
2023 in London, UK
Contact
hello@releaserun.com