NestJS。S3ストレージへのファイルのアップロード(ミニオ)

NestJSは、Node.jsプラットフォーム上で効率的でスケーラブルなサーバー側アプリケーションを構築するためのフレームワークです。 NestJSはプラットフォームに依存しないフレームワークであるという主張に出くわすかもしれません。これは、選択した2つのフレームワーク(NestJS + ExpressまたはNestJS + Fastify)のいずれかに基づいて機能できることを意味します。これは本当にそうです、またはほとんどそうです。このプラットフォームの独立性は、Content-Type:multipart / form-dataリクエストの処理で終了します。つまり、実質的には開発の2日目です。また、NestJS + Expressプラットフォームを使用している場合、これは大きな問題ではありません。ドキュメントには、Content-Type:multipart / form-dataがどのように機能するかの例があります。 NestJS + Fastifyのそのような例はなく、ネット上にはそれほど多くの例はありません。そして、これらの例のいくつかは非常に複雑な道をたどります。



NestJS + FastifyプラットフォームとNestJS + Expressプラットフォームのどちらかを選択して、NestJS + Fastifyを選択しました。Expressのreqオブジェクトに追加のプロパティをぶら下げて、アプリケーションのさまざまな部分の間で通信するという、理解できない状況での開発者の傾向を知っていたので、Expressは次のプロジェクトには含まれないと固く決心しました。



Content-Type:multipart / form-dataの技術的な問題を解決するだけで済みました。また、Content-Type:multipart / form-dataリクエストで受信したファイルをS3ストレージに保存する予定でした。この点で、NestJS + ExpressプラットフォームでのContent-Type:multipart / form-dataリクエストの実装は、ストリームでは機能しないことを私に混乱させました。



S3ローカルストレージの起動



S3は、httpプロトコルを介してアクセスできるデータストア(厳密に言えば、ファイルストアと言うかもしれません)です。 S3は元々AWSによって提供されていました。 S3 APIは現在、他のクラウドサービスでもサポートされています。しかし、それだけではありません。開発中に使用するためにローカルで起動できるS3サーバーの実装があり、場合によってはS3サーバーを本番環境に移行することもできます。



まず、S3データストレージを使用する動機を決定する必要があります。場合によっては、これによりコストを削減できます。たとえば、バックアップを保存するために、最も低速で最も安価なS3ストレージを使用できます。ストレージからデータをロードするためのトラフィックの多い高速ストレージ(トラフィックは別途課金されます)は、おそらく同じサイズのSSDドライブに匹敵するコストになります。



より強力な動機は、1)スケーラビリティ(ディスクスペースが不足する可能性があるという事実を考慮する必要がない)、および2)信頼性(サーバーはクラスター内で動作し、必要な数のコピーが常に利用可能であるため、バックアップについて考える必要がない)です。



S3サーバー(minio)の実装をローカルで上げるには、コンピューターにdockerとdocker-composeをインストールするだけで済みます。対応するdocker-compose.ymlファイル:



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


開始します-そして問題なく、4つのS3サーバーのクラスターを取得します。



NestJS + Fastify + S3



NestJSサーバーの操作については、最初のステップから説明しますが、この資料の一部はドキュメントで完全に説明されています。CLINestJSをインストールします。



npm install -g @nestjs/cli


新しいNestJSプロジェクトが作成されます。



nest new s3-nestjs-tut


必要なパッケージがインストールされます(S3での作業に必要なパッケージを含む)。




npm install --save @nestjs/platform-fastify fastify-multipart aws-sdk sharp
npm install --save-dev @types/fastify-multipart  @types/aws-sdk @types/sharp


デフォルトでは、プロジェクトはNestJS + Expressプラットフォームをインストールします。Fastifyのインストール方法については、docs.nestjs.com / techniques / performanceのドキュメントで説明されてますさらに、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();


ここで、ファイルをS3リポジトリにアップロードするサービスについて説明し、いくつかのタイプのエラーを処理するためのコードを削減しました(全文は記事リポジトリにあります)。



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);
      }
    });
  }
}


機能のうち、画像がロードされる場合、入力ストリームを2つの出力ストリームに書き込むことに注意してください。ストリームの1つは、画像を200x200のサイズに圧縮します。いずれの場合も、ストリームを使用した作業スタイルが使用されます。ただし、発生する可能性のあるエラーをキャッチしてコントローラーに返すために、aws-sdkライブラリで定義されているpromise()メソッドを呼び出します。受信したpromiseをpromise配列に蓄積します。



        const promise = s3Stream
          .upload({ Bucket: 'test', Key: filename, Body: file })
          .promise();
        promises.push(promise);


そして、さらに、メソッドでの解決を期待していPromise.all(promises)ます。



FastifyRequestをサービスに転送する必要があったコントローラーコード:



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;
  }
}


プロジェクトが開始されます:



npm run start:dev


記事 リポジトリgithub.com/apapacy/s3-nestjs-tutapapacy@gmail.com2020年 8月13日








All Articles