Skip to content

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