How can I Upload file to S3 using Next JS with Zeit Now and formidable-serverless
Asked Answered
P

2

6

Sorry I'm not good at english. It works in localhost but not works in production. (deploy with ZEIT NOW)

This is upload.ts

import { NextApiRequest, NextApiResponse } from 'next'
import AWS from 'aws-sdk';
const formidable = require("formidable-serverless");
AWS.config.region = 'ap-northeast-2';

export const config = {
  api: {
    bodyParser: false,
  }
}

export default async (req:NextApiRequest, res:NextApiResponse) => {
  const data = await new Promise(function(resolve, reject) {
    const form = formidable();

    form.keepExtensions = true;
    form.keepFilename = true;

    form.parse(req, (err:any, fields:any, files:any) => {

      const s3 = new AWS.S3({
        accessKeyId: process.env.AWS_ACCESS_KEY_CODE,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY_CODE
      });

      const params = {
        Bucket: 'mybucket',
        Key: `folder/${files.file.name}`,
        ACL: 'public-read',
        Body: require('fs').createReadStream(files.file.path);
      };

      s3.upload(params, (err:any, data:any) => {
        resolve({ err, data });
      });

      if (err) return reject(err);
      resolve({ fields, files });
    });

  });
}

I think I can't use 'fs' in serverless. but I can't find another way to upload. help me thank you!

Pyrargyrite answered 11/7, 2020 at 6:5 Comment(0)
E
16

Here's an example for how to upload images to S3 with Next.js.

// pages/api/upload-url.js

import aws from 'aws-sdk';

export default async function handler(req, res) {
  aws.config.update({
    accessKeyId: process.env.ACCESS_KEY,
    secretAccessKey: process.env.SECRET_KEY,
    region: process.env.REGION,
    signatureVersion: 'v4',
  });

  const s3 = new aws.S3();
  const post = await s3.createPresignedPost({
    Bucket: process.env.BUCKET_NAME,
    Fields: {
      key: req.query.file,
    },
    Expires: 60, // seconds
    Conditions: [
      ['content-length-range', 0, 1048576], // up to 1 MB
    ],
  });

  res.status(200).json(post);
}

This utilizes a presigned POST, which helps offload the computation from your server to AWS. Then, you can call this URL on the frontend to send the file.

export default function Upload() {
  const uploadPhoto = async (e) => {
    const file = e.target.files[0];
    const filename = encodeURIComponent(file.name);
    const res = await fetch(`/api/upload-url?file=${filename}`);
    const { url, fields } = await res.json();
    const formData = new FormData();

    Object.entries({ ...fields, file }).forEach(([key, value]) => {
      formData.append(key, value);
    });

    const upload = await fetch(url, {
      method: 'POST',
      body: formData,
    });

    if (upload.ok) {
      console.log('Uploaded successfully!');
    } else {
      console.error('Upload failed.');
    }
  };

  return (
    <>
      <p>Upload a .png or .jpg image (max 1MB).</p>
      <input
        onChange={uploadPhoto}
        type="file"
        accept="image/png, image/jpeg"
      />
    </>
  );
}

https://github.com/leerob/nextjs-aws-s3

Entomophagous answered 18/11, 2020 at 21:57 Comment(6)
Thank you for this @leerob. It was really helpful. I based my solution on your code and it made it easy to research and know what to do. I had to add my local dev setup to the CORS configuration on the S3 bucket and that's pretty much it.Folder
thanks for the example, works great! Could you maybe point me in a direction on how to retrieve the uploaded images? would a presigned get request be the best option, or is there something more simple for some sort of role based /community based access? thanks!Bimestrial
@Bimestrial Have found a solution to that?Whitton
@YassineBridi until now im using presigned urls (generated by the backend) for all the images. Not sure if thats the most efficient way though.Bimestrial
@Bimestrial Do you have a blog post talking about this? I already have a Laravel backend.Whitton
Any reason you capped it at 1MB?Leavening
U
8

Here's my upload example using nextjs, multer (typescript)

.env.local

AWS_ACCESS_KEY_ID=<your-key-id>
AWS_SECRET_ACCESS_KEY=<your-access-key>

BUCKET_NAME=<your-bucket-name>

@utils/runMiddleware (https://nextjs.org/docs/api-routes/api-middlewares)

import { NextApiRequest, NextApiResponse } from 'next';

const runMiddleware = (req: NextApiRequest, res: NextApiResponse, fn: any) => {
  return new Promise((resolve, reject) => {
    fn(req, res, (result: unknown) => {
      if (result instanceof Error) {
        return reject(result);
      }

      return resolve(result);
    });
  });
};

export default runMiddleware;

@utils/aws/upload.ts

import { config, S3 } from 'aws-sdk';

// Set the region (other credentials are in process.env)
config.update({ region: 'ap-northeast-2' });

// Create S3 service object
const s3 = new S3({ apiVersion: '2006-03-01' });

const upload = async (bucket: string, fileName: string, body: Buffer) => {
  return new Promise((resolve, reject) => {
    s3.upload(
      {
        Bucket: bucket,
        Key: fileName,
        Body: body,
        ACL: 'public-read',
      },
      (err, data) => {
        if (err) reject(err);
        else resolve(data);
      },
    );
  });
};

export default upload;

/api/upload.ts

import { NextApiRequest, NextApiResponse } from 'next';
import multer from 'multer';
import runMiddleware from '@utils/runMiddleware';
import uploadS3 from '@utils/aws';

interface RequestWithFile extends NextApiRequest {
  file?: any;
}

const upload = multer({
  storage: multer.memoryStorage(),
  limits: {
    // Max file size (1 mb)
    fileSize: 1024 * 1024,
  },
});

export const config = {
  api: {
    bodyParser: false,
  },
};

const handler = async (req: RequestWithFile, res: NextApiResponse) => {
  try {
    if (req.method === 'POST') {
      await runMiddleware(req, res, upload.single('image'));

      if (!req.file) return res.status(400).json({ error: 'File empty' });

      const { fileName } = req.body;

      if (!fileName) return res.status(400).json({ error: 'FileName empty' });

      const uploadResult = await uploadS3(
        process.env.BUCKET_NAME,
        fileName,
        sharpImage,
      ) as { Location: string }

      return res.json({ src: uploadResult.Location, error: '' });
    }

    return res.status(404).json({ error: '404 not found' });
  } catch (err) {
    return res.status(500).json({ error: err.name, message: err.message });
  }
};

export default handler;

Uranyl answered 30/11, 2020 at 2:45 Comment(1)
what does sharpImage do?Miraculous

© 2022 - 2024 — McMap. All rights reserved.