1
votes

Think i tried everything, but nothing seems to work. I have a React app, and i want to upload pictures to a folder. I am using Apollo server and Graphql.

The weird thing is that if the file is less than 17 kb, it works!. But anything bigger than that, the file gets corrupted. I can see the image file in the folder but it is 0kb, and when i try to open it it says that the image contains errors". I don't have a limit to size of items.

The resolver: I tried using a different method of doing this but i got the same issue, so i don't believe this is the issue.

const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { UserInputError } = require('apollo-server');
const path = require('path');
const fs = require('fs');

async uploadFile(_, { file }) {
      const { createReadStream, filename, mimetype, encoding } = await file;
       
      const stream = createReadStream();
      const pathName = path.join(__dirname, "../../public/images/", filename);
      await stream.pipe(fs.createWriteStream(pathName));

      return {
        url: `http://localhost:5000/images/${filename}`,
      }
    }  

Type definitions:

    type File {
    url: String!
    filename: String!
    mimetype: String!
    encoding: String!
  }
    type Mutation {
    uploadFile(file: Upload!): File!
  }

The Apollo provider:

import React from 'react';
import App from './App';
import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
/* import { createHttpLink } from 'apollo-link-http'; */
import { ApolloProvider } from '@apollo/react-hooks';
import { setContext } from 'apollo-link-context';
import { createUploadLink } from 'apollo-upload-client';


const uploadLink = createUploadLink({
  uri: 'http://localhost:5000/graphql'
});

const authLink = setContext(() => {
  const token = localStorage.getItem('jwtToken');
  return {
    headers: {
      Authorization: token ? `Bearer ${token}` : ''
    }
  };
});

const client = new ApolloClient({
  link: authLink.concat(uploadLink),
  cache: new InMemoryCache(),
  onError: ({ networkError, graphQLErrors }) => {
    console.log( 'graphQLErrors', graphQLErrors)
    console.log( 'networkError', networkError)
  }
});

export default (
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);

The Index.js on the server:

const { ApolloServer, PubSub } = require('apollo-server-express');
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');

const typeDefs = require('./graphql/typeDefs');
const resolvers = require('./graphql/resolvers');
const { MONGODB } = require('./config.js');

const pubsub = new PubSub();

const PORT = process.env.PORT || 5000;

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({ req, pubsub })
});

const app = express();
server.applyMiddleware({ app });

app.use(express.static('public'));
app.use(cors());

mongoose
  .connect(MONGODB, { useNewUrlParser: true })
  .then(() => {
    console.log('MongoDB Connected');
    return app.listen({ port: PORT }, () => {
      console.log(`Server running at http://localhost:5000/ Graphql Playground at http://localhost:5000/graphql `);
    });
  })
  .catch(err => {
    console.error(err)
  })

I tried doing the input form differently but i got the same issue so i don't think the issue is here, but i don't know.

import React, { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import { useMutation, gql } from '@apollo/client';

function UploadForm() {

    const [uploadFile] = useMutation(UPLOAD_FILE_MUTATION, {
        onCompleted: data => console.log(data),
    });

    const onDrop = useCallback(
        ([file]) => {
            uploadFile({ variables: { file } });
        },
        [uploadFile]
    );
    const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });

    return (
        <div {...getRootProps()}>
            <input {...getInputProps()}/>
            {isDragActive ? (
                <p>Drop the files here!</p>
            ) : (
                <p>Drag and drop files here, or click to select files</p>
            )}
        </div>
    )
}

const UPLOAD_FILE_MUTATION = gql`
    mutation uploadFile($file: Upload!){
        uploadFile(file: $file){
            url
        }
    }
`

export default UploadForm;

I suspect the issue is in the index.js or the Apollo provider, but i really don't know. Thanks.

1
async/await only works with promises, not with streams ... use searchxadm
Wow it works! Thank you so muchCasper

1 Answers

3
votes

"async/await only works with promises, not with streams" - xadm

Solution:

Changed the resolver to this:

const path = require('path');
const fs = require('fs');

const Image = require('../../models/Image');

const files = [];
  
  module.exports = {

    Mutation: {
    async uploadProfilePic(_, { file }, context) {
      const { createReadStream, filename, mimetype, encoding } = await file;

      const { ext } = path.parse(filename);
      const randomName = generateRandomString(12) + ext;

        files.push(randomName);

        console.log(file);

        const storeUpload = async ({ stream, filename, mimetype, encoding }) => {
        
            const path = `public/images/${randomName}`
        
            return new Promise((resolve, reject) =>
            stream
                .pipe(fs.createWriteStream(path))
                .on("finish", () => resolve({ 
                category: "profilePic",
                url: `http://localhost:5000/images/${randomName}`, 
                path, 
                filename: randomName, 
                mimetype,
                encoding,
                createdAt: new Date().toISOString(),
                commentsCount: 0,
                likesCount: 0,
                sharesCount: 0
                }))
                .on("error", reject)
            );
        };

        const processUpload = async (upload) => {
          const { createReadStream, filename, mimetype, encoding } = await upload;
          const stream = createReadStream();
          const file = await storeUpload({ stream, filename, mimetype, encoding });
          return file;
        };

        const upload = await processUpload(file);

        
        return {
          url: `http://localhost:5000/images/${randomName}`,
        };
      
    }
  }
};