2019.02.17

Node.js(Express.js)アプリで AWS S3 からファイルをダウンロードする実装例


この記事では、Express.js アプリで S3 にホスティングされているファイルをダウンロードする実装例を紹介します。

まず1ファイルで処理の全体像を紹介したあと、もう少し本格的なアプリケーションらしくファイルを分割したバージョンも作成してみます。

処理の全体像

依存パッケージ

まず依存パッケージをインストールします。

$ npm i -S express aws-sdk

メインロジック

server.js
const express = require('express')
const AWS = require('aws-sdk')

const app = express()

// S3 を操作するためのインスタンスを生成
const s3Client = new AWS.S3({
  accessKeyId: `アクセスキー`,
  secretAccessKey: `シークレットキー`,
  region: `リージョン`,
})

/**
 * GET /download?filename=***
 */
app.get('/download', (req, res) => {
  const { filename } = req.query

  // ブラウザにダウンロードダイアログを表示させるための
  // レスポンスヘッダーを付与
  res.attachment(filename)

  const params = {
    Bucket: `バケット名`,
    Key: filename,
  }

  // S3からダウンロードしたファイルの内容を
  // ストリームオブジェクトに変換し、レスポンスに書き込む
  s3Client.getObject(params)
    .createReadStream()
    .on('error', err => {
      res.status(500).send({ error: err })
    })
    .pipe(res)
})

const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`)
})

ポイントは以下の2点でしょう。

  1. res.attachment でブラウザにダウンロードダイアログを表示させるためのレスポンスヘッダー(Content-Disposition および Content-Type)を付与しています。
  2. getObject でダウンロードしたファイルは、createReadStreamストリームオブジェクトに変換し、pipe でレスポンスオブジェクトに書き込みます。こうすることでダウンロードしたファイルをブラウザに届けられるのですね。

リファクタリング

上掲のコードをいくつかのファイルに分割して見通しを改善してみようと思います。

ディレクトリ構成

最終的にこのような構成になります。

├─ controllers
│  └─ download.js
├─ services
│  ├─ download.js
│  └─ s3-client.js
├─ .env
└─ server.js

dotenv

S3 に接続するためのアクセスキーなどは設定ファイルに切り出して管理します。

今回は設定ファイルの内容を環境変数として読み出せる dotenv ライブラリを使用します。

$ npm install -S dotenv

.env という名前で設定ファイルを作成します。

.env
AWS_ACCESS_KEY=アクセスキー
AWS_SECRET_ACCESS_KEY=シークレットキー
AWS_S3_REGION=リージョン
AWS_S3_BUCKET=バケット名

dotenv は、動作環境によって異なる設定値を管理できます(たとえばテスト環境と本番環境の S3 バケットが異なるなど)。

また .env はたいてい Git の管理から外します。パブリックなリポジトリにコードをアップロードしたときに接続パスワードなどのセンシティブな設定情報を晒さないためです。

Service

メインロジックのうち、HTTP メッセージの受送信、つまりリクエストやレスポンスとは関係のない部分をサービスとして切り出します。

S3クライアント

S3 クライアントを生成するコードです。

services/s3-client.js
const AWS = require('aws-sdk')

const client = new AWS.S3({
  accessKeyId: process.env.AWS_ACCESS_KEY,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: process.env.AWS_S3_REGION
})

module.exports = client

ダウンロードロジック

実際にファイルをダウンロードする関数を定義します。

services/download.js
const s3Client = require('./s3-client')

/**
 * ファイルダウンロード
 * @param {string} filename
 * @returns {fs.ReadStream}
 */
function download(filename) {
  const params = {
    Bucket: process.env.AWS_S3_BUCKET,
    Key: filename,
  }

  return s3Client.getObject(params).createReadStream()
}

module.exports = download

ストリームオブジェクトを生成するところまでをサービスの役割にしてみました。
このストリームオブジェクトを使ってレスポンスを作り出すのは、次に登場するコントローラーの役目です。

Contoller

HTTP メッセージの受送信を行うロジックをコントローラーに分類します。

controllers/download.js
const downloadService = require('../services/download')

/**
 * ダウンロードコントローラーメソッド
 */
function download(req, res) {
  const { filename } = req.query

  res.attachment(filename)

  downloadService(filename)
    .on('error', err => {
      res.status(500).send({ error: err })
    })
    .pipe(res)
}

module.exports = download

Server

最終的に、server.js は以下のようになります。

server.js
const express = require('express')
const download = require('./controllers/download')

require('dotenv').config()

const app = express()

/**
 * GET /download?filename=***
 */
app.get('/download', download)

const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`)
})

以上、Express.js アプリで S3 にホスティングされているファイルをダウンロードする実装例を紹介しました。