— 2 min read
Setup AWS S3 bucket for Node.js
Create a properly scoped S3 bucket and IAM user, and upload files from Node with the AWS SDK v3.
- AWS
- Node.js
- S3
The part of S3 setup that actually matters isn't the SDK call — it's getting the bucket permissions and IAM policy right so you're not left with a publicly-open bucket or an over-privileged access key.
1. Create the bucket
In the S3 console (or via aws s3api create-bucket), create a bucket with
Block all public access left on. Files should be reached through
signed URLs or your own API, not by making the bucket public.
2. Create a scoped IAM user
Don't reuse root or admin credentials in your app. Create an IAM user with
a policy limited to exactly what the app needs — usually just
PutObject/GetObject on one bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}Generate an access key for that user and store it as environment variables — never commit it to the repo.
3. Install the SDK
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner4. Upload a file
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const s3 = new S3Client({ region: "us-east-1" });
async function uploadFile(key, body, contentType) {
await s3.send(
new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
Body: body,
ContentType: contentType,
})
);
}5. Generate a signed URL for downloads
Since the bucket isn't public, give clients a time-limited signed URL instead of a direct object URL:
import { GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
async function getDownloadUrl(key) {
const command = new GetObjectCommand({ Bucket: process.env.S3_BUCKET, Key: key });
return getSignedUrl(s3, command, { expiresIn: 3600 });
}Notes
- Set a lifecycle rule to expire or transition old objects (to Glacier, or delete) if the bucket is for temporary uploads rather than permanent storage — it's a config toggle, not something you need to build yourself.
- If uploads come directly from the browser, use a presigned
PutObjectURL instead of proxying the file through your server.