Justin Jaeger
Justin Jaeger

Reputation: 321

Uploading image - data appears like this "���"�!1A"Qaq��2��B�#" and image is blank - Next.js application upload to DigitalOcean Spaces / AWS S3

I am trying to let my users upload photos in a Next.js application. I set up a remote database and I am writing to the database properly, but the images are appearing blank. I'm thinking it must be a problem with the format of the data coming in.

Here is my code on the front end in React:

async function handleProfileImageUpload(e) {
    const file = e.target.files[0];

    await fetch('/api/image/profileUpload', {
      method: 'POST',
      body: file,
      'Content-Type': 'image/jpg',
    })
     .then(res => {
         console.log('final:', res);
      })
  };

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

The "file" I declare above (const file = e.target.files[0]) appears like this on console.log(file):

+ --------++-+-++-+------------+----++-+--7--7----7-���"�!1A"Qaq��2��B�#br���$34R����CSst���5����)!1"AQaq23B����
                                                                                                                ?�@��P�n�9?Y�
                                                                                                                             ޞ�p@��zE�  Nk�2iH��l��]/P4��JJ!��(�@�r�Mң[      ���+���PD�HVǵ�f(*znP�>�HRT�!W��\J���$�p(Q�=JF6L�ܧZ�)�z,[�q���   *
�i�A\5*d!%6T���ͦ�@J{6�6��
k@��:JK�bꮘh�A�%=+E      q\���H
q�Q��"�����B(��OЛL��B!Le6���(�� aY
                                  �*zOV,8E�2��IC�H��*)@4է4.�ɬ(�<5��j!§eR27��
��s����IdR���V�u=�u2a��

... and so on. It's long.

I am uploading to Digital Ocean's Spaces object storage, which interfaces with AWS S3. Again, my application is written in Next.js and I am using a serverless environment.

Here is the API route I am sending it to ('/api/image/profileUpload.js'):

import AWS from 'aws-sdk';

export default async function handler(req, res) {

  // get the image data
  let image = req.body;

  // 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: 'asdff',
    Body: image,
    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)
  })

  // returning arbitrary object for now
  return res.json({});
};

When I console.log(image), it shows the same garbled string that I posted above, so I know it's getting the same exact data. Maybe this needs to be further parsed?

The code above is directly from a Digital Ocean tutorial but catered to my environment. I am taking note of the "Body" parameter, which is where the garbled string is being passed in.

What I've tried:

I've spent days on this issue. Any guidance would be much appreciated.

Upvotes: 3

Views: 1043

Answers (1)

Justin Jaeger
Justin Jaeger

Reputation: 321

Figured it out. I wasn't encoding the image properly in my Next.js serverless backend.

First, on the front end, I made my fetch request like this. It's important to put it in the "form" format for the next step in the backend:

async function handleProfileImageUpload(e) {
    const file = e.target.files[0];
    const formData = new FormData();
    formData.append('file', file);
    
    // CHECK THAT THE FILE IS PROPER FORMAT (size, type, etc)
    
    let url = false;
    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. Here's the API route:

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.

Upvotes: 3

Related Questions