Minio como alternativa ao S3 em aplicações Node
Tags: minio, javascript, tutorial, braziliandevs
Published: 2020-06-11 22:13:14

A facilidade em criar buckets e enviar arquivos para o S3 da Amazon é algo fantástico. Armazenar objetos, ao invés de ter um sistema de arquivos, diminui a complexidade e traz uma eficiência à esse tipo de operação.

Um dos problemas da Amazon é o seu valor. Em projetos menores, que não se beneficiam da grande disponibilidade da plataforma, é comum adotar - pelo menos para hospedar a aplicação - a Digital Ocean. Nesse artigo, vamos utilizar uma "s3 própria" por meio do Minio em uma API básica para upload de arquivos, feita com Node.js e Javascript.

> Curte uma trilha sonora? Eu fiz esse artigo ouvindo [essa playlist no spotify](https://open.spotify.com/playlist/6gPhzZxZcmMFYvRts5WsLi?si=cFSVPx39QQ6KGq6-Vgcelg). 

# O que é o Minio?
Segundo a [própria documentação](https://docs.min.io/), o Minio é uma tecnologia de armazenamento baseada em objetos, de alta performance. Ele é escrito em Go e tem uma API compatível com o AWS S3.

![Minio Docs page](https://dev-to-uploads.s3.amazonaws.com/i/2xmp91bi1yyyp48h92qk.png)
 
Vale destacar a questão da compatibilidade. Como veremos, é possível até configurar o cliente do S3 da SDK para Javascript de modo que ele utilize, na verdade, o minio.

# Criando a API
> Aqui, suponho que você possua um projeto configurado para utilizar ES6 Modules - uma forma simples é por meio do Babel. Caso precise, utilize [este template](https://github.com/jrmmendes/js-with-babel).

O primeiro passo é criar uma API para que possamos testar, no futuro, a funcionalidade. Utilizando express, teríamos algo assim:

```javascript
import express from 'express';
import { S3 } from 'aws-sdk';
import cors from 'cors';
import morgan from 'morgan';

const app = express();
app.use(cors());
app.use(morgan('dev'));

app.listen(3000, () => console.log('[READY]')); 
```
Depois disso, é necessário criar uma rota que trate o upload de arquivos individuais. Aqui faremos uso do Multer:

```javascript
app.route('/upload').post(
  multer({ storage: multer.memoryStorage() }).single('file'),
  uploadHandler,
);
```

### Upload Handler
É nessa função em que utilizamos a API da S3:

```javascript
const uploadHandler = async (req, res) => {
  try {
    await s3.putObject({
      Bucket: 'tests',
      Key: req.file.originalname,
      Body: req.file.buffer,
    }).promise();
    return res
      .status(201)
      .send({
        message: `File "${req.file.originalname}" uploaded`
      });
  } catch (e) {
    console.log(e);
      return res
        .status(500)
        .send({ error: e.message })
  }
}
```
E assim criamos o objeto `s3`:

```javascript
const s3 = new S3({
  endpoint: 'http://play.minio.io:9000',
  accessKeyId: 'Q3AM3UQ867SPQQA43P2F',
  secretAccessKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
  sslEnabled: false,
  s3ForcePathStyle: true,
});
```

Nesse caso, as credenciais são para o [servidor de testes do Minio](https://play.minio.io).

> Por que não usar a SDK do Minio para JS? Diga-se de passagem, por design, não existe muita diferença, mas aqui estou fazendo isso para mostrar como adaptar, sem basicamente nenhuma mudança na estrutura do projeto, o serviço utilizado. Isso também é útil para utilizar pacotes como o `multer-s3`.

Dessa forma, o código completo seria o seguinte:

```javascript
import express from 'express';
import { S3 } from 'aws-sdk';
import cors from 'cors';
import morgan from 'morgan';

const s3 = new S3({
  endpoint: 'http://play.min.io:9000',
  accessKeyId: 'Q3AM3UQ867SPQQA43P2F',
  secretAccessKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
  sslEnabled: false,
  s3ForcePathStyle: true,
});

const app = express();
app.use(cors());
app.use(morgan('dev'));

const uploadHandler = async (req, res) => {
  try {
    await s3.putObject({
      Bucket: 'tests',
      Key: req.file.originalname,
      Body: req.file.buffer,
    }).promise();
    return res
      .status(201)
      .send({
        message: `File "${req.file.originalname}" uploaded`
      });
  } catch (e) {
    console.log(e);
      return res
        .status(500)
        .send({ error: e.message })
  }
}

app.route('/upload').post(
  multer({ storage: multer.memoryStorage() }).single('file'),
  uploadHandler,
);

app.listen(3000, () => console.log('[READY]')); 
```
# Utilizando a API própria do Minio
Caso você não precise utilizar a API da S3, algumas alterações seriam necessárias. A primeira é a própria definição do objeto "s3". Poderíamos substituir pelo seguinte:

```javascript
const minioClient = new Minio.Client({
    endPoint: 'play.min.io',
    port: 9000,
    useSSL: true,
    accessKey: 'Q3AM3UQ867SPQQA43P2F',
    secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG'
});
```
A segunda seria na utilização desse cliente para realizar o upload, dentro da função uploadHandler, para a seguinte forma:
```javascript
//...
await minioClient.putObject(
  'tests',
  req.file.originalname,
  req.file.buffer,
);
```
 
# Considerações
Caso você execute o código criado, é possível realizar upload de arquivos por meio de requisições http para `localhost:3000/upload` utilizando `multipart/form-data`. O multer faz o trabalho pesado de modo que temos total acesso ao arquivo na memório (atravez do memoryStorage).

Dito isso, utilizamos a SDK da AWS da maneira convencional, contudo o objeto que nos dá acesso à API da S3 é criado com alguns parâmetros importantes - sobretudo o endpoint, que recebe a URL do servidor minio de interesse. Observe que quase nenhuma alteração é necessária, uma vez que o Minio foi estruturado para permitir esse tipo de compatibilidade.

# Referências
- https://min.io/
- https://github.com/minio/minio/issues/2667
- [Como configurar um servidor Minio na Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-set-up-an-object-storage-server-using-minio-on-ubuntu-18-04)