1
votes

I am trying to upload file with apollo-upload-client from Next.js app to Node.js back end using graphql.

My Apollo client config

const createClient = (ctx: NextPageContext) =>
new ApolloClient({
  credentials: "include",
  headers: {
    cookie:
      (typeof window === "undefined"
        ? ctx?.req?.headers.cookie
        : undefined) || "",
  },
  cache: new InMemoryCache(),
  link: createUploadLink({uri:'http://localhost:4000/graphql', credentials:"include"})
});

My express resolver with TypeORM

@Resolver()
export class ProfilePictureResolver {
  @Mutation(() => Boolean)
  async addProfilePicture(@Arg("picture", () => GraphQLUpload)
  {
    createReadStream,
    filename
  }: Upload): Promise<boolean> {
    return new Promise(async (resolve, reject) =>
      createReadStream()
        .pipe(createWriteStream(__dirname + `/../../../images/${filename}`))
        .on("finish", () => resolve(true))
        .on("error", () => reject(false))
    );
  }
}

Page

const Profile:React.FC<IProps> = () => {

  const user = useSelector(state => state.user);
  const [file, setFileToUpload] = useState(null);
  const [mutate, {loading}] = useMutation(UPLOAD_IMAGE_MUTATION);


   function onChange({
    target: {
      validity,
      files: [file],
    },
  }) {
    if (validity.valid) mutate({ variables: { picture: file } });
  }

  const onSubmit = async (e) =>{
    e.preventDefault();
    console.log(file)
    const response = await mutate({
          variables: {picture: file}
    });


  }

    return (
        <Box mt={20} pl={30} pr={30}>
          <Header>
            Edit Profile
          </Header>
          <input onChange={onChange} type="file" placeholder="photo" />
          <button onClick={(e)=>onSubmit(e)}>Submit</button>
        </Box>
      
    )
};

FormData

------WebKitFormBoundary3bcoEmOQWM0eUhCG Content-Disposition: form-data; name="operations"

{"operationName":"addProfilePicture","variables":{"picture":null},"query":"mutation addProfilePicture($picture: Upload!) {\n addProfilePicture(picture: $picture)\n}\n"} ------WebKitFormBoundary3bcoEmOQWM0eUhCG Content-Disposition: form-data; name="map"

{"1":["variables.picture"]} ------WebKitFormBoundary3bcoEmOQWM0eUhCG Content-Disposition: form-data; name="1"; filename="73387406_149357266327075_4919835380817576193_n.jpg" Content-Type: image/jpeg

------WebKitFormBoundary3bcoEmOQWM0eUhCG--

Before call mutation in console I see that file is present. What am I doing wrong?

Update: I fixed mutation on the client side, changed file on picture:file. Now I have another error:

Variable "$picture" got invalid value {}; Upload value invalid.

Update 2:

Here is what my query looks like, might be of help

js export const UPLOAD_IMAGE_MUTATION = gql` mutation addProfilePicture($picture: Upload!) { addProfilePicture(picture: $picture) } `;

SOLUTION

I figured that indeed there was something wrong with my apollo server resolver

I've change the old resolver (see above) on this one:

 uploadFile: async (_, { file }) => {
      const { createReadStream, filename } = await file;

      await new Promise(res =>
        createReadStream()
          .pipe(createWriteStream(path.join(__dirname, "../images", filename)))
          .on("close", res)
      );

      files.push(filename);

      return true;
    },

So as you see I dont use graphql upload anymore, and also I've changed my client mutation back to

mutate({ variables: { file } }

And new mutation query:

  mutation UploadFile($file: Upload!) {
    uploadFile(file: $file)
  }

Now it works like a charm. Case closed.

2
mutate called twice? should be setFileToUpload(file) in onChange? update FormDataxadm
yes, you are correct, I did this for debugging, to skip one step and start uploading right away.Good Guy
still variables.file in FormData - show backend specs for this mutation, what is the required mutation arg name - file or picture ?xadm
required - pictureGood Guy
... again ... update FormData .... does BE/API support uploads properly?xadm

2 Answers

0
votes

As the error indicates, your operation includes a variable named $picture, but you have not provided such a variable. When calling mutate, the only variable you're providing is one named $file. You should instead do:

const response = await mutate({
  variables: { picture: file },
});
0
votes

I figured that indeed there was something wrong with my apollo server resolver

I've change the old resolver from the question on this one:

 uploadFile: async (_, { file }) => {
      const { createReadStream, filename } = await file;

      await new Promise(res =>
        createReadStream()
          .pipe(createWriteStream(path.join(__dirname, "../images", filename)))
          .on("close", res)
      );

      files.push(filename);

      return true;
    },

So as you see I dont use graphql upload anymore, and also I've changed my client mutation back to

mutate({ variables: { file } }

And new mutation query:

  mutation UploadFile($file: Upload!) {
    uploadFile(file: $file)
  }

Now it works like a charm.