Armazém

Exemplos de código

Os exemplos usam o client configurado em Conectar aplicações. Eles funcionam como no S3: se você já tem código para S3, basta trocar o endereço e a região.

Metadados

Metadados são pares de nome e valor gravados junto com o arquivo, como quem enviou ou o nome original. Use valores sem acento.

import { HeadObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'

await client.send(new PutObjectCommand({
  Bucket: 'contratos',
  Key: '2026/contrato.pdf',
  Body: arquivo,
  ContentType: 'application/pdf',
  Metadata: { 'enviado-por': 'maria', 'nome-original': 'contrato-2026.pdf' },
}))

const { Metadata, ContentType } = await client.send(new HeadObjectCommand({ Bucket: 'contratos', Key: '2026/contrato.pdf' }))
client.put_object(Bucket='contratos', Key='2026/contrato.pdf', Body=dados, ContentType='application/pdf', Metadata={'enviado-por': 'maria'})
metadados = client.head_object(Bucket='contratos', Key='2026/contrato.pdf')['Metadata']

Listar arquivos

Cada resposta traz até 1.000 arquivos. Use o paginador do SDK para percorrer todos:

import { paginateListObjectsV2 } from '@aws-sdk/client-s3'

for await (const pagina of paginateListObjectsV2({ client }, { Bucket: 'contratos', Prefix: '2026/' })) {
  for (const arquivo of pagina.Contents ?? []) console.log(arquivo.Key, arquivo.Size)
}
for pagina in client.get_paginator('list_objects_v2').paginate(Bucket='contratos', Prefix='2026/'):
    for arquivo in pagina.get('Contents', []):
        print(arquivo['Key'], arquivo['Size'])

Apagar arquivos

Apagar exige uma chave com nível Completo. DeleteObjects apaga até 1.000 arquivos por requisição.

import { DeleteObjectCommand, DeleteObjectsCommand } from '@aws-sdk/client-s3'

await client.send(new DeleteObjectCommand({ Bucket: 'contratos', Key: '2026/rascunho.pdf' }))
await client.send(new DeleteObjectsCommand({
  Bucket: 'contratos',
  Delete: { Objects: [{ Key: '2026/a.pdf' }, { Key: '2026/b.pdf' }] },
}))
client.delete_object(Bucket='contratos', Key='2026/rascunho.pdf')
client.delete_objects(Bucket='contratos', Delete={'Objects': [{'Key': '2026/a.pdf'}, {'Key': '2026/b.pdf'}]})

Tratar erros

Os SDKs devolvem o código de erro do Armazém, o mesmo do S3. A tabela de códigos está em Conectar aplicações.

import { GetObjectCommand, NoSuchKey, S3ServiceException } from '@aws-sdk/client-s3'

try {
  await client.send(new GetObjectCommand({ Bucket: 'contratos', Key: '2026/nao-existe.pdf' }))
} catch (erro) {
  if (erro instanceof NoSuchKey) {
    // arquivo não existe
  } else if (erro instanceof S3ServiceException) {
    console.error(erro.name, erro.$metadata.httpStatusCode)
  } else {
    throw erro
  }
}
from botocore.exceptions import ClientError

try:
    client.get_object(Bucket='contratos', Key='2026/nao-existe.pdf')
except client.exceptions.NoSuchKey:
    pass  # arquivo não existe
except ClientError as erro:
    print(erro.response['Error']['Code'])

Envio direto do navegador

O navegador do usuário envia o arquivo direto ao Armazém, com um link de envio que a sua aplicação gera. O arquivo não passa pelo seu servidor, a chave nunca vai para o navegador e o link só permite enviar aquele arquivo, por pouco tempo.

1. Liberar o domínio do site no bucket

Uma vez por bucket, com uma chave de nível Completo, acrescente uma regra de CORS para o domínio do seu site. O bucket já tem uma regra usada pelo console: leia as regras atuais e acrescente a sua, em vez de substituí-las.

import { GetBucketCorsCommand, PutBucketCorsCommand } from '@aws-sdk/client-s3'

const atuais = await client.send(new GetBucketCorsCommand({ Bucket: 'fotos' }))
  .then((resposta) => resposta.CORSRules ?? [])
  .catch((erro) => { if (erro.name === 'NoSuchCORSConfiguration') return []; throw erro })

const meuSite = {
  ID: 'meu-site',
  AllowedOrigins: ['https://www.meusite.com.br'],
  AllowedMethods: ['PUT', 'GET'],
  AllowedHeaders: ['*'],
  ExposeHeaders: ['ETag'],
  MaxAgeSeconds: 3600,
}

await client.send(new PutBucketCorsCommand({
  Bucket: 'fotos',
  CORSConfiguration: { CORSRules: [...atuais.filter((regra) => regra.ID !== meuSite.ID), meuSite] },
}))

A rota confere quem está enviando, escolhe o nome do arquivo e devolve o link. O link vale para o tipo de arquivo informado; o navegador precisa enviar o mesmo Content-Type.

No JavaScript, crie o cliente que gera links com requestChecksumCalculation: 'WHEN_REQUIRED'. Sem essa opção, as versões recentes do SDK colocam no link um checksum calculado sobre um corpo vazio, que não corresponde ao arquivo enviado depois.

import { PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const url = await getSignedUrl(client, new PutObjectCommand({
  Bucket: 'fotos',
  Key: `usuarios/${usuarioId}/${crypto.randomUUID()}.png`,
  ContentType: 'image/png',
}), { expiresIn: 900 })
url = client.generate_presigned_url(
    'put_object',
    Params={'Bucket': 'fotos', 'Key': f'usuarios/{usuario_id}/{uuid.uuid4()}.png', 'ContentType': 'image/png'},
    ExpiresIn=900,
)

3. Enviar do navegador

const { url } = await fetch('/api/links-de-envio', { method: 'POST' }).then((resposta) => resposta.json())

const resposta = await fetch(url, { method: 'PUT', headers: { 'Content-Type': arquivo.type }, body: arquivo })
if (!resposta.ok) throw new Error(`Envio recusado: ${resposta.status}`)

Para validar tamanho e tipo do lado do servidor, confira o arquivo depois do envio com HeadObject antes de usá-lo.

Arquivos grandes: envio em partes pelo navegador

Para arquivos grandes, o navegador corta o arquivo em partes e envia cada uma com o seu próprio link. A aplicação abre o envio, gera um link por parte e, no fim, junta as partes. Cada parte tem no mínimo 5 MB (a última pode ser menor), e um envio tem até 10.000 partes; o limite é de 20 GB por arquivo. Se a conexão cair, só a parte interrompida precisa ser enviada de novo.

No servidor, abra o envio e gere os links das partes:

import { CompleteMultipartUploadCommand, CreateMultipartUploadCommand, S3Client, UploadPartCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const signer = new S3Client({
  endpoint: 'https://armazem.zenifra.com',
  region: 'br-sp1',
  credentials: { accessKeyId: process.env.ACCESS_KEY_ID!, secretAccessKey: process.env.SECRET_ACCESS_KEY! },
  requestChecksumCalculation: 'WHEN_REQUIRED',
})

const { UploadId } = await signer.send(new CreateMultipartUploadCommand({ Bucket: 'videos', Key: chave, ContentType: 'video/mp4' }))
const links = await Promise.all(Array.from({ length: totalDePartes }, (_, i) =>
  getSignedUrl(signer, new UploadPartCommand({ Bucket: 'videos', Key: chave, UploadId, PartNumber: i + 1 }), { expiresIn: 3600 })))

No navegador, envie as partes e guarde o ETag de cada uma. A regra de CORS do bucket precisa expor o ETag (ExposeHeaders: ['ETag'], como no passo 1):

const TAMANHO_DA_PARTE = 50 * 1024 * 1024
const partes = await Promise.all(links.map(async (link, i) => {
  const resposta = await fetch(link, { method: 'PUT', body: arquivo.slice(i * TAMANHO_DA_PARTE, (i + 1) * TAMANHO_DA_PARTE) })
  if (!resposta.ok) throw new Error(`Parte ${i + 1} recusada: ${resposta.status}`)
  return { PartNumber: i + 1, ETag: resposta.headers.get('ETag') }
}))

Para muitas partes, limite quantas são enviadas ao mesmo tempo (três ou quatro costumam bastar).

De volta ao servidor, junte as partes:

await signer.send(new CompleteMultipartUploadCommand({ Bucket: 'videos', Key: chave, UploadId, MultipartUpload: { Parts: partes } }))

As partes enviadas ficam guardadas até o envio ser concluído ou cancelado. Para retomar um envio, ListParts mostra as partes que já chegaram; para desistir, chame AbortMultipartUpload, que apaga as partes. ListMultipartUploads lista os envios abertos do bucket.

Versões de arquivos

Há duas formas de guardar o histórico de um arquivo.

Um arquivo por versão

Grave cada versão com um nome próprio, por exemplo documentos/edital/2026-11-03T10-00-00.pdf, e liste o prefixo para ver o histórico. Funciona com chaves Leitura e envio e não exige nenhuma configuração no bucket.

Versionamento do bucket

Com o versionamento ligado, cada envio com o mesmo nome guarda a versão anterior, e cada versão ganha um VersionId. Ligar e desligar exige uma chave Completo.

import { GetObjectCommand, ListObjectVersionsCommand, PutBucketVersioningCommand } from '@aws-sdk/client-s3'

await client.send(new PutBucketVersioningCommand({ Bucket: 'documentos', VersioningConfiguration: { Status: 'Enabled' } }))

const { Versions } = await client.send(new ListObjectVersionsCommand({ Bucket: 'documentos', Prefix: 'edital.pdf' }))
const antiga = Versions.find((versao) => !versao.IsLatest)
const arquivo = await client.send(new GetObjectCommand({ Bucket: 'documentos', Key: 'edital.pdf', VersionId: antiga.VersionId }))

Antes de ligar, saiba que:

  • versões antigas contam no armazenamento cobrado, até serem apagadas;
  • apagar um arquivo sem VersionId só o esconde: as versões continuam guardadas. Para liberar o espaço, apague cada versão com DeleteObject informando o VersionId;
  • o console mostra só a versão mais recente de cada arquivo;
  • depois de ligado, o versionamento não volta a desligado, só a suspenso (Status: 'Suspended'): novos envios deixam de criar versões, e as já guardadas continuam.

Próximos passos

Última atualização em

Nessa página