Exportar archivos a Amazon S3 en Node.js: guía completa
Usa PutObjectCommand para una subida simple y Upload de @aws-sdk/lib-storage para subidas
multiparte. La CLI siguiente exporta un archivo local a Amazon S3, descarga el objeto resultante y
comprueba su SHA-256, tamaño y metadatos antes de informar del éxito. Ambas modalidades de subida
devuelven un estado de salida que indica un fallo si falla la subida o la verificación.
Requisitos previos
Necesitas Bash en Linux, Node.js, Corepack instalado por separado y un bucket de S3 privado, de propósito general, ya existente. Este ejemplo se probó con Node.js 24.21.0 y 26.8.1, Corepack 0.36.0, Yarn 4.12.0 y los paquetes del SDK de AWS 3.1149.0. Usa una versión de Node.js 24 LTS o 26 que siga recibiendo mantenimiento; estas son versiones probadas, no un mínimo intrínseco del SDK. Node.js 26 no incluye Corepack.
Usa un archivo local normal y mantén su contenido sin cambios hasta que termine la verificación.
Pon su ruta entre comillas al invocar la CLI. Se admiten espacios, signos de porcentaje literales y
caracteres Unicode suplementarios. Se rechazan las rutas que contienen el carácter de sustitución
Unicode U+FFFD, incluso si el nombre del archivo contiene legítimamente ese carácter.
Node.js puede sustituir los bytes no válidos de los argumentos nativos por ese carácter, lo que hace
que dos nombres de archivo diferentes resulten indistinguibles. Cambia el nombre de esos archivos
antes de usar esta CLI.
Elige una clave de objeto sin usar bajo un prefijo que te pertenezca. Este ejemplo se niega a
reemplazar un objeto existente. La credencial necesita s3:PutObject para las subidas,
s3:GetObject para la verificación y s3:AbortMultipartUpload para la limpieza tras fallos en subidas multiparte.
Configurar las credenciales de AWS
El SDK usa su cadena predeterminada de proveedores de credenciales.
Usa un perfil de AWS existente y autenticado en tu entorno local, o un rol de IAM en AWS. También
funcionan las credenciales temporales del entorno; requieren AWS_SESSION_TOKEN, además de la clave
de acceso y la clave secreta. No incluyas credenciales en el programa ni las guardes en el repositorio
junto con el proyecto.
Los comandos siguientes leen la región del bucket desde AWS_REGION. Para un perfil
compartido con nombre, define AWS_PROFILE en el shell que ejecuta los comandos. Si las
credenciales caducan, renuévalas mediante tu proceso habitual de autenticación antes de volver a
intentarlo. El ejemplo no crea un bucket ni configura IAM.
Instalar y usar el SDK de AWS
Pega este bloque en el directorio donde quieras crear un proyecto s3-export. Comprueba
cada ejecutable antes de crear archivos y se ejecuta en un subshell, por lo que deja intactos el
directorio y las opciones del shell desde el que lo invocas. Si ya existe un directorio
s3-export, la configuración se detiene antes de sobrescribir cualquier archivo.
(
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
)
El manifiesto local y el archivo de bloqueo delimitan los paquetes y módulos de este proyecto hijo,
incluso dentro de un proyecto npm o Yarn. Yarn sigue leyendo la configuración aplicable de
.yarnrc.yml en el directorio de inicio y en los directorios superiores.
Comprueba la versión efectiva con corepack yarn --version desde s3-export; debería ser 4.12.0.
Si un valor heredado de yarnPath, un registro o una política de red impiden la
instalación, resuelve esa política con el propietario del proyecto o usa un directorio fuera de esa
configuración. No la eludas de forma silenciosa.
Si la instalación falla después de crear el proyecto, conserva los archivos y vuelve a intentar la instalación desde el shell que lo invocó:
(cd s3-export && corepack yarn install)
Guarda 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
})
Ejecuta lo siguiente desde el directorio que contiene s3-export. Sustituye la región,
el bucket y la clave por los de tu propio destino. El archivo de ejemplo se crea solo si no existe,
por lo que una nueva ejecución conserva su contenido:
(
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
)
Una ejecución exitosa imprime JSON con el bucket, la clave, el número de bytes y el SHA-256 exactos. La verificación vuelve a leer el objeto desde esa clave; no considera el ETag ni la respuesta de la subida como una suma de comprobación del archivo local. La descarga supone una solicitud adicional y vuelve a leer todos los bytes.
Gestionar archivos grandes con subidas multiparte
El componente auxiliar de subidas multiparte
divide un flujo en partes que almacena en búfer. Para una entrada de longitud conocida,
elige un tamaño de parte
de al menos 5 MiB, ajustado al límite del número de partes. queueSize: 2 permite subir dos
partes a la vez; no carga todo el archivo en memoria. Las entradas pequeñas, incluidos los archivos
vacíos, usan en cambio un único PUT.
Usa el mismo programa y usa multipart en 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
)
El progreso se imprime en stderr. Incluso un evento de progreso que muestra todos los bytes
transferidos no significa que la operación haya terminado: el programa espera a
upload.done() y después verifica el objeto almacenado. leavePartsOnError: false
pide al componente auxiliar que aborte la subida si falla la subida de una parte. En esta versión
fijada, un error de
CompleteMultipartUpload,
incluida una colisión de claves, deja la subida sin terminar.
Pide al propietario del bucket que inspeccione y aborte esa subida; terminar el proceso también
puede dejar partes pendientes.
S3 permite hasta 5 GB en un único PUT y hasta 50 TB mediante subidas multiparte, pero el componente auxiliar de JavaScript y su número máximo de partes no garantizan que admita todos los tamaños máximos del servicio. Para objetos de más de 5 TB, la guía de subidas de AWS te remite al S3 Transfer Manager en Java, Python o la CLI de AWS. Este tutorial verifica el comportamiento de las subidas simples y multiparte con archivos de prueba pequeños; no es una guía para transferir 50 TB.
Buenas prácticas de seguridad
Mantén el bucket privado y limita el acceso al prefijo previsto. Para las operaciones de subida y verificación de este programa, y las operaciones de aborto de subidas multiparte del componente auxiliar, un administrador puede conceder acceso con esta política a nivel de objeto, sustituyendo el bucket y el prefijo:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:AbortMultipartUpload"],
"Resource": "arn:aws:s3:::YOUR_BUCKET/exports/*"
}
]
}
Estos permisos siguen los requisitos de las operaciones multiparte
y los requisitos de GetObject.
Las políticas del bucket pueden seguir denegando una solicitud. El programa usa HTTPS y solicita
explícitamente cifrado administrado por S3 con AES256; no solicita una ACL pública.
Un bucket que requiera SSE-KMS necesita una configuración de cifrado diferente y los permisos de
KMS correspondientes, así que usa la política de ese bucket en lugar de añadir permisos amplios a
este ejemplo.
Gestionar errores y reintentos
La condición IfNoneMatch: '*'
rechaza una clave existente en la escritura final de la subida. Una colisión devuelve un fallo y
conserva el objeto existente; usa una clave nueva en lugar de eliminar los datos de otra persona.
Ambas modalidades de subida también fallan si se rechazan las credenciales o no existe el archivo
solicitado.
maxAttempts: 1 configura el SDK para realizar un intento por solicitud. La configuración
cuenta la solicitud inicial, por lo que un valor mayor permite reintentos. Un flujo de un único PUT
ya consumido no puede volver a enviarse sin más; el componente auxiliar de subidas multiparte
almacena las partes en búfer, lo que permite al SDK reintentar las solicitudes de esas partes. Si
vuelves a ejecutar toda la CLI, esta abre un flujo nuevo. Un fallo de subida puede dejar un resultado
almacenado incierto, y la verificación puede fallar después de que la subida haya tenido éxito.
Inspecciona esa clave exacta antes de volver a intentarlo. Para verificar sin volver a subir, ejecuta:
(
cd s3-export &&
AWS_REGION=us-east-1 corepack yarn node export.ts verify sample.txt YOUR_BUCKET exports/sample.txt
)
Esto comprueba los bytes y los metadatos frente al archivo local actual y termina con un código de salida distinto de cero si hay una discrepancia. No elimina un objeto que no coincida ni revierte una subida completada.
Soporte para TypeScript
Node.js ejecuta este archivo mediante la
eliminación nativa de tipos.
La configuración declara "type": "module", y corepack yarn node proporciona el cargador de
dependencias de Yarn, también en proyectos Plug’n’Play. No se necesita tsx,
ts-node ni un paso de compilación. La ejecución nativa no comprueba los tipos:
PutObjectCommandInput ayuda a tu editor a comprobar los campos de las solicitudes, mientras que las
comprobaciones en tiempo de ejecución siguen validando los argumentos y los resultados almacenados.
Conclusión
Usa el JSON impreso como resultado de una subida y una verificación completadas, y conserva stderr para diagnosticar fallos. Si la exportación es un paso de un flujo de trabajo más amplio de procesamiento de archivos, consulta nuestros servicios.
