--- type: video yt_id: VVU2YVRMdUlfajQtMHdpRFN6bWFQY3RRLmVRQUlvamNBclJZ videoId: eQAIojcArRY title: "Storing Images in S3 from Node Server" date: "2022-07-25T14:00:16Z" slug: "storing-images-in-s3-from-node-server" image: name: "storing-images-in-s3-from-node-server.jpg" alt: "Storing Images in S3 from Node Server" width: 1280 height: 720 status: 'published' description: "Learn how to store your web app's files in an s3 bucket. Upload, Download, Update, and Delete images in an s3 bucket, from a node.js application." tags: ["s3", "aws", "cdn", "nodejs", 'JavaScript', 'js', 'aws', 'serverless', 'next.js', 'react.js'] previousPostSlug: "aws-cli" nextPostSlug: "set-up-a-cloudfront-cdn-for-an-s3-bucket" --- Learn how to store your web app's files in an s3 bucket. Upload, Download, Update, and Delete images in an s3 bucket, from a node.js application. Uploading an image goes through the express server allowing us to modify the image before it's stored in the s3 bucket. Downloading the image happens directly from the s3 bucket to put less strain on the server and make it easier to integrate our bucket with a CDN in the future. ## Chapters: * 0:00​ Intro * 3:17 Post a photo with multipart/form-data * 5:06 Multer * 8:40 Create an S3 Bucket * 11:38 IAM User and Policy * 16:36 AWS SDK S3 Client * 19:00 Uploading an image to S3 * 22:07 Updating an image * 23:18 Random Image Names * 25:16 Resizing Images * 27:36 Saving data to the database * 29:55 Getting images with signed url * 35:28 Deleting an image * 38:19 Summary ## Code Examples of how to upload, download and delete files from S3 bucket using [multer](https://www.npmjs.com/package/multer) and the [AWS SDK for JavaScript v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/index.html) Node.js. [https://github.com/meech-ward/s3-get-put-and-delete](https://github.com/meech-ward/s3-get-put-and-delete) **Libraries:** * Multer: [https://www.npmjs.com/package/multer](https://www.npmjs.com/package/multer) * AWS S3 Client: [https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/index.html](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-s3/index.html) * S3 request presigner: [https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_request_presigner.html](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_request_presigner.html) I used [Prisma](/prisma-and-nextjs) with MySQL for the database and [tailwind](https://tailwindcss.com/) for the styling. These are irrelevant details for this example. Any database or styling would work. The important part is how the Node.js backend communicates with S3. ## Posting an image to the server To post an image to the server, the client sends a `multipart/form-data` request to the server with the image data and any other data that the client wants to send. **HTML:** ```html
``` **React/Next:** ```jsx export default function NewPost() { const [file, setFile] = useState() const [caption, setCaption] = useState("") const submit = async event => { event.preventDefault() const formData = new FormData(); formData.append("image", file) formData.append("caption", caption) await axios.post("/api/posts", formData, { headers: {'Content-Type': 'multipart/form-data'}}) } return (
setFile(e.target.files[0])} type="file" accept="image/*"> setCaption(e.target.value)} type="text" placeholder='Caption'>
) } ``` ## Processing the image The server then accepts the image data using [multer](https://www.npmjs.com/package/multer) and keeps the image in memory so it can be easily modified and sent to S3. The app's in this example modify the image by resizing it using [sharp](https://www.npmjs.com/package/sharp). At all times the image is stored in memory as a buffer. ```js import multer from 'multer' import sharp from 'sharp' const storage = multer.memoryStorage() const upload = multer({ storage: storage }) app.post('/posts', upload.single('image'), async (req, res) => { const file = req.file const caption = req.body.caption const fileBuffer = await sharp(file.buffer) .resize({ height: 1920, width: 1080, fit: "contain" }) .toBuffer() // ... }) ``` ## PUTing the image to S3 Next we need to send the image to S3. First S3 needs to be configured with the correct credentials. ```js import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3" import dotenv from 'dotenv' dotenv.config() const bucketName = process.env.AWS_BUCKET_NAME const region = process.env.AWS_BUCKET_REGION const accessKeyId = process.env.AWS_ACCESS_KEY const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY const s3Client = new S3Client({ region, credentials: { accessKeyId, secretAccessKey } }) ``` Then the image buffer data can be sent to S3 using the `PutObjectCommand`. Since the images in S3 all need to have unique names, we can use the crypto library to generate a unique unguessable name. ```js import crypto from 'crypto' const generateFileName = (bytes = 32) => crypto.randomBytes(bytes).toString('hex') app.post('/posts', upload.single('image'), async (req, res) => { const file = req.file const caption = req.body.caption const fileBuffer = await sharp(file.buffer) .resize({ height: 1920, width: 1080, fit: "contain" }) .toBuffer() // Configure the upload details to send to S3 const fileName = generateFileName() const uploadParams = { Bucket: bucketName, Body: fileBuffer, Key: fileName, ContentType: file.mimetype } // Send the upload to S3 await s3Client.send(new PutObjectCommand(uploadParams)); // Save the image name to the database. Any other req.body data can be saved here too but we don't need any other image data. const post = await prisma.posts.create({ data: { imageName, caption, } }) res.send(post) }) ``` ## Generating signed URL Once the images are being successfully uploaded to S3, we need to generate a signed URL so the client can GET the image from S3. The database only stores the image name, so we generate a signed URL using the image name. ```js import { GetObjectCommand } from "@aws-sdk/client-s3" import { getSignedUrl } from "@aws-sdk/s3-request-presigner" app.get("/", async (req, res) => { const posts = await prisma.posts.findMany({ orderBy: [{ created: 'desc' }] }) // Get all posts from the database for (let post of posts) { // For each post, generate a signed URL and save it to the post object post.imageUrl = await getSignedUrl( s3Client, new GetObjectCommand({ Bucket: bucketName, Key: imageName }), { expiresIn: 60 }// 60 seconds ) } res.send(posts) }) ``` https://aws.amazon.com/blogs/developer/generate-presigned-url-modular-aws-sdk-javascript/ Then any client can GET the image from S3 using the signed URL in the `src` of an `img` tag. ## DELETEing the image from S3 If you want to delete the image from S3, you can use the `DeleteObjectCommand` passing in the image name, then delete the corresponding post from the database. ```js app.delete("/api/posts/:id", async (req, res) => { const id = +req.params.id const post = await prisma.posts.findUnique({where: {id}}) const deleteParams = { Bucket: bucketName, Key: post.imageName, } return s3Client.send(new DeleteObjectCommand(deleteParams)) await prisma.posts.delete({where: {id}}) res.send(post) }) ```