CodePark/ questions / S3 & object storage
START PRACTICINGPRACTICE
Backend question · Cloud & AWS › S3 & object storage

Implement a direct-to-S3 upload from a NestJS API. What does this code return, which permissions apply to the URL, and how does the backend learn the upload finished?

import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { randomUUID } from "node:crypto";

const s3 = new S3Client({});

export async function createUploadUrl(userId: string, contentType: string) {
  const key = `uploads/${userId}/${randomUUID()}`;
  const cmd = new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: contentType });
  const url = await getSignedUrl(s3, cmd, { expiresIn: 300 });
  return { url, key };
}

It returns a pre-signed URL. This URL lets the client PUT exactly one key for 300 seconds, without any AWS credentials. The backend chose the key, so the client cannot write anywhere else. The signed ContentType must match the header the client sends.

The URL has the permissions of whoever signed it. Here, the task role needs s3:PutObject on the prefix. If that role loses the permission, the URL stops working. It also stops working if the role's temporary credentials expire. Both can happen before expiresIn ends.

The backend can learn the upload finished in two ways:

  • The client calls a confirm endpoint. The endpoint then checks the object with HeadObject.
  • You subscribe to S3 event notifications for ObjectCreated and send them to SQS, Lambda or EventBridge.

Check the file size and type after the upload, because a pre-signed PUT cannot enforce a maximum size. A pre-signed POST policy can.

  • A pre-signed URL allows one operation on one key until it expires.
  • The server picks the key, and signed headers like Content-Type must match.
  • The URL uses the signer's permissions and the signer's credential lifetime.
  • Confirm the upload with a callback endpoint that calls HeadObject, or with S3 event notifications.
  • Validate the file after upload, because only pre-signed POST policies can limit size.