Uploading files with GraphQL in Symfony
Tags: engineeringmonday, symfony, tutorial, graphql
Published: 2020-08-03 09:09:07

###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] (https://github.com/jaydenseric/graphql-multipart-request-spec) that’s widely used in the Apollo ecosystem with [apollo-upload-client] (https://github.com/jaydenseric/apollo-upload-client) and [apollo-server] (https://github.com/apollographql/apollo-server), which allows us to send multipart requests to the GraphQL server. In the Symfony world, Overblog’s [GraphQLBundle] (https://github.com/overblog/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.

```php
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.

```php
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.

```php
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.

```php
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.
__________
If you have any questions, comments or experiences with using GraphQL you'd like to share, put them in the comments section below!