I want the users of my Next.js application to be able to upload their own photos. I am currently testing the ability to upload an image to Spaces via Postman.

I followed these instructions and all of the bucket operations work fine like listing buckets, etc. Uploading an image even works – the size of the file seems correct at 19.6KB, so it isn’t empty – but the image does not appear.

Click here to see what I mean

Here is my code:

let formData = req.body;
  formData = JSON.stringify(formData)
  // console.log('formData', formData)

  // create S3 instance with credentials
  const s3 = new AWS.S3({
    endpoint: new AWS.Endpoint('nyc3.digitaloceanspaces.com'),
    accessKeyId: process.env.SPACES_KEY,
    secretAccessKey: process.env.SPACES_SECRET,
    region: 'nyc3',
  });

  // create parameters for upload
  const uploadParams = {
    Bucket: 'oscarexpert',
    Key: 'asdf.jpg',
    Body: formData,
    ContentType: "image/jpeg",
    ACL: "public-read",
  };

  // execute upload
  s3.upload(uploadParams, (err, data) => {
    if (err) return console.log('reject', err)
    else return console.log('resolve', data)
  }

I would hone in on the parameter, Body: formData. It seems this would be the issue, that the data is not formatted properly. I get the data from the body, which is a single image, but perhaps since Next.js is a serverless application, is not parsing the image properly.

I’d appreciate any insight into this problem. I’ve been working on it for days.

Lastly, let me show you what the string that I am actually inputting to the Body param looks like:

“—————————-325431949202422494893832\r\nContent-Disposition: form-data; name="file"; filename="download.jpg"\r\nContent-Type: image/jpeg\r\n\r\n����\u0000\u0010JFIF\u0000\u0001\u0001\u0000\u0000\u0001\u0000\u0001\u0000\u0000��\u0000�\u0000\t\u0006\u0007\u0013\u0013\u0010\u0015\u0012\u0012\u0013\u0015\u0015\u0015\u0016\u0010\u0012\u0015\u0015\u0015\u0015\u0015\u0016\u0015\u0015\u0015\u0012\u0015\u0015\u0016\u0017\u0015\u0011\u0016\u0015\u0018\u001d( \u0018\u001a%\u001b\u0015\u0015!1!&)+…\u0017\u001f383,7(-.+\u0001\n\n\n\u000e\r\u000e\u001b\u0010\u0010\u001a+ \u001f\u001e——–++-+-++-+————+—-++-+–7–7—-7-��\u0000\u0011\b\u0000�\u0000�\u0003\u0001"\u0000\u0002\u0011\u0001\u0003\u0011\u0001��\u0000\u001c\u0000\u0000"

… etc etc.

Would this Body parameter be able to interpret data like this? I have also tried to input an unstringified version of the req.body.

These answers are provided by our Community. If you find them useful, show some love by clicking the heart. If you run into issues leave a comment, or add your own answer to help others.

×
Submit an Answer
1 answer

Figured it out. I wasn’t encoding the image properly in my Next.js serverless backend. Perhaps this needs special instructions.

Conceptually, this is because Next.js’s body parser converts the image in the body to a very strange format, the string you saw here. You need to first disable that.

Then you need to parse the data yourself, and I found a great way to do that was with the formidable-serverless library. I also found that you need to chain .upload AND .send to your s3 instance. All of this will be clear below.

The post that helped me solve this is linked here – huge shoutout.

First, here’s my React HTML for allowing me to drag and drop the image in. Note that this is for making the profile image a clickable element. Clicking will open a pop up that allows for the drag and drop.

<label htmlFor="file-upload">
    <div>
      <img src={profileImage} className=""/>
      <div id="dashboard-image-hover" >Upload Image</div>
    </div>
</label>,
<input id="file-upload" type="file" onChange={handleProfileImageUpload}/>

Second, here is the event handler tied to the input element above. It’s important to take the file and put it in the “form” format as such.

async function handleProfileImageUpload(e) {
    const file = e.target.files[0];
    const formData = new FormData();
    formData.append('file', file);

    // Check that file is in proper format before making request

    await fetch(`/api/image/profileUpload`, {
      method: 'POST',
      body: formData,
      'Content-Type': 'image/jpg',
    })
}

There were several components that helped me finally do this on the backend, so I am just going to post the code I ended up with rather than break down all of those. Everything step here is important. Here’s my entire API route (/api/image/profileUpload):

import AWS from 'aws-sdk';
import formidable from 'formidable-serverless';
import fs from 'fs';

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

export default async (req, res) => {

  // create S3 instance with credentials
  const s3 = new AWS.S3({
    endpoint: new AWS.Endpoint('nyc3.digitaloceanspaces.com'),
    accessKeyId: process.env.SPACES_KEY,
    secretAccessKey: process.env.SPACES_SECRET,
    region: 'nyc3',
  });

  // parse request to readable form
  const form = new formidable.IncomingForm();
  form.parse(req, async (err, fields, files) => {
    // Account for parsing errors
    if (err) return res.status(500);
    // Read file
    const file = fs.readFileSync(files.file.path);
    // Upload the file
    s3.upload({ 
      // params
      Bucket: process.env.SPACES_BUCKET,
      ACL: "public-read",
      Key: 'something',
      Body: file,
      ContentType: "image/jpeg",
    })
    .send((err, data) => {
      if (err) {
        console.log('err',err)
        return res.status(500);
      };
      if (data) {
        console.log('data',data)

        return res.json({
          url:  data.Location,
        });
      };
    });
  });
};

If you have any questions feel free to leave a comment.