DEV Community

Miguel MV
Miguel MV

Posted on

Handling Image Uploads to Amazon S3 Using AstroJS API Endpoints

Prerequisites

  • Node.js
  • npm, pnpm, or yarn (Bun recommend)
  • AWS account with S3 bucket access

Step 1: Install Astro

Open a terminal and run:

npm create astro@latest s3-image-upload-api
Enter fullscreen mode Exit fullscreen mode

Follow the interactive setup.Where will be asked for the project configuration, once done. Navigate into your project:

cd s3-image-upload-api
Enter fullscreen mode Exit fullscreen mode

Start the development server:

npm run dev
Enter fullscreen mode Exit fullscreen mode

If something went wrong, please follow the official Astro docs for installing. Here Install Astro


Step 2: Install AWS SDK and Configure S3 Client

Install the AWS SDK:

npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Enter fullscreen mode Exit fullscreen mode

Create an .env file in your project root and add:

AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=your-region
AWS_BUCKET_NAME=your-bucket
Enter fullscreen mode Exit fullscreen mode

Create an s3Client.js file inside lib/:

import { S3Client } from "@aws-sdk/client-s3";

export const s3Client = new S3Client({
   region: process.env.AWS_REGION,
   credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
   },
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Generate a Signed URL

Create a function to generate signed URLs

import { GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { s3Client } from "./s3Client";

export async function getSignedUrlForFile(fileName) {
   const command = new GetObjectCommand({
      Bucket: process.env.AWS_BUCKET_NAME,
      Key: fileName,
   });
   return await getSignedUrl(s3Client, command, { expiresIn: 3600 });
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Generate a Seting up endpoints

export const POST: APIRoute = async ({ request }) => {
    const formData = await request.formData();
    const file = formData.get("image") as File;

    if (!file) {
        return new Response(JSON.stringify({ error: "No file uploaded" }), {
            status: 400,
            headers: { "Content-Type": "application/json" },
        });
    }

    const uniquePrefix = Date.now() + "-" + Math.round(Math.random() * 1e9)
    const fileExtension = file.name.split(".").pop()
    const fileName = `${uniquePrefix}.${fileExtension}`

    const bytes = await file.arrayBuffer()
    const fileBuffer = Buffer.from(bytes)

    const putObjectParams = {
        Bucket: process.env.AWS_BUCKET_NAME!,
        Key: fileName,
        Body: fileBuffer,
        ContentType: file.type,
    };

    const command = new PutObjectCommand(putObjectParams);
    const s3UploadResponse = await s3Client.send(command);

    /* IMPORTANT: We are saving only a reference to the image (the S3 object key).
    Since this is a private S3 bucket, you must configure your S3 bucket for 
    public or general access if you want to use direct URLs.

    Alternatively, you can generate a pre-signed URL to grant temporary access.

    Here, we handle a private S3 bucket, but since we define a limited time 
    for the valid link, it allows temporary access to the file. */

    // const publicS3Url = await getPresignedUrl(fileName);

    // Save only the S3 object key for future queries.

    return new Response(JSON.stringify({ path: fileName }), {
        status: 200,
        headers: { "Content-Type": "application/json" },
    });
Enter fullscreen mode Exit fullscreen mode

In Amazon S3, the term KEY refers to the resource's unique identifier, which is essentially the name of the file within the bucket. Here, we save this key for future queries.


Step 5: Use Signed URLs in Astro

Use the API in your Astro page:

---
let signedUrl = "";
const res = await fetch("/api/signed-url");
const data = await res.json();
signedUrl = data.url;
---

<a href={signedUrl} download>Download File</a>
Enter fullscreen mode Exit fullscreen mode

Lastly if you need to consume the file or serve the url can call like this having a private buket:

export const GET: APIRoute = async ({ request }) => {
    const key = new URL(request.url).searchParams.get('key')!;

    if (!key) {
        return new Response(JSON.stringify({
            status: 400,
            message: 'Missing key'
        }))

    }

    try {
        const srcFile = await getPresignedUrl(key)
        return new Response(JSON.stringify({
            srcFile
        }))
    } catch (error) {
        return new Response(JSON.stringify({
            status: 500,
            message: `Error generating URL: ${error}`
        }))
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

So if you followed the steps, you should able to see a link to your images and use it in your front end, the code is a messy right now maybe later will give a check. 🎉

AWS Q Developer image

Your AI Code Assistant

Ask anything about your entire project, code and get answers and even architecture diagrams. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Start free in your IDE

Top comments (0)