lunes, 20 de julio de 2020

Subir, listar y eliminar archivos en AWS S3 desde aplicación de nodejs


En este post intentaré acercar desde mi experiencia el uso de Amazon s3 para el almacenamiento de archivos desde un aplicación de servidor en Node.js. Para ello crearemos una aplicación básica en Express en la que mediante un formulario enviaremos archivos al servidor que se encargará de subirlos a un bucket de S3. Del mismo modo accederemos a nuestro bucket para obtener el listado de los archivos almacenados y eliminaremos los que queramos. Haremos uso de las librerías aws-sdk para acceder a la API de Amazon,  y de Multer para la transferencia de archivos cliente-servidor. Pasaré por alto las explicaciones sobre la creación de la aplicación en sí para centrarme en el uso de la SDK de AWS. Empecemos.

Creando la Aplicación

Necesitaremos instalar tanto Express como Multer y por supuesto la sdk ejecutando desde la terminal el comando siguiente:
	
    npm i express multer aws-sdk
    
Para evitar confusiones dejo aquí abajo las versiones que he utilizado para el ejemplo.

    "aws-sdk": "2.701.0",
    "express": "4.17.1",
    "multer": "1.4.2"

Ahora crearemos el servidor de express, en este ejemplo lo haré de manera básica:

	const express = require("express")

	const app = express()

	app.use(express.static('public'))

	app.get('/', (req, res) => {
            res.render('index')
	})

	app.listen(8000, () => console.log('App in port 8000'))
Lo siguiente es crear nuestra vista 'index.html' donde estará nuestro formulario para cargar los archivos que queramos almacenar en Amazon S3.


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>S3 con Node.js</title>
</head>
<body>
    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="uploadFiles" multiple>
        <input type="submit" value="Send">
    </form>
</body>
</html>

 
El formulario enviará los archivos a la ruta POST "/upload" que después crearemos. Le decimos al input que admita varias entradas con el atributo "multiple" y para que pueda procesar los archivos "enctype="multipart/form-data"". Tendremos lo siguiente:

 
Requerimos Multer y lo configuramos para guardar los archivos en una carpeta llamada "uploads":


const multer = require('multer')({
    dest: path.join(__dirname, 'uploads'),
})


De momento mostraremos los archivos cargados:


app.post('/upload', multer.array('uploadFiles'), (reqres=> {
    res.json(req.files)
})



Subiendo archivos a S3

En este punto ya tenemos los archivos en nuestro servidor y los podemos procesar accediendo a ellos mediante el atributo "path". En nuestro caso solo queremos subirlos a nuestro bucket, así que requeriremos la libreria "aws-sdk" y la prepararemos para ello. En este punto ya debemos tener nuesta instancia de S3 creada asi como el bucket y haber obtenido las claves de acceso


const AWS = require('aws-sdk')
const fs = require('fs')

//-> Le pasamos nuestros datos
AWS.config.update({
    accessKeyId: 'nuestra-Key-ID,
    secretAccessKey: 'nuestra-Secret-Key',
    region: 'la región escogida, p.ej: "us-east-1a"
})

Recuerda guardar tus claves siempre en lugar seguro y acceder a ellas mediante variables de entorno. Si vas a compartir tu código en un repo añade el archivo donde tengas las claves (.env) a tu .gitignore.

Ahora modificamos nuestra función de la ruta POST para que al cargarlos en el servidor subirlos directamente a S3:


app.post('/upload', multer.array('uploadFiles'), async (reqres=> {
     
    req.files.forEach(async file => {
        const s3 = new AWS.S3()
        const s3params = {
            Bucket: 'nombre-del-bucket',
            Body: fs.createReadStream(file.path),
            Key: file.originalname
        }
    
        const imageUploaded = await s3.upload(s3params, function (err) {
            if (err) {
                throw new Error("Error"err)
            }
        }).promise()
    
        console.log('imageUploaded', imageUploaded.Location)
    
         fs.unlink(file.path, err => {
            if(err) console.error(err)
        })
    })

    res.send('Files Uploaded')
        
})


Una vez cargados los eliminamos de nuestro servidor con "fs.unlink".

Listando los archivos del bucket

Si vamos a nuestra consola de AWS podremos ver nuestros objetos alojados en ele bucket, pero nosotros queremos mostrarlos en nuestra apicación. Para ello creamos el endpoint correspondiente:


app.get('/objects', (reqres=> {
    res.send('Objects')
})


Pero vamos a devolver un json con los nuestros archivos. Para esto tenemos el método "listObjectsV2". 


app.get('/objects'async (reqres=> {

    const s3 = new AWS.S3()
    const s3params = {
        Bucket: S3_BUCKET_NAME
    }

    const objects = await s3.listObjectsV2(s3params, function(errdata) {
        if(err){
            throw new Error("Error"err)
        }
    }).promise()

    res.json(objects.Contents)

})


Si queremos filtrar los resultados en los parámetros añadiremos "Prefix" con el valor que nos interese. Es decir, si quiero mostrar solo los archivos que he guardado en una carpeta llamada "pdf" nuestro objeto s3params quedará así:

        
const s3params = {
    Bucket: S3_BUCKET_NAME,
    Prefix: 'pdf/'
}

Nuestra vista "/objects" nos debería mostrar un json con la información de los archivos:

[{"Key":"file1.pdf","LastModified":"2020-07-19T08:28:01.000Z","ETag":"\"db07bd7afbd0769aca43b4da1b7d7d41\"","Size":1950,"StorageClass":"STANDARD"}, {"Key":"file2.txt","LastModified":"2020-07-19T08:27:23.000Z","ETag":"\"47f35abe7d9134d156fd1f38a4655d77\"","Size":1962,"StorageClass":"STANDARD"}]


Eliminando archivos

Para eliminar archivos del bucket "aws-sdk" nos ofrece el método "deleteObject". Solo tenemos que pasarle al objeto params el nombre del archivo.

Para simplificarlo crearemos el endpoint y le pasaremos en la ruta el nombre del archivo que queremos eliminar. Nuestro servidor quedaría asi: 


app.get('/delete/:key'async (reqres=> {

    const s3 = new AWS.S3()
    const s3params = {
        Bucket: S3_BUCKET_NAME,
        Key: req.params.key 
    }
    
    await s3.deleteObject(s3params, function(errdata){
        if (err) {
            console.error(err)            
            throw new Error("Error"err)
        }

    }).promise()    

    res.json({ Deleted: req.params.key })
})


 Entonces para eliminar el primer archivo de la lista visitaremos "http://localhost:8000/delete/file1.pdf". Obtendríamos: 

{"Deleted":"file1.pdf"}

Con el método "deleteObjects" (en plural) podemos eliminar varios archivos. la diferencia es que en s3params debemos incluir la llave "Delete: { Objects: "array de objetos" }". Por ejemplo:

           
const s3params = {
    Bucket: S3_BUCKET_NAME,
    Delete: { Objects: [{ Key: key1 }, { Key: key2 }, ...] }
}



Hay muchas más opciones, como eliminar un bucket entero, etc. Para no complicarlo más lo dejaremos así. 

Nada más. Un saludo y comentad abajo cualquier duda que tengáis. Gracias.





No hay comentarios:

Publicar un comentario