javascriptnode.jsgoogle-maps-api-3next.jsnode-fetch

Is there a way to pipe ReadableStream<Uint8Array> into NextApiResponse?


I'd like to display Google Place Photos in my NextJS application. To GET these images from their specified URL an API key is needed, but at the same time, I do not want to expose this API key to the public.

My goal is to implement a NextJS API route that fetches and returns a specified image from Google Places Photos, while also being able to be accessed directly from an image tag like so:

<img src={`/api/photos/${place?.photos[0].photo_reference}`} alt='' />

I found a few different sources online that recommended I pipe the response stream from my google request directly to the response stream of the outgoing response like so:

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const response = await fetch(
    `https://maps.googleapis.com/maps/api/place/photo
  &photo_reference=${id}
  &key=${process.env.GOOGLE_PLACE_API_KEY}`,
  );

  if (!response.ok) {
    console.log(response);
    res.status(500).end();
    return;
  }

  response.body.pipe(res);
}

however as response.body is a ReadableStream, it does not have a .pipe() function. Instead it has .pipeTo() and .pipeThrough().

I then tried

response.body.pipeTo(res);

however, this also fails to work because res is a NextApiResponse rather than a WritableStream. Although I searched online, I haven't found out how to write to NextApiResponse similarly to WritableStreams.

Finally, I tried manually converting the response into a buffer and writing it to the NextApiResponse like so:

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const response = await fetch(
    `https://maps.googleapis.com/maps/api/place/photo
  &photo_reference=${id}
  &key=${process.env.GOOGLE_PLACE_API_KEY}`,
  );

  if (!response.ok) {
    console.log(response);
    res.status(500).end();
    return;
  }

  const resBlob = await response.blob();
  const resBufferArray = await resBlob.arrayBuffer();
  const resBuffer = Buffer.from(resBufferArray);

  const fileType = await fileTypeFromBuffer(resBuffer);
  res.setHeader('Content-Type', fileType?.mime ?? 'application/octet-stream');
  res.setHeader('Content-Length', resBuffer.length);
  res.write(resBuffer, 'binary');
  res.end();
}

and while this completes the response, no image is displayed.

How can I directly pass the retrieved google places image from the server to the frontend so that it can be used by a tag?


Solution

  • What worked for me was to simply cast the response.body of type ReadableStream<Uint8Array> to a NodeJS.ReadableStream and then use the pipe function.

    const readableStream = response.body as unknown as NodeJS.ReadableStream;
    readableStream.pipe(res);