-
-
Save patheard/f75cdc16b0c45ce7c4863967b2fd7797 to your computer and use it in GitHub Desktop.
NextJS S3 object download API endpoint
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { NextApiRequest, NextApiResponse } from "next"; | |
import { middleware } from "@lib/middleware"; | |
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; | |
import { logMessage } from "@lib/logger"; | |
const FILE_ATTACHMENT_BUCKET = process.env.VAULT_FILE_STORAGE as string; | |
const s3Client = new S3Client({ | |
endpoint: process.env.LOCAL_AWS_ENDPOINT ?? undefined, | |
forcePathStyle: process.env.LOCAL_AWS_ENDPOINT ? true : undefined, | |
region: process.env.AWS_REGION ?? "ca-central-1", | |
}); | |
/** | |
* Download an S3 object with a give `key` query param | |
* @param req | |
* @param res | |
*/ | |
const handler = async (req: NextApiRequest, res: NextApiResponse) => { | |
let maxMemory = 0; | |
let previousUsed = getMemory(); | |
const objectKey = req.query.key as string; | |
// Get the S3 object | |
const s3Object = await s3Client.send( | |
new GetObjectCommand({ | |
Bucket: FILE_ATTACHMENT_BUCKET, | |
Key: objectKey, | |
}) | |
); | |
// Stream the object back to the response | |
if (s3Object.Body) { | |
res.setHeader("Content-Type", "application/octet-stream"); | |
res.setHeader("Content-Disposition", "attachment; filename=download.img"); | |
const s3ObjectStream = s3Object.Body as NodeJS.ReadableStream; | |
s3ObjectStream | |
.on("data", () => { | |
const used = getMemory(); | |
logMessage.info(`${objectKey}: streaming memory ${formatNumber(used)} MB (delta ${formatNumber(used - previousUsed)} MB)`); | |
maxMemory = Math.max(maxMemory, used); | |
previousUsed = used; | |
}) | |
.on("end", () => { | |
logMessage.info(`${objectKey}: max memory ${formatNumber(maxMemory)} MB`); | |
}); | |
s3ObjectStream.pipe(res); | |
} | |
}; | |
const formatNumber = (num: number): string => { | |
return num.toLocaleString("en-CA"); | |
} | |
const getMemory = (): number => { | |
return process.memoryUsage().heapUsed / 1024 / 1024; | |
} | |
export default middleware([], handler); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment