NodeJS create and POST file with GQL

Viewed 804

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

1 Answers

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

Related