Generate a pre-signed URL for an AWS S3 bucket object using Typescript in NextJS, allowing for easy file downloads on the client-side using @aws-sdk/S3Client

In the utilization of v3 of the S3Client, it appears that all existing examples are based on the old aws-sdk package. The goal is for the client (browser) to access a file from S3 without revealing the key from the backend. From my research, it seems that utilizing

const presigned = await signer.presign(request);
from the documentation found at https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_request_presigner.html is the correct approach, but I am unsure how to construct the request parameter needed. Any guidance on this matter would be greatly appreciated.

Answer №1

pages/api/file.ts:

import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { NextApiRequest, NextApiResponse } from "next";

export interface GetFileProps {
  url: string;
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<GetFileProps>,
) {
  const s3client = new S3Client({
    region: "region",
    credentials: {
      accessKeyId: "",
      secretAccessKey: "",
    },
  });

  const command = new GetObjectCommand({
    Bucket: "bucketName",
    Key: req.query.path as string,
  });

  const url = await getSignedUrl(s3client, command, { expiresIn: 900 });
  res.status(200).json({ url });
}

The URL obtained can be utilized by the frontend:

  const aRef = React.useRef<any>();

  const getFileUrlAndDownload = React.useCallback((path: string) => {
    from(fetch(`http://localhost:3000/api/file?path=${path}`))
      .pipe(
        mergeMap(response => {
          return response.json();
        }),
        mergeMap((res: GetFileProps) => {
          aRef.current.href = res.url;
          aRef.current.click();
          return EMPTY;
        }),
        catchError(err => {
          console.log("Error fetching download URL", err);
          return err;
        }),
      )
      .subscribe();
  }, []);

This link needs to be rendered in the tsx file:

      <a ref={aRef} />

To trigger the download process, an onClick event should call getFileUrlAndDownload with the file path.

Answer №2

While seeking a solution, I encountered difficulties trying to obtain the URL as expected. However, I came up with an alternative approach. You may not necessarily require a signed URL to display the image in the browser. Here is how I resolved the issue: Simply retrieve the image from the S3 bucket and then send the response.Body to the client because it contains a buffer object holding the image data. You can adjust the header as required.

File Location: pages/api/id/image/[...id].ts

import * as AWS from "aws-sdk";
import S3 from "aws-sdk/clients/s3"; // set up global aws credentials
import type { NextApiRequest, NextApiResponse } from "next";
import ConnectDb from "../../middleware/Mongoose";
import type { ResponseData } from "../../models/ApiResponse";

if (process.env.AWS_ACCESS_KEY_ID) {
  AWS.config.update({
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    region: process.env.AWS_REGION,
  });
}
const path = "users/";
const s3 = new S3();
const get = async (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
  try {
    let input = req.query?.id || [];
    let userId = input[0];
    let name = input[1];
    if (!userId || !name)
      return res.status(200).json({
        success: false,
        message: "Please provide user id and image name",
      });
    const face = await s3
      .getObject({
        Bucket: process.env.AWS_SECRET_BUCKET as string,
        Key: path + userId + "/" + name,
      })
      .promise();

    res.setHeader("Content-Type", "image/jpeg");
    res.end(face.Body);
  } catch (e: any) {
    console.log(e, "error");
    return res
      .status(200)
      .json({ success: false, message: "error: " + e.message });
  }
};
export default ConnectDb(
  async (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
    try {
      return req.method === "GET"
        ? await get(req, res)
        : res.status(404).json({ success: true, message: "404 not Found" });
    } catch (error) {
      console.log(error);
      return res
        .status(500)
        .json({ success: false, message: "Internal Server Error" });
    }
  }
);

Your front-end code:

 <img src={`/api/id/image/${el.user._id}/front.jpeg`} alt=""/>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

TS2339 Error: The property does not exist on this particular type when referencing a file relatively

