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

Viewed 8879

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!

2 Answers

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

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;

Related