Search code examples
multipartform-dataapolloapollo-serverimgurapollo-datasource-rest

Apollo REST Data Source and Imgur API - Keep getting 400 Bad Request using form data


I am trying to implement apollo-datasource-rest to handle image uploading by URL via Imgur's API (documentation here: https://apidocs.imgur.com/)

I initially was getting a 400 error which read We don't support that file type!, and determined that it was due to apollo-datasource-rest automatically setting the Content-Type to application/json. After fixing that issue using the form-data npm package, here's what my code looks like:

class ImgurAPI extends RESTDataSource {
  constructor() {
    super();
    this.baseURL = "https://api.imgur.com/3/";
  }

  willSendRequest(request) {
    request.headers.set("Content-Type", "multipart/form-data");
    request.headers.set(
      "Authorization",
      `Client-ID ${process.env.IMGUR_CLIENT_ID}`
    );
    console.log(request);
  }

  async uploadImageFromUrl(url) {
    const formData = new FormData();
    formData.append("image", url);
    return this.post("upload", formData);
  }
}

I now no longer get the We don't support that file type! error, but I still get a 400 response with a status text of just Bad Request. The console.log() from the previous code snippet prints this:

{
  method: 'POST',
  path: 'upload',
  body: FormData {
    _overheadLength: 104,
    _valueLength: 80,
    _valuesToMeasure: [],
    writable: false,
    readable: true,
    dataSize: 0,
    maxDataSize: 2097152,
    pauseStreams: true,
    _released: false,
    _streams: [
      '----------------------------594660553626244976225816\r\n' +
        'Content-Disposition: form-data; name="image"\r\n' +
        '\r\n',
      'https://upload.wikimedia.org/wikipedia/commons/a/a0/Sunflower_as_gif_websafe.gif',
      [Function: bound ]
    ],
    _currentStream: null,
    _insideLoop: false,
    _pendingNext: false,
    _boundary: '--------------------------594660553626244976225816'
  },
  params: URLSearchParams {},
  headers: Headers {
    [Symbol(map)]: [Object: null prototype] {
      'Content-Type': [Array],
      Authorization: [Array]
    }
  }
}

What am I missing here? The API seems to be accepting my form data so I would think that maybe there's some issue with one of the other headers, but in Postman it looks like there's only a few required headers, most of which are calculated (e.g. Content-Length) and so I assume apollo-datasource-rest must be handling that.


Solution

  • After doing some more research into multipart/form-data, I found that the boundary parameter is mandatory, and must be added to the Content-Type value in the request for the server to be able to parse the payload. Furthermore I am unable to manually set it as a parameter in the request (at least not in a way that actually works). Postman normally calculates this field when the request is sent, but apollo-datasource-rest doesn't automatically handle that.

    Changing the Content-Type to application/x-www-form-urlencoded and using a url encoded string instead of form-data fixed the issue.

    Here's the updated code:

      willSendRequest(request) {
        request.headers.set("Content-Type", `application/x-www-form-urlencoded`);
        request.headers.set(
          "Authorization",
          `Client-ID ${process.env.IMGUR_CLIENT_ID}`
        );
        console.log(request);
      }
    
      async uploadImageFromUrl(url) {
        const formData = `image=${url}&album=${process.env.IMGUR_ALBUM_DELETE_HASH}&type=url`;
        return this.post("upload", formData);
      }