フロントから直接S3にファイルをアップロードする

1 min read読了の目安(約1500字

こんにちは、ハトです。いままでサーバーを通じでS3にアップロードさせてたのですが、直接アップロードさせたほうが、作りやすいことに気づいて移行しました。以下知見の共有です。

流れ

シーケンス図

STEP:

  1. ブラウザからファイルをアップロード。ファイルのtypeとnameをバックエンドに送信。
  2. バックエンドはS3のsdkをもとに署名付きURLをS3サービスにリクエストする。このときBucket名やファイル名(Key)とファイルタイプをパラメータとして送る
  3. バックエンドはsdkから署名付きURLを受け取り、フロントエンドにわたす
  4. フロントエンドはもらったURLに直接ファイルをPUTする。

フロントエンドのコード

ファイルを何らかの方法で取得した前提です(inputタグからとか)。

    // step1: 署名付きurlを取得
    const resSignedUrl = await axios.get(`/upload`,
      {      
        fileName: file.name,
        fileType: file.type
      }
    );
    
    // step4: 
    const res = await axios.put(
      resSignedUrl.data.url,
      file,
      {
        headers: {
          'Content-Type': file.type,
        },
      }
    );

バックエンドのコード

nodejs。予めS3のバケットを公開設定にしておいてください。
またフロントからアップロードする際にはS3のバケットにCORS設定が必要になるかもしれません。
EC2上で動かす場合、IAMロールなどでS3に署名付きURLを要求できる権限が必要です。

 showPresignedUrl: (req, res, next) => {
    // NOTE: signatureVersion: 'v4'にしないと403forbiddenエラーになる。
    const s3 = new AWS.S3({ signatureVersion: 'v4' });
    const params = {
      Bucket: process.env.S3_BUCKET_NAME, // 環境変数に公開設定されたS3のバケット名を指定しておく。
      Key: req.query.fileName,
      Expires: 60,
      ContentType: req.query.fileType,
    };

    // step3: SDKを通じて署名付きurlを取得する。
    s3.getSignedUrl('putObject', params, (err, url) => {
      if (err) {
        next(err)
	return;
      }
      // step4: フロントに返す。
      res.json({
        url,
      })
      return;
    });
  }