How to upload files with GraphQL in Symfony

File uploads are a common thing in web development and you can most often see it in action when you try to change your account’s profile picture. GraphQL doesn’t have a file as a type defined by the specification, but a GraphQL community found a solution.

There is a spec that’s widely used in the Apollo ecosystem with apollo-upload-client and apollo-server, which allows us to send multipart requests to the GraphQL server. In the Symfony world, Overblog’s GraphQLBundle is one of the most popular options with support for both Apollo and Relay. This bundle also supports file uploads out of the box by providing Upload type and resolving it to UploadedFile object that’s commonly seen in Symfony when working with uploaded files.

Define your schema

In this example we will work with a single Author entity that consists of 3 fields: firstName, lastName and pictureFilename. GraphQL type definition should have exactly the same fields.

Author:
  type: object
  config:
    fields:
      id:
        type: "ID!"
      firstName:
        type: "String!"
      lastName:
        type: "String!"
      profileFilename:
        type: "String!"

Here, we also need only one mutation with arguments for required data and Author type as output. Instead of the pictureFilename defined in Author type, here we will use picture as an argument name.

Mutation:
  type: object
  config:
    fields:
      CreateAuthor:
        type: 'Author!'
        resolve: '@=mutation("App\\GraphQL\\Mutation\\AuthorMutation::createAuthor", [args["firstName"], args["lastName"], args["picture"]])'
        args:
          firstName:
            type: String!
          lastName:
            type: String!
          picture:
            type: AuthorPictureUploadFile!

As you can see, we used custom type for the profile picture field and that is where most of the magic happens. Now, the only thing missing in the schema is to add a definition for it by extending the Upload type provided by bundle.

AuthorPictureUploadFile:
  type: custom-scalar
  config:
    scalarType: '@=newObject("Overblog\\GraphQLBundle\\Upload\\Type\\GraphQLUploadType")'

Handling files in mutation

Now, when we have the schema defined, we can add the mutation logic. In the Mutation type definition, we already specified our mutation class and how it will pass arguments to its createAuthor method. Because the bundle will transform uploaded files to UploadedFile objects before passing them to the mutation, we can easily move them to the desired folder and use the filename to populate entity data.

class AuthorMutation implements MutationInterface
{
    ...

    public function createAuthor(string $firstName, string $lastName, UploadedFile $picture): Author {
        $filename = uniqid('author_picture_').'.'.$picture->guessExtension();
        $picture->move($this->uploadsPath, $filename);

        $author = new Author();
        $author->setFirstName($firstName);
        $author->setLastName($lastName);
        $author->setPictureFilename($filename);

        $this->entityManager->persist($author);
        $this->entityManager->flush();

        return $author;
    }
}

Conclusion

Uploading files with GraphQL is simple and easy with multipart specification widely supported by Apollo and its ecosystem. Even without the official specification regarding uploads, there are a few options available, but the one shown in this post is probably the simplest. Additionally, Symfony also has a great GraphQL bundle to provide server-side runtime with features like batching, file uploads and much more.