Node.js cookbook · Intermediate

Upload files to S3 with presigned URLs from Node.js

Let clients upload large files directly to S3 while the API controls keys, types and sizes.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Use AWS SDK v3's createPresignedPost to create a short-lived upload form with conditions on content type and size, for a server-generated key under the user's prefix. After upload, the client confirms and the API verifies the object with HeadObject.

Solution

import { randomUUID } from "node:crypto";
import { S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
import { z } from "zod";

const s3 = new S3Client({ region: process.env.AWS_REGION });
const Bucket = process.env.UPLOAD_BUCKET!;
const UploadRequest = z.object({
  contentType: z.enum(["image/png", "image/jpeg", "application/pdf"]),
  size: z.number().int().positive().max(50 * 1024 * 1024),
});

app.post("/api/uploads", requireAuth, validate({ body: UploadRequest }), async (req, res) => {
  const { contentType, size } = res.locals.body as z.infer<typeof UploadRequest>;
  const Key = `users/${req.user!.id}/${randomUUID()}`;
  const { url, fields } = await createPresignedPost(s3, {
    Bucket, Key,
    Conditions: [["content-length-range", 1, size], ["eq", "$Content-Type", contentType]],
    Fields: { "Content-Type": contentType },
    Expires: 300,
  });
  res.json({ key: Key, url, fields });
});

app.post("/api/uploads/complete", requireAuth, async (req, res) => {
  const key = String(req.body.key ?? "");
  if (!key.startsWith(`users/${req.user!.id}/`)) return res.status(403).json({ title: "Forbidden" });
  const head = await s3.send(new HeadObjectCommand({ Bucket, Key: key }));
  await files.register(req.user!.id, key, head.ContentLength!, head.ContentType!);
  res.status(204).end();
});

Pitfalls

  • Streaming uploads through Node when not needed (memory, bandwidth).
  • Client-chosen keys.
  • Missing CORS configuration on the bucket for browser uploads.

Variations

  • multer + @aws-sdk/lib-storage Upload when the server must process files.
  • S3 event → Lambda for scanning/thumbnails.

Further reading: AWS SDK v3: s3-presigned-post

Reference: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-s3-presigned-post/

More Node.js recipes

Was this page helpful?