iOS Client cannot upload file to Back-end server

I need to upload an image from iOS Client to NestJS server
Here is the munation

createSellerProfile(seller: SellerInput, userId: ID!, uploads: Upload): Seller

iOS Client’s .graphql file:

mutation createSellerProfile($seller: SellerInput, $userId: ID!, $uploads: Upload) {
    createSellerProfile (seller: $seller, userId: $userId, uploads: $uploads) {
        id
        firstName
        lastName
    }
}

And this is the client’s code

let avaimg = avatarImage.jpegData(compressionQuality: 0.1)

let file = GraphQLFile(fieldName: "avatar", originalName: "avatar", mimeType: "image/jpeg", data: avaimg)
        
let sellerData = SellerInput(firstName: "phuong", lastName: "nguyen", description: "Hello, this is Phuong. I am freelance web developer")
        
Network.shared.apollo.upload(operation: CreateSellerProfileMutation(seller: sellerData, 
userId: "4d146299-a219-4bde-9874-bed936dda129", uploads: "avatar"), files: [file]) {
       res in
       switch (res) {
       case .success(let graphQLResult):
            print("success")
       case .failure(let error):
            print("error: \(error)")
       }
}

But on my NestJS server, I only receive a single string “avatar” for my uploads field instead of file content. Please help me out, thank you.

It looks like you’re passing the string “avatar” to your mutation for the uploads argument, which explains why you’re getting that on the server. You probably want to pass your file object in as uploads instead.

For more details see: Performing mutations - Client (iOS) - Apollo GraphQL Docs

1 Like

I followed the guide here and the example as well. It says the Upload is underhood the string param. But it doesnt seem to work idk why

Hugh, unfortunately this is correct for Web but not for iOS.

TL;DR - you need to make the fieldName be "uploads" when you create the GraphQLFile rather than "avatar" because it has to match the name of the field name in the createSellerProfile mutation in GQL.

The long version:

This is something that is unfortunate about generating type-safe code: You can’t just pass in the different type when it actually makes sense to do so.

In this case, Upload is defined as a custom scalar, whose underlying value is String. That means all the parameters in Swift that want an Upload actually take a String, and all the parameters that want an [Upload] want an array of String. You can’t just hand a GraphQLFile in when a parameter is expecting a String, because it won’t compile.

The way we work around this is to hand the actual files you want to upload over to the networking client via the files parameter of the upload operation. Then, using each file’s fieldName property, we set up a multipart-form request that actually uploads each of the files for the appropriate field.

This is why the fieldName has to match the name of the GraphQL field it’s being uploaded for in the schema: So that particularly in circumstances where we have several files being passed in, we know which file(s) are for which field(s) and we can map them properly.

Is this more complicated than it should be for developers? Oh yes! The problem is that “fixing” it would involve special-casing code generation for Upload scalars, which would be very, very difficult in our current codegen (and frankly would still probably be difficult in our forthcoming Swift-based codegen, which is still under construction).

This is why in the docs we actually recommend using a purpose-built uploader if you can, and only passing the URL string of the location where the file was uploaded via GraphQL.

It’s a lot easier to hide all this detail under the covers in Javascript, where there isn’t a type system being enforced. I think that’s where @hwilson got caught up, as that’s his area of expertise.

Hope that all helps!

2 Likes

Haha - in my case I just misread the docs that I even included in my response! :man_facepalming: Thanks for the correction @designatednerd!

worked beautifully, thank you so much

2 Likes

Solved.