この記事では、Express.jsを使用してAWS SDKを利用し、S3バケットにファイルをアップロードする方法と、署名付きURL(Presigned URL)を生成する方法を紹介する。
全体像
実装の流れ
- パッケージインストール
- 環境変数設定
- S3に接続するためのクラスを作成する
- MediaFileモデルの実装
- MediaFileを操作するためのコントローラーを実装
- ルーティングを定義
実装
パッケージインストール
以下のパッケージをインストールする。
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner multer uuid
- @aws-sdk/client-s3:
Amazon S3 (Simple Storage Service) を操作するためのクライアントライブラリ - @aws-sdk/s3-request-presigner:
Amazon S3のリクエストに対して事前署名URL (Presigned URL) を生成するためのライブラリ - multer:
Node.jsとExpressのためのミドルウェアで、フォームデータ(特にファイルのアップロード)の処理を行う。ファイルのアップロード、アップロードされたファイルの一時保存などをサポートする。
環境変数を設定
バケット名、アクセスキー、シークレットアクセスキー、リージョンを環境変数で登録する。
# AWS S3 設定情報
AWS_S3_BUCKET_NAME=xxxxxxx
AWS_S3_ACCESS_KEY_ID=xxxxxxxxxx
AWS_S3_SECRET_ACCESS_KEY=xxxxxxxxxx
AWS_REGION=ap-northeast-1
S3に接続するためのクラスを作成する
以下のコードでS3クライアントの設定とファイルのアップロード、および署名付きURLを生成するクラスを作成する。
src/config/aws/s3.ts
import {
S3Client,
PutObjectCommand,
GetObjectCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import dotenv from "dotenv";
import { v4 as uuidv4 } from "uuid";
dotenv.config();
// Presigned URLの有効期限: 1時間有効で設定
const SIGNED_URL_EXPIRES_IN = 3600;
class AWSS3Config {
private static instance: AWSS3Config;
private s3Client: S3Client;
private constructor() {
// S3クライアントの初期化
this.s3Client = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_S3_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY!,
},
});
}
public static getInstance(): AWSS3Config {
if (!AWSS3Config.instance) {
AWSS3Config.instance = new AWSS3Config();
}
return AWSS3Config.instance;
}
/**
* S3にファイルをアップロードする
* @param file ファイル
* @param directory ディレクトリパス(例: 'post/{postId}')
* @returns アップロードされたファイルのURL
*/
public async uploadToS3(file: Express.Multer.File, directory: string) {
const params = {
Bucket: process.env.AWS_S3_BUCKET_NAME,
Key: `${directory}/${uuidv4()}-${file.originalname}`, // ディレクトリパスを含むユニークなファイル名を生成
Body: file.buffer,
ContentType: file.mimetype,
};
try {
const command = new PutObjectCommand(params);
await this.s3Client.send(command);
return `https://${process.env.AWS_S3_BUCKET_NAME}.s3.${process.env.AWS_REGION}.amazonaws.com/${params.Key}`;
} catch (error) {
console.error("Error uploading to s3: ", error);
throw error;
}
}
/**
* Presigned URLを生成する関数(GET操作用)
* @param key S3オブジェクトのキー(ファイル名)
* @returns Presigned URL
*/
public async generatePresignedUrlForViewing(key: string): Promise<string> {
const command = new GetObjectCommand({
Bucket: process.env.AWS_S3_BUCKET_NAME,
Key: key,
});
try {
const url = await getSignedUrl(this.s3Client, command, {
expiresIn: SIGNED_URL_EXPIRES_IN,
});
return url;
} catch (error) {
console.error("Error generating presigned URL: ", error);
throw error;
}
}
}
export default AWSS3Config.getInstance();
MediaFileモデルの実装
次に、MediaFileモデルを定義する。このモデルは、アップロードされたファイルのデータを保存する。
src/model/MediaFile.ts
import mongoose, { Document, Schema } from "mongoose";
export interface IMediaFile extends Document {
url: string;
title?: string;
description?: string;
alt_text?: string;
file_type: string; // 画像、動画、PDFなど
uploaded_by: string; // Cognito Sub
created_at: Date;
updated_at: Date;
}
const mediaFileSchema = new Schema(
{
url: { type: String, required: true },
title: { type: String },
description: { type: String },
alt_text: { type: String },
file_type: { type: String, required: true }, // 画像、動画、PDFなど
uploaded_by: { type: String, required: true }, // Cognito Sub
},
{ timestamps: { createdAt: "created_at", updatedAt: "updated_at" } }
);
const MediaFile = mongoose.model<IMediaFile>("MediaFile", mediaFileSchema);
export default MediaFile;
MediaFileを操作するためのコントローラーを実装
次に、ファイルのアップロードや署名付きURLの生成を行うコントローラーを実装する。
src/controllers/MediaFileController.ts
import { NextFunction, Request, Response } from "express";
import MediaFile from "../model/MediaFile";
import AWSS3Config from "../config/aws/s3";
// メディアファイルの一覧取得
export const getFiles = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const results = await MediaFile.find();
return res.status(200).json({ status: "success", data: results });
} catch (error) {
next(error);
}
};
/**
* 画像をS3にアップロードして、その情報をImageモデルに保存する
*/
export const uploadFile = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const file = req.file;
if (!file) {
return res.status(400).json({ message: "No file uploaded" });
}
const directory = req.body.directory || "others";
// アップロードしてURLを受け取る
const s3Url = await AWSS3Config.uploadToS3(file, directory);
// アップロード失敗の場合はエラーにする
if (!s3Url) {
throw new Error("S3 upload failed");
}
const { title, description, alt_text, uploaded_by, file_type } = req.body;
// MediaFileモデルに保存
const newMediaFile = new MediaFile({
url: s3Url,
title,
description,
alt_text,
file_type,
uploaded_by,
});
await newMediaFile.save();
res.status(201).json({ status: "success", data: newMediaFile });
} catch (error) {
next(error);
}
};
/**
* Presigned URLを生成するコントローラー関数
*/
export const generatePresignedUrlForViewing = async (
req: Request,
res: Response,
next: NextFunction
) => {
const { url } = req.body;
// URL未指定の場合は400エラーを返す
if (!url) {
return res
.status(400)
.json({ status: "error", message: "URL is required" });
}
try {
// URLからバケット名とキーを抽出
const bucketName = process.env.AWS_S3_BUCKET_NAME;
const key = url.split(
`${bucketName}.s3.${process.env.AWS_REGION}.amazonaws.com/`
)[1];
// keyが抽出できない場合は400エラーを返す
if (!key) {
return res.status(400).json({ status: "error", message: "Invalid URL" });
}
// Presigned URLを生成
const presignedUrl = await AWSS3Config.generatePresignedUrlForViewing(key);
res.status(200).json({ status: "success", data: presignedUrl });
} catch (error) {
next(error);
}
};
ルーティングを定義
最後に、ルーティングを定義する。ここでは、ファイルのアップロードや署名付きURLの生成を行うエンドポイントを定義。
src/routes/mediaFile.ts
import express from "express";
import {
generatePresignedUrlForViewing,
uploadFile,
getFiles,
} from "../controllers/MediaFileController";
import { checkAuth } from "../middleware/authMiddleware";
import multer from "multer";
const router = express.Router();
const upload = multer();
router.get("/list", getFiles);
router.post("/upload", checkAuth, upload.single("file"), uploadFile);
router.post(
"/generate-presigned-url",
checkAuth,
generatePresignedUrlForViewing
);
export default router;
これで、AWS SDKを使用してS3へのファイルアップロードと署名付きURLの取得ができるようになる。
この記事は役に立ちましたか?
もし参考になりましたら、下記のボタンで教えてください。
コメント