1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IBM Cloud Object Storageを使ったWebアプリ開発

Last updated at Posted at 2024-10-25

はじめに

この記事では、IBM Cloud Object Storageを活用したファイルのアップロード、ダウンロード、削除機能を備えたWebアプリの開発方法について、デモアプリを通して紹介します。

IBM Cloud Object Storageは、高い可用性と耐久性を持ち、画像やドキュメントなどの非構造化データを効率的に管理できるクラウド・ストレージ・サービスで、複雑なインフラ構築をせずに手軽に利用できます。

この記事を通じて、以下の技術に関する知識を得ることができます。

  • IBM Cloud Object Storageのリソース作成と接続方法
  • Webアプリにおけるファイルのアップロード、ダウンロード、削除の実装方法

デモアプリの概要

デモアプリのソースコードはGitHubにて公開しています。

このデモアプリには、次の機能があります。

  • IBM Cloud Object Storageへのファイルアップロード
  • ファイルの一覧表示
  • ファイルのダウンロード
  • ファイルの削除

以下は、アプリの動作を示すデモGIF画像です。

icos-demo.gif

全体構成図

全体構成図は以下の通りです。

全体構成図
Object Storageへのアクセスキーなどの機密情報はWebサーバー側で管理されています。
ユーザーは、直接ブラウザーからObject Storageにアクセスするのではなく、Webサーバーにリクエストを送り、WebサーバーがIBM COS SDKを使ってObject Storage内のファイル情報を取得、操作を実行し、その結果をレスポンスします。

デモアプリの実行

このセクションでは、お使いのPCでデモアプリの環境準備から実行までの手順を説明します。

環境準備

前提条件

デモアプリを実行するために、以下の準備が必要です。この記事では、これらの前提条件が整っていることを前提としています。

  1. IBM Cloudのアカウント
  2. Git
  3. Node.js(v20.6.0以降)

ソースコードの取得

以下のコマンドを実行して、リポジトリをローカルにクローンします。

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」(または「インスタンスの作成」)ボタンをクリックします。

create-instance.jpg

必要事項を入力し、インスタンスを作成します。

create-instance-detail.jpg

無料アカウントで作成できるインスタンスの数は1つで、すでに作成している場合は、既存のインスタンスを利用しても構いません。

バケットの作成

インスタンスが作成されたら、その詳細ページに移動し、「バケットの作成」ボタンをクリックします。

create-bucket-button.jpg

画面の指示に従い、バケット名を設定してバケットを作成します。無料枠として5GBが提供されています。

create-bucket.jpg

create-bucket-detail.jpg

作成されたバケットのページで、バケット名とエンドポイントのパブリックURLをコピーし、ソースコードの.envファイル内で<bucket-name>および<endpoint>を対応する値に書き換えます。

endpoint.jpg

サービス資格情報の作成

次に、サービス資格情報を作成します。インスタンスの詳細ページで「サービス資格情報」タブを開き、「新規資格情報」ボタンをクリックします。

create-api-key.jpg

HMAC資格情報を含めた設定で新規資格情報を作成し、access_key_idsecret_access_keyをコピーして、.envファイル内の<hmac-access-key-id>および<hmac-secret-access-key>をそれぞれ書き換えます。

create-api-key-detail.jpg

api-key-detail.jpg

デモアプリの実行

以下のコマンドでWebアプリのサーバーを起動します。

npm start

npm-start.jpg

Listening on port 3000と表示されたら、ブラウザーでhttp://localhost:3000にアクセスし、ファイルのアップロードなどを行えます。

demo-ui.jpg

実装詳細・ソースコードの説明

このセクションでは、デモアプリの実装について解説します。

ファイルアップロードの実装

フロントエンド (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のfetchAPIを使用して、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 のlistObjectsAPIを使用して、バケット内にあるオブジェクトのリストを取得します。さらに、インターネットから資格情報を必要とせずにダウンロードするための事前署名URLをgetSignedUrlPromiseAPIで取得します。このURLをコンテンツ情報追加し、レスポンスとして返します。

リンクの有効期限を指定したい場合は、次のように指定できます。

const contentUrl = await s3.getSignedUrlPromise("getObject", {
  Bucket: bucketName,
  Key: content.Key,
  Expires: 60 * 10, // リンクの有効期限を10分に指定
});

続いて、ファイルを削除ためのDELETE /api/file/<ファイル名>ルーティングを定義します。
ファイルの削除には、s3のdeleteObjectAPIを使用します。

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アプリケーションの実装方法を解説しました。

さらに詳しい情報を得たい方は、以下のリンクも参考にしてください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?