AWS
S3
Node.js
file upload
binary file

Upload a binary file to S3 using AWS SDK for Node.js

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Uploading binary files to S3 from Node.js is straightforward when credentials, content type, and error handling are explicit. Problems usually come from implicit defaults, such as unknown MIME type, missing region, or loading huge files fully into memory. A production-safe approach uses AWS SDK v3, validates inputs, and chooses the right upload method for file size.

Minimal Binary Upload With SDK v3

Use S3Client and PutObjectCommand for small to medium files.

javascript
1import { readFileSync } from "node:fs";
2import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
3
4const s3 = new S3Client({ region: "us-east-1" });
5
6async function uploadFile() {
7  const body = readFileSync("./assets/photo.jpg");
8
9  const cmd = new PutObjectCommand({
10    Bucket: "my-bucket",
11    Key: "uploads/photo.jpg",
12    Body: body,
13    ContentType: "image/jpeg",
14  });
15
16  await s3.send(cmd);
17  console.log("upload complete");
18}
19
20uploadFile().catch(console.error);

This works well for files that fit comfortably in memory.

Prefer Streams for Large Files

For larger binaries, stream from disk instead of using readFileSync.

javascript
1import { createReadStream } from "node:fs";
2import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
3
4const s3 = new S3Client({ region: "us-east-1" });
5
6async function uploadLarge(path, key) {
7  const stream = createReadStream(path);
8
9  await s3.send(
10    new PutObjectCommand({
11      Bucket: "my-bucket",
12      Key: key,
13      Body: stream,
14      ContentType: "application/octet-stream",
15    })
16  );
17}

Streaming reduces memory spikes and is safer under concurrency.

Add Metadata and Integrity Signals

When files are consumed by downstream services, include metadata such as checksum and content disposition.

javascript
1import { createHash } from "node:crypto";
2import { readFileSync } from "node:fs";
3
4const data = readFileSync("./assets/report.pdf");
5const sha256 = createHash("sha256").update(data).digest("hex");
6
7await s3.send(
8  new PutObjectCommand({
9    Bucket: "my-bucket",
10    Key: "reports/2026-03/report.pdf",
11    Body: data,
12    ContentType: "application/pdf",
13    Metadata: {
14      sha256,
15      source: "batch-job",
16    },
17  })
18);

Metadata helps verification and traceability during incident analysis.

Credentials and Permissions

Use IAM roles when running on AWS infrastructure and avoid embedding credentials in source code. Minimum required permissions for upload usually include s3:PutObject and, if checking result, s3:HeadObject.

Local development options:

  • AWS profile with AWS_PROFILE.
  • Temporary credentials in environment variables.
  • LocalStack for integration tests.

Explicit region configuration is required. Missing region leads to confusing endpoint errors.

Handle Errors by Category

Distinguish between retryable network errors and permanent validation errors.

javascript
1try {
2  await uploadFile();
3} catch (err) {
4  if (err.name === "NoSuchBucket") {
5    console.error("bucket does not exist");
6  } else if (err.name === "AccessDenied") {
7    console.error("insufficient IAM permissions");
8  } else {
9    console.error("upload failed", err);
10  }
11}

Categorized error handling keeps operational triage faster and clearer.

Multipart Upload for Very Large Files

For very large objects, multipart upload is more reliable. AWS SDK v3 provides @aws-sdk/lib-storage upload helper that handles parts and retries.

javascript
1import { Upload } from "@aws-sdk/lib-storage";
2import { createReadStream } from "node:fs";
3
4const uploader = new Upload({
5  client: s3,
6  params: {
7    Bucket: "my-bucket",
8    Key: "videos/big-file.mp4",
9    Body: createReadStream("./big-file.mp4"),
10    ContentType: "video/mp4",
11  },
12});
13
14await uploader.done();

Use this when single PutObject becomes unstable due to file size or network conditions.

Track upload latency and failure rates by object size so retry and timeout tuning is driven by measured behavior, not guesswork.

Common Pitfalls

A common pitfall is uploading binary content as a string, which corrupts file bytes. Another issue is omitting ContentType, causing downstream clients to mis-handle files. Teams also often run uploads with broad IAM credentials instead of least privilege policies. Reading large files into memory in high-concurrency services can trigger out-of-memory failures. Finally, many implementations log full exception objects without context fields like bucket, key, and request ID, making production debugging slower.

Summary

  • Use AWS SDK v3 S3Client with explicit region and bucket settings.
  • Use PutObject for small files and stream or multipart for large files.
  • Set ContentType and metadata intentionally for downstream correctness.
  • Apply least-privilege IAM and avoid hardcoded credentials.
  • Add structured error handling and integrity checks for production reliability.

Course illustration
Course illustration

All Rights Reserved.