NestJS Azure Blob Storageにファイルをアップロードする
@azure/storage-blob を使いBlobServiceClientでコンテナへ接続し、NestJSからファイルをアップロードする基本パターン
NestJS基礎まで終えている前提。
この記事では、Azure Blob Storageに対してファイルをアップロードする最小パターンを実装する。BlobServiceClient でストレージアカウントに接続し、getContainerClient → getBlockBlobClient の流れでBlobに書き込む。
この記事でやること
@azure/storage-blobを導入するAzureBlobServiceでBlobServiceClient/getContainerClient/getBlockBlobClientを使ったアップロードロジックを実装するPOST /files/uploadでmultipartファイルを受け取り、Blobに保存してURLを返すcurlでファイルのアップロードを確認する
触るファイル一覧
api-sample/
└─ src/
├─ app.module.ts # AzureBlobModule / FilesModuleを追加
├─ azure-blob/
│ ├─ azure-blob.module.ts # 新規作成
│ └─ azure-blob.service.ts # 新規作成(アップロードロジック)
└─ files/
├─ files.module.ts # 新規作成
└─ files.controller.ts # 新規作成(POST /files/upload)0. パッケージを追加する
pnpmの場合。
pnpm add @azure/storage-blob
pnpm add -D @types/multernpmの場合。
npm install @azure/storage-blob
npm install --save-dev @types/multer@azure/storage-blob はAzure公式のNode.js SDK。@types/multer はNestJSのファイルアップロードで使うmulterの型定義。
1. 環境変数を追加する
.env に接続文字列とコンテナ名を追加する。接続文字列はAzureポータルの「ストレージアカウント → アクセスキー」から取得できる。
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=<account>;AccountKey=<key>;EndpointSuffix=core.windows.net
AZURE_STORAGE_CONTAINER_NAME=uploads2. AzureBlobService を作る
2-1. azure-blob.service.ts を作る
新規作成するファイルは api-sample/src/azure-blob/azure-blob.service.ts。
import { Injectable } from "@nestjs/common";
import { BlobServiceClient, BlockBlobClient } from "@azure/storage-blob";
@Injectable()
export class AzureBlobService {
private readonly blobServiceClient: BlobServiceClient;
private readonly containerName: string;
constructor() {
const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
if (!connectionString) {
throw new Error("AZURE_STORAGE_CONNECTION_STRING is not set");
}
this.containerName =
process.env.AZURE_STORAGE_CONTAINER_NAME ?? "uploads";
this.blobServiceClient =
BlobServiceClient.fromConnectionString(connectionString);
}
async upload(
fileName: string,
buffer: Buffer,
mimeType: string,
): Promise<string> {
const containerClient = this.blobServiceClient.getContainerClient(
this.containerName,
);
// コンテナが存在しない場合は作成する
await containerClient.createIfNotExists({ access: "blob" });
const blobName = `${crypto.randomUUID()}-${fileName}`;
const blockBlobClient: BlockBlobClient =
containerClient.getBlockBlobClient(blobName);
await blockBlobClient.uploadData(buffer, {
blobHTTPHeaders: { blobContentType: mimeType },
});
return blockBlobClient.url;
}
}BlobServiceClient.fromConnectionStringで接続文字列からクライアントを作るgetContainerClientでコンテナへの操作クライアントを取得するcreateIfNotExistsでコンテナが無ければ自動作成する(2回目以降は何もしない)getBlockBlobClientにBlobの名前を渡し、uploadDataでBufferを書き込むblockBlobClient.urlがアップロード後のアクセスURLになる
2-2. azure-blob.module.ts を作る
新規作成するファイルは api-sample/src/azure-blob/azure-blob.module.ts。
import { Module } from "@nestjs/common";
import { AzureBlobService } from "./azure-blob.service";
@Module({
providers: [AzureBlobService],
exports: [AzureBlobService],
})
export class AzureBlobModule {}exports に追加することで、AzureBlobModule をimportした他のModuleから AzureBlobService を注入できる。
3. FilesModule / FilesController を作る
3-1. files.controller.ts を作る
新規作成するファイルは api-sample/src/files/files.controller.ts。
import {
Controller,
Post,
UploadedFile,
UseInterceptors,
BadRequestException,
} from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
import { AzureBlobService } from "../azure-blob/azure-blob.service";
@Controller("files")
export class FilesController {
constructor(private readonly azureBlobService: AzureBlobService) {}
@Post("upload")
@UseInterceptors(FileInterceptor("file"))
async upload(
@UploadedFile() file: Express.Multer.File,
): Promise<{ url: string }> {
if (!file) {
throw new BadRequestException("ファイルが指定されていません");
}
const url = await this.azureBlobService.upload(
file.originalname,
file.buffer,
file.mimetype,
);
return { url };
}
}FileInterceptor("file") の引数はフォームフィールド名。@UploadedFile() でmulterが解析したファイルを受け取る。file.buffer にファイルの内容が入っており、これをそのまま AzureBlobService.upload に渡す。
3-2. files.module.ts を作る
新規作成するファイルは api-sample/src/files/files.module.ts。
import { Module } from "@nestjs/common";
import { FilesController } from "./files.controller";
import { AzureBlobModule } from "../azure-blob/azure-blob.module";
@Module({
imports: [AzureBlobModule],
controllers: [FilesController],
})
export class FilesModule {}4. AppModule に追加する
編集するファイルは api-sample/src/app.module.ts。
import { Module } from "@nestjs/common";
import { UsersModule } from "./users/users.module";
import { AuthModule } from "./auth/auth.module";
import { AzureBlobModule } from "./azure-blob/azure-blob.module";
import { FilesModule } from "./files/files.module";
@Module({
imports: [
UsersModule,
AuthModule,
AzureBlobModule,
FilesModule,
],
})
export class AppModule {}5. 動作確認
アプリを起動する。
pnpm start:dev別ターミナルでファイルをアップロードする。
curl -X POST http://localhost:3000/files/upload \
-F "file=@/path/to/image.png"成功すると次のようなレスポンスが返る。
{
"url": "https://<account>.blob.core.windows.net/uploads/abc123-image.png"
}返ってきたURLにブラウザからアクセスしてファイルが表示されれば完了。
よくあるエラー
AZURE_STORAGE_CONNECTION_STRING is not set
.env に接続文字列が設定されていないか、アプリ起動時に環境変数が読み込まれていない。@nestjs/config を使っている場合は AppModule に ConfigModule.forRoot() が追加されているか確認する。
ContainerNotFound
createIfNotExists を呼んでいない場合、コンテナが存在しないとエラーになる。AzureBlobService.upload 内で createIfNotExists が getBlockBlobClient より前に呼ばれているか確認する。
AuthenticationFailed
接続文字列のアカウントキーが間違っているか期限切れ。Azureポータルで再発行したキーで .env を更新する。
file.buffer が undefined
multerが diskStorage で動いている場合に起きる。FileInterceptor にオプションを明示して memoryStorage を指定する。
import { memoryStorage } from "multer";
@UseInterceptors(
FileInterceptor("file", { storage: memoryStorage() }),
)実装タスク(チェックリスト)
-
@azure/storage-blobと@types/multerをインストール -
.envにAZURE_STORAGE_CONNECTION_STRINGとAZURE_STORAGE_CONTAINER_NAMEを追加 -
src/azure-blob/azure-blob.service.tsを作成(BlobServiceClient.fromConnectionString/getContainerClient/createIfNotExists/getBlockBlobClient/uploadData) -
src/azure-blob/azure-blob.module.tsを作成(AzureBlobServiceをprovidersとexportsに追加) -
src/files/files.controller.tsを作成(@Post("upload")/FileInterceptor/@UploadedFile) -
src/files/files.module.tsを作成(AzureBlobModuleをimport) -
AppModuleにAzureBlobModuleとFilesModuleを追加 -
pnpm start:dev→curl -X POST ... -F "file=@..."でアップロードを確認