Upload de arquivos com Apollo GraphQL (React + Express)

quinta-feira, 1 de fevereiro de 2024 às 18:54:28 Horário Universal Coordenado

O surgimento da ideia

Recentemente, enquanto eu estava criando uma API utilizando Apollo GraphQL, tive a ideia de implementar o upload de arquivos. Isso facilitaria o envio de mídias diversas e documentos através de formulários.

No entanto, a função Upload não possui nenhuma documentação oficial da Apollo. Ressalto que foi extremamente trabalhoso fazer isso funcionar. Então, se essa publicação te ajudar de alguma forma, peço por gentileza que dê uma ✨ no repositório do projeto.

Por que usar GraphQL?

Apesar de exigir um trabalho considerável no início para configurar, o GraphQL facilita e muito implementações futuras. Afinal, depois de tudo configurado, você só precisa escrever a Query ou Mutation e esperar o resultado no lado do cliente. O Apollo Client oferece hooks extremamente sofisticados para realização das operações.

Introdução

Pensando nisso, decedi escrever esse post com os passos que segui para conseguir um resultado satisfatório. O resultado final dessa implementação pode ser encontrado no seguinte respositório no Github!

Passos

Servidor (Express):

Após configurado um server Apollo básico... Tutorial

Instale a seguinte dependência:

$ yarn add graphql-upload-ts stream-to-blob @aws-sdk/client-s3 sharp

Instale as tipagens:

$ yarn add @types/stream-to-blob -D

Primeiramente, adicione a middleware de upload em seu server Express:

Obs: Ela deve ser adicionada antes de qualquer outra!

server.use(
  '/gql',
  graphqlUploadExpress({ maxFileSize: 1000000000, maxFiles: 10 })
)
server.use(express.json())

No código acima, você pode configurar a quantidade máxima de arquivos que podem subir ao mesmo tempo e também o tamanho máximo deles.


Agora crie ou abra a Mutation onde você quer receber o(s) arquivo(s). No meu caso, eu previsava criar uma publicação no meu blog:

createBlogPost: async (parent: any, {content, groupSlug, attachments}: any, contextValue: any) => {
  
  for(const attachment of await attachments){
    const { createReadStream, filename, mimetype, encoding } = attachment.file
    const stream: ReadStream = createReadStream()
    const blob = await streamToBlob(stream, mimetype)

    const res = await files.upload(blob, filename.split(".")[0], `groups/${groupSlug}/${id}`)
  }

}

No código acima, o loop acessa todos os arquivos que vierem pelo argumento "attachments" e cria um Blob para cada que pode ser usado para fazer upload para um Bucket S3 por exemplo.

Nesse caso, o upload é realizado pelo objeto files fruto da classe que está no arquivo em lib/S3.ts no repositório do projeto.


Cliente (Vite):

Após configurado um cliente básico Apollo... Tutorial

Instale as dependências:

yarn add apollo-upload-client

Crie o script da Mutation:

const CREATE_POST = gql`
  mutation (
    $attachments: [Upload]
  ) {
    createGroupPost (
      attachments: $attachments
    ) {
      id content attachments
    }
  }
`

Prepare o método de criação:

const [ createPost, { loading, error } ] = useMutation(CREATE_POST)

E finalmente, no momento da chamada, coloque os arquivos no escopo de variáveis:

createPost({
    variables: {
      attachments: formData.files
    }
}

No exemplo acima, o objeto formData é fruto de um formulário.

🎉Pronto! 🎉

Agora você será capaz de subir arquivos para a sua API usando GraphQL!