Skip to content

File Uploads

In GraphQL, you model a file upload operation as a GraphQL mutation request from a client to your DGS.

The following sections describe how you implement file uploads and downloads using a Multipart POST request. For more context on file uploads and best practices, see Apollo Server File Upload Best Practices by Khalil Stemmler from Apollo Blog.

File Uploads using Spring GraphQL starter

If you are using the Spring GraphQL integration, you will need to add an explicit dependency for file uploads functionality. Specifically, you need to add this to your build.gradle

dependencies {
    implementation("name.nkonev.multipart-spring-graphql:multipart-spring-graphql:version")
}

If you are using the regular DGS starter, you don't need to do add that dependency.

Multipart File Upload

A multipart request is an HTTP request that contains multiple parts in a single request: the mutation query, file data, JSON objects, and whatever else you like. You can use Apollo’s upload client, or even a simple cURL, to send along a stream of file data using a multipart request that you model in your schema as a Mutation.

File uploads with multipart

See GraphQL multipart request specification for the specification of a multipart POST request for uploading files using GraphQL mutations.

The DGS framework supports the Upload scalar with which you can specify files in your mutation query as a MultipartFile. When you send a multipart request for file upload, the framework processes each part and assembles the final GraphQL query that it hands to your data fetcher for further processing.

Here is an example of a Mutation query that uploads a file to your DGS:

scalar Upload

extend type Mutation  {
    uploadScriptWithMultipartPOST(input: Upload!): Boolean
}

Note that you need to declare the Upload scalar in your schema, although the implementation is provided by the DGS framework. In your DGS, add a data fetcher to handle this as a MultipartFile as shown here:

@DgsData(parentType = DgsConstants.MUTATION.TYPE_NAME, field = "uploadScriptWithMultipartPOST")
    public boolean uploadScript(DataFetchingEnvironment dfe) throws IOException {
        // NOTE: Cannot use @InputArgument  or Object Mapper to convert to class, because MultipartFile cannot be
        // deserialized
        MultipartFile file = dfe.getArgument("input");
        String content = new String(file.getBytes());
        return ! content.isEmpty();
    }

Note that you will not be able to use a Jackson object mapper to deserialize a type that contains a MultipartFile, so you will need to explicitly get the file argument from your input.

On your client, you can use apollo-upload-client to send your Mutation query as a multipart POST request with file data. Here’s how you configure your link:

import { createUploadLink } from 'apollo-upload-client'

const uploadLink = createUploadLink({ uri: uri })

const authedClient = authLink && new ApolloClient({
        link: uploadLink)),
        cache: new InMemoryCache()
})

Once you set this up, set up your Mutation query and the pass the file that the user selected as a variable:

// query for file uploads using multipart post
const UploadScriptMultipartMutation_gql = gql`
  mutation uploadScriptWithMultipartPOST($input: Upload!) {
    uploadScriptWithMultipartPOST(input: $input)
  }
`;

function MultipartScriptUpload() {
  const [
    uploadScriptMultipartMutation,
    {
      loading: mutationLoading,
      error: mutationError,
      data: mutationData,
    },
  ] = useMutation(UploadScriptMultipartMutation_gql);
  const [scriptMultipartInput, setScriptMultipartInput] = useState<any>();

  const onSubmitScriptMultipart = () => {
    const fileInput = scriptMultipartInput.files[0];
    uploadScriptMultipartMutation({
      variables: { input: fileInput },
    });
  };

  return (
    <div>
      <h3> Upload script using multipart HTTP POST</h3>
      <form
        onSubmit={e => {
          e.preventDefault();
          onSubmitScriptMultipart();
        }}>
        <label>
          <input
            type="file"
            ref={ref => {
              setScriptMultipartInput(ref!);
            }}
          />
        </label>
        <br />
        <br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

The Upload scalar is mapped to MultipartFile in the build.gradle file of the Codegen plugin. You can rename or remove the Upload scalar by modifying this typeMapping. Read more about the typeMapping configuration here.

generateJava {
    typeMapping = [Upload: "org.springframework.web.multipart.MultipartFile"]
}