Upload de Arquivos no R2 da Cloudflare com Signed URLs e GraphQL.

Dizem que, se queremos aprender algo, é só colocar de forma errada na internet que sempre aparece alguém te xingando e corrigindo. 😅

Então, vou mostrar como fiz o upload de arquivos para o R2 usando signed URLs e uma API GraphQL. Essa é uma aplicação que eu fiz, e agora precisei implementar o upload de arquivos, algo que nunca tinha feito antes.

Para simplificar, não vou mostrar tratamentos de erros, controles de carregamento, etc... Mas pode ficar tranquilo que eles existem, viu? 😂

Fluxo

Etapa 1: (Aplicação) Solicitar uma URL Assinada

  1. A aplicação envia uma requisição para a API solicitando a URL assinada.
  2. Incluímos os dados necessários no corpo da requisição ou nos parâmetros, como:
    • Tipo de conteúdo (ex.: image/jpeg)
    • Nome do arquivo (ex.: relatorio.jpeg)

Exemplo de Requisição:

Como podemos fazer upload de múltiplos arquivos, utilizamos Promise.all para executar as requisições de forma paralela. Para cada arquivo, enviamos o tipo e o nome, permitindo que a API gere uma URL assinada específica para cada um.

const [generateSignedUrl] = useMutation(CREATED_URL_SIGNED)

const { data } = await generateSignedUrl({
			variables: { contentType: filetype, key: fileName },
	})

const signedUrls = await Promise.all(
				fileList.map(async (file) => {
					const { data } = await generateSignedUrl({
						variables: { contentType: file.type, key: file.name },
					})
					return { file, signedUrl: data.generateSignedUrl }
    }))

Etapa 2: (API) Gerar a URL assinada com o token.

  1. Na API, receba os dados da solicitação e valide o nome do arquivo e tipo de conteúdo, garantindo que são permitidos.
  2. Utilize as credenciais da Cloudflare para acessar o R2 e gerar a URL assinada.
  3. Configure os seguintes parâmetros:
    • Método: PUT (para upload do arquivo)
    • Tempo de expiração da URL (ex.: 15 minutos)
  4. Retorne a URL assinada.
//cliente r2
export const r2 = new S3Client({
	region: 'auto',
	endpoint: process.env.CLOUDFLARE_ENDPOINT,
	credentials: {
		accessKeyId: process.env.CLOUDFLARE_ACCESS_KEY_ID,
		secretAccessKey: process.env.CLOUDFLARE_SECRET_ACCESS_KEY,
	},
})

//resolver
export default {
    Mutation: {
        generateSignedUrl: async (_, { fileType, contentType }) => {
        try {
            const command = new PutObjectCommand({
                Bucket: process.env.CLOUDFLARE_BUCKET,
                Key: fileType,
                ContentType: contentType,
            });

            const signedUrl = await getSignedUrl(r2, command, { expiresIn: 3600 }); 

            return signedUrl;
        } catch (error) {
            console.error('Erro ao gerar URL assinada:', error);
            throw new Error('Não foi possível gerar a URL assinada.');
        }
    },
}}

//schema
type Mutation {
  generateSignedUrl( key: String!, contentType: String!): String!
}

Se tudo der certo, a API retornará uma URL assinada que pode ser usada para fazer o upload do arquivo para o bucket S3.

Etapa 3: (Aplicação) Realizar o upload do arquivo usando a URL assinada.

  1. Após receber a URL assinada da API, use-a para fazer o upload do arquivo diretamente para o R2.
  2. Utilize fetch ou outra biblioteca HTTP para enviar o arquivo no método PUT.
  3. Inclua o cabeçalho correto para o tipo de conteúdo.
	await Promise.all(
				signedUrls.map(({ file, signedUrl }) =>
					axios.put(signedUrl, file, {
						headers: { 'Content-Type': file.type },
					}),
				),
			)

Pronto, os arquivos estão salvos. O downloader fica para outra hora. Hoje, a aplicação não tem a necessidade.