2

I've been unable to figure out in NodeJS how to:

  1. create a "file" in memory from a raw string; and,
  2. how to POST that data to another server that expects a multipart/form-data payload.

Seems you cannot use the Blob or File classes in NodeJS. I've read the pattern should be to use the Buffer class. I still cannot get it to work with Buffers.

My GQL Datasoruce class looks something like:

const { RESTDataSource } = require('apollo-datasource-rest');
const FormData = require('form-data');

export default class MyDatasource extends RESTDataSource {

  async postFileToServer({ string }) {

    const inMemoryFile = Buffer.from(string, 'utf-8');

    const myForm = new FormData();
    myForm.append('file', inMemoryFile, 'file.txt');

    const url = 'http://examnple.com';
    const opts = { headers: { 'Content-Type': 'multipart/form-data' } };

    return await this.post(url, myForm, opts);
  }
}

The endpoint I want to hit works fine when I use Postman to make the API call with a file from my local machine. However, I need the GQL server to create the file from a raw string to afterwards call the example.com endpoint that is expecting a multipart/form-data.

The above example code always gives me an error of Status 400 and SyntaxError: Unexpected token - in JSON at position 0

jaimefps
  • 2,244
  • 8
  • 22
  • 45
  • separate problems - sending file from node (some info in this thread https://stackoverflow.com/a/25345124/6124657) from data returned as graphql response - check in the middle? convert response? – xadm Jul 30 '20 at 22:05
  • That answer is dependent on using a specific npm module. I'm hoping for a solution within the boundaries of the problem I've stated above. – jaimefps Jul 30 '20 at 22:22
  • just hints ... method should be the same - separate problems ... if you can check sending file is ok then work only on returning good data structure for graphql response ... if you can't, search for your 'specific module' node sending solutions – xadm Jul 30 '20 at 22:28

1 Answers1

3

File upload works for me using the apollo-datasource-rest package. Here is an example:

server.ts:

import { ApolloServer, gql } from 'apollo-server';
import MyDatasource from './datasource';

const typeDefs = gql`
  type Query {
    dummy: String
  }
  type Mutation {
    upload: String
  }
`;
const resolvers = {
  Mutation: {
    upload(_, __, { dataSources }) {
      return dataSources.uploadAPI.postFileToServer({ str: '1234' });
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  dataSources: () => {
    return {
      uploadAPI: new MyDatasource(),
    };
  },
});
const port = 3001;
server.listen(port).then(({ url }) => console.log(` Server ready at ${url}`));

datasource.ts:

import { RESTDataSource } from 'apollo-datasource-rest';
import FormData from 'form-data';

export default class MyDatasource extends RESTDataSource {
  public async postFileToServer({ str }) {
    const inMemoryFile = Buffer.from(str, 'utf-8');
    const myForm = new FormData();
    myForm.append('file', inMemoryFile, 'file.txt');
    const url = 'http://localhost:3000/upload';

    return this.post(url, myForm);
  }
}

uploadServer.ts:

import multer from 'multer';
import express from 'express';
import path from 'path';

const upload = multer({ dest: path.resolve(__dirname, 'uploads/') });
const app = express();
const port = 3000;

app.post('/upload', upload.single('file'), (req, res) => {
  console.log(req.file);
  console.log(req.body);
  res.sendStatus(200);
});

app.listen(port, () => {
  console.log(`upload server is listening on http://localhost:${port}`);
});

The logs printed in the controller of /upload API:

{
  fieldname: 'file',
  originalname: 'file.txt',
  encoding: '7bit',
  mimetype: 'text/plain',
  destination: '/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/src/stackoverflow/63181608/uploads',
  filename: '3cba4dded6089479ad495e2fb2daac21',
  path: '/Users/ldu020/workspace/github.com/mrdulin/apollo-graphql-tutorial/src/stackoverflow/63181608/uploads/3cba4dded6089479ad495e2fb2daac21',
  size: 4
}
[Object: null prototype] {}

source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/63181608

Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • This is amazing @slideshowp2, thanks for this immensely! – jaimefps Aug 04 '20 at 17:53
  • My problems had to do with company-specific routing issues that rejected my api calls before reaching the BE services. However, my code looks practically identical to this, so definitely putting this down as the solution. Thanks for such an awesome response! – jaimefps Aug 04 '20 at 21:15