Escolhendo entre a plataforma NestJS + Fastify e NestJS + Express, optei por NestJS + Fastify. Conhecendo a inclinação dos desenvolvedores em qualquer situação incompreensível para pendurar propriedades adicionais no objeto req no Express e assim se comunicar entre as diferentes partes do aplicativo, decidi firmemente que o Express não estará no próximo projeto.
Eu só precisava resolver um problema técnico com Content-Type: multipart / form-data. Além disso, planejei salvar os arquivos recebidos por meio de solicitações de Content-Type: multipart / form-data no armazenamento S3. A este respeito, a implementação de solicitações de Content-Type: multipart / form-data na plataforma NestJS + Express me confundiu por não funcionar com streams.
Iniciando o S3 Local Storage
S3 é um armazenamento de dados (pode-se dizer, embora não estritamente falando, um armazenamento de arquivos) acessível através do protocolo http. S3 foi originalmente fornecido pela AWS. A API S3 é atualmente suportada por outros serviços em nuvem também. Mas não só. Existem implementações de servidor S3 que você pode trazer localmente para usar durante o desenvolvimento e, possivelmente, colocar seus servidores S3 em produção.
Primeiro, você precisa decidir sobre a motivação para usar o armazenamento de dados S3. Em alguns casos, isso pode reduzir custos. Por exemplo, você pode usar o armazenamento S3 mais lento e barato para armazenar backups. Os armazenamentos rápidos com alto tráfego (o tráfego é cobrado separadamente) para carregar dados do armazenamento provavelmente custarão comparáveis aos drives SSD do mesmo tamanho.
Um motivo mais significativo é 1) escalabilidade - você não precisa pensar no fato de que o espaço em disco pode acabar e 2) confiabilidade - os servidores funcionam em um cluster e você não precisa pensar em backup, já que o número necessário de cópias está sempre disponível.
Para aumentar a implementação de servidores S3 - minio - localmente, você só precisa do docker e do docker-compose instalados no computador. Arquivo docker-compose.yml correspondente:
version: '3'
services:
minio1:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data1-1:/data1
- ./s3/data1-2:/data2
ports:
- '9001:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
minio2:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data2-1:/data1
- ./s3/data2-2:/data2
ports:
- '9002:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
minio3:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data3-1:/data1
- ./s3/data3-2:/data2
ports:
- '9003:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
minio4:
image: minio/minio:RELEASE.2020-08-08T04-50-06Z
volumes:
- ./s3/data4-1:/data1
- ./s3/data4-2:/data2
ports:
- '9004:9000'
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
command: server http://minio{1...4}/data{1...2}
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
interval: 30s
timeout: 20s
retries: 3
Começamos - e sem problemas obtemos um cluster de 4 servidores S3.
NestJS + Fastify + S3
Descreverei como trabalhar com o servidor NestJS desde os primeiros passos, embora parte deste material esteja perfeitamente descrito na documentação. Instala CLI NestJS:
npm install -g @nestjs/cli
Um novo projeto NestJS é criado:
nest new s3-nestjs-tut
Os pacotes necessários são instalados (incluindo aqueles necessários para trabalhar com S3):
npm install --save @nestjs/platform-fastify fastify-multipart aws-sdk sharp
npm install --save-dev @types/fastify-multipart @types/aws-sdk @types/sharp
Por padrão, o projeto instala a plataforma NestJS + Express. Como instalar o Fastify está descrito na documentação docs.nestjs.com/techniques/performance . Além disso, precisamos instalar um plugin para lidar com o Content-Type: multipart / form-data - fastify-multipart
import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import fastifyMultipart from 'fastify-multipart';
import { AppModule } from './app.module';
async function bootstrap() {
const fastifyAdapter = new FastifyAdapter();
fastifyAdapter.register(fastifyMultipart, {
limits: {
fieldNameSize: 1024, // Max field name size in bytes
fieldSize: 128 * 1024 * 1024 * 1024, // Max field value size in bytes
fields: 10, // Max number of non-file fields
fileSize: 128 * 1024 * 1024 * 1024, // For multipart forms, the max file size
files: 2, // Max number of file fields
headerPairs: 2000, // Max number of header key=>value pairs
},
});
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
fastifyAdapter,
);
await app.listen(3000, '127.0.0.1');
}
bootstrap();
Agora vamos descrever o serviço que faz upload de arquivos para o repositório S3, tendo reduzido o código para tratamento de alguns tipos de erros (o texto completo está no repositório do artigo):
import { Injectable, HttpException, BadRequestException } from '@nestjs/common';
import { S3 } from 'aws-sdk';
import fastify = require('fastify');
import { AppResponseDto } from './dto/app.response.dto';
import * as sharp from 'sharp';
@Injectable()
export class AppService {
async uploadFile(req: fastify.FastifyRequest): Promise<any> {
const promises = [];
return new Promise((resolve, reject) => {
const mp = req.multipart(handler, onEnd);
function onEnd(err) {
if (err) {
reject(new HttpException(err, 500));
} else {
Promise.all(promises).then(
data => {
resolve({ result: 'OK' });
},
err => {
reject(new HttpException(err, 500));
},
);
}
}
function handler(field, file, filename, encoding, mimetype: string) {
if (mimetype && mimetype.match(/^image\/(.*)/)) {
const imageType = mimetype.match(/^image\/(.*)/)[1];
const s3Stream = new S3({
accessKeyId: 'minio',
secretAccessKey: 'minio123',
endpoint: 'http://127.0.0.1:9001',
s3ForcePathStyle: true, // needed with minio?
signatureVersion: 'v4',
});
const promise = s3Stream
.upload(
{
Bucket: 'test',
Key: `200x200_${filename}`,
Body: file.pipe(
sharp()
.resize(200, 200)
[imageType](),
),
}
)
.promise();
promises.push(promise);
}
const s3Stream = new S3({
accessKeyId: 'minio',
secretAccessKey: 'minio123',
endpoint: 'http://127.0.0.1:9001',
s3ForcePathStyle: true, // needed with minio?
signatureVersion: 'v4',
});
const promise = s3Stream
.upload({ Bucket: 'test', Key: filename, Body: file })
.promise();
promises.push(promise);
}
});
}
}
Dos recursos, deve-se notar que escrevemos um fluxo de entrada em dois fluxos de saída se uma imagem for carregada. Um dos streams comprime a imagem para um tamanho de 200x200. Em todos os casos, o estilo de trabalho com fluxos é usado. Porém, para detectar possíveis erros e devolvê-los ao controlador, chamamos o método Promessa (), que é definido na biblioteca aws-sdk. Acumulamos as promessas recebidas na matriz de promessas:
const promise = s3Stream
.upload({ Bucket: 'test', Key: filename, Body: file })
.promise();
promises.push(promise);
E, além disso, esperamos sua resolução no método
Promise.all(promises).
O código do controlador, no qual ainda tive que encaminhar FastifyRequest para o serviço:
import { Controller, Post, Req } from '@nestjs/common';
import { AppService } from './app.service';
import { FastifyRequest } from 'fastify';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('/upload')
async uploadFile(@Req() req: FastifyRequest): Promise<any> {
const result = await this.appService.uploadFile(req);
return result;
}
}
O projeto é lançado:
npm run start:dev
Repositório de artigos github.com/apapacy/s3-nestjs-tut
apapacy@gmail.com
13 de agosto de 2020