はじめに
この記事では、IBM Cloud Object Storageを活用したファイルのアップロード、ダウンロード、削除機能を備えたWebアプリの開発方法について、デモアプリを通して紹介します。
IBM Cloud Object Storageは、高い可用性と耐久性を持ち、画像やドキュメントなどの非構造化データを効率的に管理できるクラウド・ストレージ・サービスで、複雑なインフラ構築をせずに手軽に利用できます。
この記事を通じて、以下の技術に関する知識を得ることができます。
- IBM Cloud Object Storageのリソース作成と接続方法
- Webアプリにおけるファイルのアップロード、ダウンロード、削除の実装方法
デモアプリの概要
デモアプリのソースコードはGitHubにて公開しています。
このデモアプリには、次の機能があります。
- IBM Cloud Object Storageへのファイルアップロード
- ファイルの一覧表示
- ファイルのダウンロード
- ファイルの削除
以下は、アプリの動作を示すデモGIF画像です。
全体構成図
全体構成図は以下の通りです。
Object Storageへのアクセスキーなどの機密情報はWebサーバー側で管理されています。
ユーザーは、直接ブラウザーからObject Storageにアクセスするのではなく、Webサーバーにリクエストを送り、WebサーバーがIBM COS SDKを使ってObject Storage内のファイル情報を取得、操作を実行し、その結果をレスポンスします。
デモアプリの実行
このセクションでは、お使いのPCでデモアプリの環境準備から実行までの手順を説明します。
環境準備
前提条件
デモアプリを実行するために、以下の準備が必要です。この記事では、これらの前提条件が整っていることを前提としています。
- IBM Cloudのアカウント
- Git
- Node.js(v20.6.0以降)
- v20.6.0未満の場合、以下の記事を参考に、Node.jsのアップデートまたは
dotenv
関連コードの修正が必要です - 筆者の環境: v20.16.0
- v20.6.0未満の場合、以下の記事を参考に、Node.jsのアップデートまたは
ソースコードの取得
以下のコマンドを実行して、リポジトリをローカルにクローンします。
git clone https://github.com/mountln/icos-demo
cd icos-demo/
ソースコードの主なファイルは以下の通りです。
ファイル | 中身 |
---|---|
.env |
接続情報などの環境変数を定義するファイル |
public/index.html |
フロントエンドのコード |
app.js |
バックエンドのコード |
package.json |
プロジェクトで使用するパッケージやスクリプトなどを定義するファイル |
パッケージのインストール
デモアプリで使用する必要なNode.jsパッケージは、package.json
に記載されています。プロジェクトのルートディレクトリ(package.json
が存在するディレクトリ)で、以下のコマンドを実行します。
npm install
このコマンドを実行すると、package.json
に記載されているパッケージや、それらのパッケージが依存する他のパッケージが自動的にインストールされます。
npmやpackage.jsonについて詳しく知りたい方は、こちらの記事を参考にしてください。
IBM Cloud Object Storageでのリソース作成と接続設定
インスタンスの作成
IBM Cloud Object Storageのページにアクセスし、「Create an instance」(または「インスタンスの作成」)ボタンをクリックします。
必要事項を入力し、インスタンスを作成します。
無料アカウントで作成できるインスタンスの数は1つで、すでに作成している場合は、既存のインスタンスを利用しても構いません。
バケットの作成
インスタンスが作成されたら、その詳細ページに移動し、「バケットの作成」ボタンをクリックします。
画面の指示に従い、バケット名を設定してバケットを作成します。無料枠として5GBが提供されています。
作成されたバケットのページで、バケット名とエンドポイントのパブリックURLをコピーし、ソースコードの.env
ファイル内で<bucket-name>
および<endpoint>
を対応する値に書き換えます。
サービス資格情報の作成
次に、サービス資格情報を作成します。インスタンスの詳細ページで「サービス資格情報」タブを開き、「新規資格情報」ボタンをクリックします。
HMAC資格情報を含めた設定で新規資格情報を作成し、access_key_id
とsecret_access_key
をコピーして、.env
ファイル内の<hmac-access-key-id>
および<hmac-secret-access-key>
をそれぞれ書き換えます。
デモアプリの実行
以下のコマンドでWebアプリのサーバーを起動します。
npm start
Listening on port 3000
と表示されたら、ブラウザーでhttp://localhost:3000
にアクセスし、ファイルのアップロードなどを行えます。
実装詳細・ソースコードの説明
このセクションでは、デモアプリの実装について解説します。
ファイルアップロードの実装
フロントエンド (public/index.html
)
ファイルのアップロードフォームは以下の通りです。POST /
リクエストを通じて、ファイルをアップロードします。(参考: multer - Usage)
<form action="/" method="post" enctype="multipart/form-data">
<input
type="file"
class="form-control mt-4 mb-3"
id="input-file"
name="file"
/>
<button class="btn btn-primary px-4" type="submit">アップロード</button>
</form>
バックエンド (app.js
)
バックエンドでは、ibm-cos-sdk
を使用してS3クライアントを作成します。(参考: S3 API Reference)
const cos = require("ibm-cos-sdk");
const s3 = new cos.S3({ endpoint: process.env.COS_ENDPOINT });
次に、multipart/form-data
を処理するためのミドルウェアmulter
を使って、アップロードされたファイルを処理するミドルウェアを定義します。multer-s3
を使うことで、アップロードされたファイルを直接IBM Cloud Object Storageに保存されます。
const multer = require("multer");
const multerS3 = require("multer-s3");
const bucketName = process.env.COS_BUCKET_NAME;
const upload = multer({
storage: multerS3({
s3: s3,
bucket: bucketName,
acl: "public-read",
metadata: (_req, file, callback) => {
callback(null, { fieldName: file.fieldname });
},
key: (_req, file, callback) => {
// ファイル名をUTF-8に変換(日本語文字化けの対応)
const key = Buffer.from(file.originalname, "latin1").toString("utf8");
callback(null, key);
},
}),
});
POST /
のルーティングを次のように定義します。
app.post("/", upload.single("file"), (req, res) => {
if (res.statusCode === 200) {
console.log("Uploaded file successfully");
console.log(req.file);
} else {
console.log("Upload failed");
}
res.redirect("/");
});
ファイルのリスト・ダウンロード・削除
フロントエンド (public/index.html
)
ファイルリストは、ページのロード時に取得され、次の<ul>
要素内にリストとして表示されます。
<div class="my-4 p-4 pb-3 rounded border bg-body-tertiary shadow-sm">
<h3>ファイルリスト</h3>
<ul id="file-list" class="list-group mt-4 mb-2"></ul>
</div>
JavaScriptのfetch
APIを使用して、GET /api/file
リクエストをサーバーに送信し、バケット内に保存されているファイルのリストを取得します。
取得したファイルを最新順にソートし、<ul>
要素内に追加します。
const renderFileList = async () => {
const fileList = document.getElementById("file-list");
const res = await fetch("/api/file");
if (res.ok) {
const contents = await res.json();
if (contents.length > 0) {
// 最新順にソートする
contents.sort((a, b) => {
return new Date(a.LastModified) < new Date(b.LastModified);
});
for (const content of contents) {
// リスト要素
const itemElement = document.createElement("li");
itemElement.classList.add(
"list-group-item",
"d-flex",
"align-items-center",
"gap-1"
);
// ファイル名
const filenameNode = document.createElement("span");
filenameNode.innerText = content.Key;
itemElement.appendChild(filenameNode);
// サイズ
const sizeNode = document.createElement("span");
sizeNode.classList.add("ms-auto", "me-3", "text-secondary");
sizeNode.innerText = `${content.Size}バイト`;
itemElement.appendChild(sizeNode);
// ダウンロードボタン
const downloadButton = document.createElement("a");
downloadButton.classList.add("btn", "btn-primary");
downloadButton.href = content.Url;
downloadButton.innerText = "ダウンロード";
itemElement.appendChild(downloadButton);
// 削除ボタン
const deleteButton = document.createElement("button");
deleteButton.classList.add("btn", "btn-danger");
deleteButton.onclick = async () => {
const res = await fetch(`/api/file/${content.Key}`, {
method: "delete",
});
if (res.status === 200) {
// ファイルを削除した
location.reload();
} else {
console.error(`Cannot delete ${content.Key}: ${res.status}`);
}
};
deleteButton.innerText = "削除";
itemElement.appendChild(deleteButton);
fileList.appendChild(itemElement);
}
}
}
};
削除ボタンをクリックすると、DELETE /api/file/<ファイル名>
リクエストがサーバーに送信されます。
バックエンド (app.js
)
バックエンド側では、まず、ファイルのリストを取得するためのGET /api/file
リクエストにレスポンスする必要があります。
ルーティングの定義は次の通りです。
app.get("/api/file", async (_req, res) => {
try {
const data = await s3.listObjects({ Bucket: bucketName }).promise();
const contents = [];
if (data) {
for (const content of data.Contents) {
// オブジェクトをダウンロードするためのリンクを取得する
const contentUrl = await s3.getSignedUrlPromise("getObject", {
Bucket: bucketName,
Key: content.Key,
});
contents.push({ ...content, Url: contentUrl });
}
}
res.json(contents);
} catch (error) {
console.error(`ERROR: ${error.code} - ${error.message}`);
}
});
s3 のlistObjects
APIを使用して、バケット内にあるオブジェクトのリストを取得します。さらに、インターネットから資格情報を必要とせずにダウンロードするための事前署名URLをgetSignedUrlPromise
APIで取得します。このURLをコンテンツ情報追加し、レスポンスとして返します。
リンクの有効期限を指定したい場合は、次のように指定できます。
const contentUrl = await s3.getSignedUrlPromise("getObject", {
Bucket: bucketName,
Key: content.Key,
Expires: 60 * 10, // リンクの有効期限を10分に指定
});
続いて、ファイルを削除ためのDELETE /api/file/<ファイル名>
ルーティングを定義します。
ファイルの削除には、s3のdeleteObject
APIを使用します。
app.delete("/api/file/:filename", async (req, res) => {
try {
await s3
.deleteObject({ Bucket: bucketName, Key: req.params.filename })
.promise();
res.end();
} catch (error) {
console.error(`ERROR: ${error.code} - ${error.message}`);
res.status(500).end;
}
});
まとめ
この記事では、デモアプリを通じて、IBM Cloud Object Storageを利用したファイルのアップロード、ダウンロード、削除機能を持つWebアプリケーションの実装方法を解説しました。
さらに詳しい情報を得たい方は、以下のリンクも参考にしてください。