Currently, I am in the process of developing my own UMD library using typescript and webpack. However, I encountered an issue when importing a file that resulted in the error TS2339 (Property 'makeRequest' does not exist on type 'typeof Util ...

Pass the parameter name to the controller using the Change function in Angular 2

When creating a string from multiple inputs, I have a requirement to include the name of the input element as the second parameter in a function. <input [(ngModel)]="programSearched" name="programSearched"(ngModelChange)="stringBuilderOnChangeMaker(pro ...

Learn the art of bypassing TypeScript errors using @ts-ignore!

I recently encountered an issue when trying to import a pure JavaScript library into a TypeScript project, resulting in the error message: Could not find a declaration file for module xxx. After some research, I learned that this error can be suppressed u ...

Error message: The class heritage events_1.EventEmitter is invalid and cannot be recognized

There seems to be a problem with the [email protected] npm dependency. I am attempting to incorporate mongodb into my Vue.js + Vite + Typescript application, but it crashes and fails to load due to an error originating from mongodb. It appears that th ...

Managing API responses using Redux and Typescript

As a beginner in Typescript, I am struggling to integrate Redux with it. The documentation on using Redux with Typescript is confusing me. I am attempting to fetch data and dispatch it to my reducer for future use, just as I did before adopting Typescript ...

TypeScript's type casting will fail if one mandatory interface property is missing while an additional property is present

While running tsc locally on an example file named example.ts, I encountered some unexpected behavior. In particular, when I created the object onePropMissing and omitted the property c which is not optional according to the interface definition, I did not ...

Struggling to locate a declaration file for the 'cloudinary-react' module? Consider running `npm i --save-dev @types/cloudinary-react` or exploring other options to resolve this issue

Currently, I am working with Typescript in React. Strangely, when I try to import the following: import { Image } from 'cloudinary-react'; I encounter this error: Could not find a declaration file for module 'cloudinary-react'. ' ...

Altering the dimensions of radio buttons

I am a newcomer to using material-ui. I am currently working on incorporating radio buttons in a component and would like to reduce its size. While inspecting it in Chrome, I was able to adjust the width of the svg icon (1em). However, I am unsure how to a ...

Tips for preventing the occurrence of a final empty line in Deno's TextLineStream

I executed this snippet of code: import { TextLineStream } from "https://deno.land/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7201061632425c4341445c42">[email protected]</a>/streams/mod.ts"; const cm ...

Angular: The fetched data from the API is coming back as undefined

I am trying to use the Highcharts module in Angular to build a chart. The data object needed for the chart is provided by an API, and this is the structure of the object: { "success": true, "activity": [ { &q ...

What is the process for creating an Angular library using "npm pack" within a Java/Spring Boot application?

In my angular project, we have 5 custom libraries tailored to our needs. Using the com.github.eirslett maven plugin in our spring boot application, we build these libraries through the pom.xml file and then copy them to the dist/ folder. However, we also ...

Discovering the distinction between http and https within a nodejs/nextjs API handler

I'm working on building my urls for xml sitemaps and rss feeds, and I need to determine whether the webpage is currently being served over http or https. This is important for it to function correctly both locally in development and when deployed. exp ...

What are some ways to avoid hydration issues when a different website utilizes my Next.js page as a container?

Currently, I am working on a website project for a real estate client and utilizing a service called IDX Broker that is new to me. This service operates by taking a page from my website (e.g., example.com/listings) and using it as a wrapper for their own p ...

Unlock the power of Angular ViewChildren to access and manipulate SVG elements efficiently

I have an SVG file loaded as an object: <object data="assets/img/states.svg" type="image/svg+xml" id="map"></object> This SVG includes a large PNG map along with several rect and text elements. <rect y="224.72084" x="644.87109" ...

What could be the reason for the Angular dropdown values not appearing?

Encountering an issue with binding data to a dropdown element, as the dropdown displays NOTHING SELECTED. <select #classProductTypeCombobox name="classProductTypeCombobox" class="form-control col-md-3" [(ngModel)]="classifica ...

Understanding the mechanisms of Promise functionality within Typescript can be challenging, particularly when encountering error messages such as "type void is not

Recently, I've delved into the world of Typescript. Despite my efforts to stay true to the typing system, I've encountered a challenge that forces me to resort to using the any type: The issue arises with a function that returns a promise: sav ...

Is there a way to restrict the amount of RAM Nextjs uses during development?

I am currently working on a project using Nexjs with @mui/material. There is an ongoing issue regarding memory usage in Nextjs, which can be found on this GitHub link. Whenever I run the development server for a period of time, my laptop's RAM gets ...

The attribute 'limitTags' is not present in the type 'IntrinsicAttributes & AutocompleteProps'

The Versions of Material UI and Lab I am Utilizing "@material-ui/core": "^4.8.3", "@material-ui/lab": "^4.0.0-alpha.44", Visit the component here Snippet of My Code <Autocomplete multiple limitTags={2} id="multiple-limit-tags" ...

When using Jest + Enzyme to test a stateful class component, encountering problems with Material-UI's withStyles functionality is a common issue

I have a React + TypeScript application with server-side rendering (SSR). I am using Material-UI and following the SSR instructions provided here. Most of my components are stateful, and I test them using Jest + Enzyme. Below is an example of one such com ...

How can you personalize the dropdown button in dx-toolbar using DevExtreme?

Currently, I am working with the DevExtreme(v20.1.4) toolbar component within Angular(v8.2.14). However, when implementing a dx-toolbar and specifying locateInMenu="always" for the toolbar items, a dropdown button featuring the dx-icon-overflow i ...