Exportar arquivos para o Amazon S3 no Node.js: guia completo
Use PutObjectCommand para um upload simples e Upload de @aws-sdk/lib-storage para uploads
em várias partes. A CLI abaixo exporta um arquivo local para o Amazon S3, baixa o objeto resultante e
confere seu SHA-256, tamanho e metadados antes de informar o sucesso. Ambas as formas de upload
retornam um status de saída de falha quando o upload ou a verificação falha.
Pré-requisitos
Você precisa do Bash no Linux, do Node.js, do Corepack instalado separadamente e de um bucket S3 de uso geral, privado e já existente. Este exemplo foi testado com Node.js 24.21.0 e 26.8.1, Corepack 0.36.0, Yarn 4.12.0 e pacotes do AWS SDK 3.1149.0. Use uma versão 24 LTS ou 26 do Node.js que ainda receba manutenção; essas são versões testadas, e não um requisito mínimo intrínseco do SDK. O Node.js 26 não inclui o Corepack.
Use um arquivo local comum e mantenha seu conteúdo inalterado até a verificação terminar. Coloque o
caminho entre aspas ao executar a CLI. Há suporte para espaços, sinais de porcentagem literais e
caracteres Unicode suplementares. Caminhos que contêm o caractere de substituição Unicode
U+FFFD são rejeitados, inclusive nomes de arquivo legítimos que contenham esse
caractere. O Node.js pode substituir bytes inválidos dos argumentos nativos por esse caractere,
tornando dois nomes de arquivo diferentes indistinguíveis. Renomeie esses arquivos antes de usar esta
CLI.
Escolha uma chave de objeto ainda não utilizada sob um prefixo que você controla. Este exemplo se
recusa a substituir um objeto existente. A credencial precisa de s3:PutObject para
uploads, de s3:GetObject para verificação e de
s3:AbortMultipartUpload para limpeza após falhas em uploads em várias partes.
Configurar credenciais da AWS
O SDK usa sua cadeia padrão de provedores de credenciais.
Use um perfil da AWS já autenticado localmente ou uma função do IAM na AWS. Credenciais temporárias
em variáveis de ambiente também funcionam; elas exigem AWS_SESSION_TOKEN, além da chave
de acesso e da chave secreta. Não coloque credenciais no programa nem as inclua em commits do projeto.
Os comandos abaixo leem a região do bucket de AWS_REGION. Para um perfil
compartilhado nomeado, defina AWS_PROFILE no shell que executa os comandos. Se as
credenciais expirarem, renove-as pelo seu processo habitual de autenticação antes de tentar novamente.
O exemplo não cria um bucket nem configura o IAM.
Instalar e usar o AWS SDK
Cole este bloco no diretório em que você quer criar um projeto s3-export.
Ele verifica cada executável antes de criar arquivos e roda em um subshell, preservando o diretório
e as opções do shell de origem. Se o diretório s3-export já existir, a
configuração será interrompida antes que qualquer arquivo seja sobrescrito.
(
set -eu
command -v node >/dev/null || { printf 'Install Node.js first.\n' >&2; exit 1; }
command -v corepack >/dev/null || { printf 'Install Corepack first.\n' >&2; exit 1; }
mkdir s3-export
cd s3-export
cat > package.json <<'JSON'
{
"name": "s3-export",
"private": true,
"type": "module",
"packageManager": "yarn@4.12.0",
"dependencies": {
"@aws-sdk/client-s3": "3.1149.0",
"@aws-sdk/lib-storage": "3.1149.0"
}
}
JSON
touch yarn.lock
corepack yarn install
)
O manifesto local e o arquivo de lock dão a esse subprojeto seu próprio escopo de pacote e módulo,
mesmo dentro de um projeto npm ou Yarn. O Yarn ainda lê as configurações aplicáveis de
.yarnrc.yml no diretório pessoal e nos diretórios ancestrais.
Confira a versão efetiva com corepack yarn --version a partir de
s3-export; ela deve ser 4.12.0.
Se uma política herdada de yarnPath, do registro de pacotes ou da rede impedir a
instalação, resolva essa política com o responsável pelo projeto ou use um diretório fora dessa
configuração. Não a contorne silenciosamente.
Se a instalação falhar após a criação do projeto, mantenha os arquivos e tente instalar novamente a partir do shell de origem:
(cd s3-export && corepack yarn install)
Salve este programa completo como s3-export/export.ts:
import { createHash } from 'node:crypto'
import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import { Readable } from 'node:stream'
import {
GetObjectCommand,
PutObjectCommand,
S3Client,
S3ServiceException,
type PutObjectCommandInput,
} from '@aws-sdk/client-s3'
import { Upload } from '@aws-sdk/lib-storage'
class ReaderError extends Error {}
async function digest(source: AsyncIterable<Uint8Array>): Promise<{ bytes: number; sha256: string }> {
const hash = createHash('sha256')
let bytes = 0
for await (const chunk of source) {
bytes += chunk.byteLength
hash.update(chunk)
}
return { bytes, sha256: hash.digest('hex') }
}
async function main(): Promise<{ bucket: string; key: string; bytes: number; sha256: string }> {
const [mode, file, bucket, key, ...extra] = process.argv.slice(2)
if (!['put', 'multipart', 'verify'].includes(mode ?? '') || !file || !bucket || !key || extra.length) {
throw new ReaderError('Usage: export.ts put|multipart|verify FILE BUCKET KEY')
}
if (file.includes('\uFFFD')) {
throw new ReaderError('FILE must not contain U+FFFD; rename the file before exporting it.')
}
const region = process.env.AWS_REGION
if (!region) throw new ReaderError('Set AWS_REGION to the bucket’s region.')
const info = await stat(file)
if (!info.isFile()) throw new ReaderError('FILE must be a regular file.')
const expected = await digest(createReadStream(file))
if (expected.bytes !== info.size) throw new ReaderError('The input changed while being read.')
const client = new S3Client({ region, maxAttempts: 1 })
try {
if (mode !== 'verify') {
const body = createReadStream(file)
try {
const params: PutObjectCommandInput = {
Bucket: bucket,
Key: key,
Body: body,
ContentLength: expected.bytes,
ContentType: 'application/octet-stream',
Metadata: { sha256: expected.sha256 },
ServerSideEncryption: 'AES256',
IfNoneMatch: '*',
}
if (mode === 'put') {
await client.send(new PutObjectCommand(params))
} else {
const upload = new Upload({
client,
params,
queueSize: 2,
leavePartsOnError: false,
})
upload.on('httpUploadProgress', ({ loaded, total }) => {
console.error(`Transferred ${loaded ?? 0}/${total ?? expected.bytes} bytes`)
})
await upload.done()
}
} finally {
body.destroy()
}
}
const stored = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }))
if (!(stored.Body instanceof Readable)) throw new ReaderError('No readable S3 response body.')
const actual = await digest(stored.Body)
if (
actual.bytes !== expected.bytes ||
actual.sha256 !== expected.sha256 ||
stored.ContentLength !== expected.bytes ||
stored.Metadata?.sha256 !== expected.sha256 ||
stored.ContentType !== 'application/octet-stream' ||
stored.ServerSideEncryption !== 'AES256'
) {
throw new ReaderError('Stored bytes or metadata do not match the input.')
}
return { bucket, key, ...actual }
} finally {
client.destroy()
}
}
main()
.then((outcome) => console.log(JSON.stringify(outcome)))
.catch((error: unknown) => {
if (error instanceof ReaderError) {
console.error(error.message)
} else {
const reason = error instanceof S3ServiceException ? error.name : 'LocalError'
console.error(`Export failed (${reason}). Check the file, region, credentials, and permissions.`)
}
process.exitCode = 1
})
Execute o bloco a seguir a partir do diretório que contém s3-export. Substitua a
região, o bucket e a chave pelo seu destino. A primeira colagem cria sample.txt e
executa o upload. Se esse arquivo local já existir, o Bash se recusa a sobrescrevê-lo:
o bloco termina com status diferente de zero, preserva o conteúdo desse arquivo local e não executa o
comando de upload.
(
cd s3-export &&
(set -C; printf 'S3 export example\n' > sample.txt) &&
AWS_REGION=us-east-1 corepack yarn node export.ts put sample.txt YOUR_BUCKET exports/sample.txt
)
Para fazer upload do arquivo de exemplo existente para uma nova chave, execute apenas o último
comando do bloco a partir de s3-export, substituindo
exports/sample.txt por uma chave ainda não utilizada. Se um upload anterior pode ter sido
bem-sucedido, use primeiro o comando verify para
inspecionar a chave original sem fazer upload novamente.
Uma execução bem-sucedida imprime um JSON com o bucket, a chave, a contagem de bytes e o SHA-256 exatos. A verificação lê o objeto de volta a partir dessa chave; ela não trata um ETag nem a resposta do upload como uma soma de verificação do arquivo local. O download custa uma requisição adicional e lê todos os bytes novamente.
Lidar com arquivos grandes usando upload em várias partes
O utilitário de upload em várias partes
divide um fluxo em partes mantidas em buffer. Para uma entrada de tamanho conhecido, ele
escolhe um tamanho de parte
de pelo menos 5 MiB, ajustado ao limite de quantidade de partes. queueSize: 2
permite o upload de duas partes por vez; ele não carrega o arquivo inteiro na memória. Entradas
pequenas, incluindo arquivos vazios, usam um único PUT.
Use o mesmo programa e use multipart no lugar de put:
(
cd s3-export &&
AWS_REGION=us-east-1 corepack yarn node export.ts multipart /path/to/large-file.zip YOUR_BUCKET exports/large-file.zip
)
O progresso é impresso em stderr. Mesmo um evento de progresso que mostre todos os bytes transferidos
não indica conclusão: o programa aguarda upload.done() e depois verifica o objeto
armazenado. leavePartsOnError: false solicita que o utilitário aborte o upload após uma falha no
upload de uma parte. Nesta versão fixada, um erro de
CompleteMultipartUpload,
incluindo uma colisão de chaves, deixa o upload incompleto pendente.
Peça ao proprietário do bucket que inspecione e aborte esse upload; encerrar o processo também pode
deixar partes pendentes.
O S3 permite até 5 GB em um único PUT e até 50 TB por upload em várias partes, mas o utilitário JavaScript e sua quantidade máxima de partes não estabelecem suporte a todos os limites máximos de tamanho do serviço. Para objetos acima de 5 TB, o guia de upload da AWS orienta você a usar o S3 Transfer Manager em Java, Python ou a AWS CLI. Este tutorial verifica o comportamento de uploads simples e em várias partes com pequenos arquivos de teste; ele não é uma receita de transferência de 50 TB.
Boas práticas de segurança
Mantenha o bucket privado e restrinja o acesso ao prefixo pretendido. Para as operações de upload e verificação deste programa e de aborto de uploads em várias partes do utilitário, um administrador pode conceder acesso com esta política no nível de objeto, substituindo o bucket e o prefixo:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:AbortMultipartUpload"],
"Resource": "arn:aws:s3:::YOUR_BUCKET/exports/*"
}
]
}
Essas permissões seguem os requisitos das operações em várias partes
e os requisitos de GetObject.
As políticas do bucket ainda podem negar uma requisição. O programa usa HTTPS e solicita
explicitamente criptografia gerenciada pelo S3 com AES256; ele não solicita
uma ACL pública. Um bucket que exige SSE-KMS precisa de outra configuração de criptografia e das
permissões KMS aplicáveis. Portanto, siga a política desse bucket em vez de adicionar permissões
amplas a este exemplo.
Tratar erros e novas tentativas
A condição IfNoneMatch: '*'
rejeita uma chave existente na gravação final do upload. Uma colisão retorna falha e preserva o
objeto existente; use uma nova chave em vez de remover dados de outra pessoa. Ambos os modos de
upload também falham se as credenciais forem rejeitadas ou se o arquivo solicitado não existir.
maxAttempts: 1 configura o SDK para uma tentativa por requisição. A configuração conta
a requisição inicial, portanto um valor maior permite novas tentativas. Um fluxo de PUT único já
consumido não pode simplesmente ser reutilizado; o utilitário de upload em várias partes mantém as
partes em buffer, permitindo que o SDK repita as requisições dessas partes. Se você executar toda a
CLI novamente, ela abrirá um novo fluxo. Uma falha no upload pode deixar o resultado armazenado
incerto, e uma falha na verificação pode ocorrer após um upload bem-sucedido. Inspecione essa chave
exata antes de tentar novamente. Para verificar sem fazer upload novamente, execute:
(
cd s3-export &&
AWS_REGION=us-east-1 corepack yarn node export.ts verify sample.txt YOUR_BUCKET exports/sample.txt
)
Isso confere os bytes e metadados com base no arquivo local atual e termina com status diferente de zero se houver divergência. Não exclui um objeto divergente nem desfaz um upload concluído.
Suporte a TypeScript
O Node.js executa este arquivo usando a remoção nativa de tipos.
A configuração declara "type": "module", e corepack yarn node fornece o
carregador de dependências do Yarn, inclusive em projetos Plug’n’Play. Não é preciso usar
tsx ou ts-node, nem realizar uma etapa de compilação.
A execução nativa não verifica tipos: PutObjectCommandInput ajuda seu editor a conferir os
campos das requisições, enquanto as verificações em tempo de execução continuam validando os
argumentos e os resultados armazenados.
Conclusão
Use o JSON impresso como resultado de um upload e de uma verificação concluídos e mantenha stderr disponível ao diagnosticar uma falha. Se a exportação for uma etapa de um fluxo de processamento de arquivos mais amplo, conheça nossos serviços.
