ikmnjrd.github.io

AWS S3互換のMinIOにaws-sdk(client-s3)から接続する

Posted on 2022-12-08
目次

結論

s3-clientでMinIOに繋げる!

状況

ローカルで完結する開発環境でもS3にできるだけ近い形をとりたかった。
MinIOというS3互換のオブジェクトストレージが無料らしいので使ってみる。

MinIO導入

以下のようなディレクトリ構造とする。

.
├── docker
│   └── minio
│       └── data
├── docker-compose.yml
version: '3.8'
services:
  minio:
    image: minio/minio:RELEASE.2022-11-29T23-40-49Z
    ports:
      - 9000:9000
      - 9090:9090
    environment:
      - MINIO_ROOT_USER=minio
      - MINIO_ROOT_PASSWORD=miniopass
      - MINIO_VOLUMES=/data
    entrypoint: sh
    command: -c "
      minio server --console-address ':9090'"
    volumes:
      - ./docker/minio/data:/data

コンテナを起動するとlocalhost:9090でminioの管理画面が表示される。

aws-sdkでMinIOに接続する

s3.js

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'

const IS_DEV = process.env.IS_DEV
const AWS_S3_REGION = process.env.AWS_S3_REGION
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY
const AWS_S3_BUCKET_NAME = process.env.AWS_S3_BUCKET_NAME


const s3client = new S3Client(
  IS_DEV
    ? {
        region: AWS_S3_REGION,
        credentials: {
          accessKeyId: 'minio', // MINIO_ROOT_USER
          secretAccessKey: 'miniopass' // MINIO_ROOT_PASSWORD
        },
        endpoint: 'http://localhost:9000/',
        forcePathStyle: true
      }
    : {
        region: AWS_S3_REGION,
        credentials: {
          accessKeyId: AWS_ACCESS_KEY_ID,
          secretAccessKey: AWS_SECRET_ACCESS_KEY
        }
      }
)
export const uploadToS3 = async (body) => {
  try {
    const data = await s3client.send(
      new PutObjectCommand({
        Bucket: AWS_S3_BUCKET_NAME,
        Key: 'test/key.png',
        Body: body,
      })
    )
    console.debug('Success', data)
    return
  } catch (err) {
    console.error('Error', err)
    throw err
  }
}

index.js

import express, { Router } from 'express'
import { uploadToS3 } from './s3'

const app = express()
const router = Router()

router.post('/image/upload', async (req, res) => {
    // 最小のpng
    const reqBodyMock = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQIHWP4DwABAQEANl9ngAAAAABJRU5ErkJggg=='

    await uploadToS3(reqBodyMock)

    return res.status(201)
  }
)

app.use(router)
app.listen('3000')

multerS3でクライアントのフォームから送信された画像を保存する

multer.js

import multer from 'multer'
import multerS3 from 'multer-s3'
import { s3client } from './s3'

export const multerUpload = multer({
  storage: multerS3({
    s3: s3client,
    bucket: AWS_S3_BUCKET_NAME,
    metadata: (_req, file, cb) => {
      cb(null, { fieldName: file.fieldname })
    },
    key: (_req, file, cb) => {
      cb(null, file.originalname)
    }
  })
})

index.js

import express, { Router } from 'express'
import { uploadToS3 } from './s3'
import { multerUpload } from './multer'

const app = express()
const router = Router()

router.post(
  '/image/upload',
  multerUpload.single(),
  async (req, res) => {
    const file = req.file // 色々都合のいいファイル(本例だとimage/pngだったり的な)

    // s3multerでポート番号が削られてしまうので無理やりつける。
    const fileLocation = IS_DEV
      ? file.location.replace(
          /^http:\/\/localhost/,
          'http://localhost:9000'
        )
      : file.location

    return res.status(200).json({location: fileLocation})
  }
)

app.use(router)
app.listen('3000')

参考文献

関連用語

  • busboy
  • multer
  • multerS3