Azure Blob Storage là dịch vụ lưu trữ object của Microsoft Azure, tương tự AWS S3. Bài này hướng dẫn tích hợp Azure Blob Storage vào ứng dụng NestJS để upload và quản lý file.
1. Chuẩn bị Azure
Tạo Storage Account
- Vào Azure Portal → Storage accounts → Create
- Chọn Resource group, đặt tên Storage account name (chữ thường, không dấu)
- Chọn Region gần nhất (Southeast Asia nếu dùng tại Việt Nam)
- Redundancy: LRS (Locally Redundant) là đủ cho dev, GRS cho production
Lấy Connection String
Vào Storage Account → Access keys → Show keys → Copy Connection string
DefaultEndpointsProtocol=https;AccountName=mystorageaccount;AccountKey=xxx;EndpointSuffix=core.windows.net
Tạo Container
- Vào Containers → + Container
- Đặt tên container (ví dụ:
uploads) - Public access level:
Private: Chỉ truy cập qua SAS token hoặc connection stringBlob: Public đọc file, không list đượcContainer: Public hoàn toàn
2. Cài đặt SDK
npm install --save @azure/storage-blob
3. Cấu hình
Thêm vào .env:
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
AZURE_STORAGE_CONTAINER=uploads
Tạo src/config/azure-storage.config.ts:
import { BlobServiceClient } from '@azure/storage-blob';
export const blobServiceClient = BlobServiceClient.fromConnectionString(
process.env.AZURE_STORAGE_CONNECTION_STRING!,
);
export const CONTAINER_NAME = process.env.AZURE_STORAGE_CONTAINER!;
4. Tạo Azure Blob Service
src/azure-blob/azure-blob.service.ts:
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { BlobServiceClient, BlockBlobClient } from '@azure/storage-blob';
import { randomUUID } from 'crypto';
import { extname } from 'path';
@Injectable()
export class AzureBlobService {
private readonly containerClient;
constructor() {
const blobServiceClient = BlobServiceClient.fromConnectionString(
process.env.AZURE_STORAGE_CONNECTION_STRING!,
);
this.containerClient = blobServiceClient.getContainerClient(
process.env.AZURE_STORAGE_CONTAINER!,
);
}
private getBlockBlobClient(blobName: string): BlockBlobClient {
return this.containerClient.getBlockBlobClient(blobName);
}
async upload(file: Express.Multer.File, folder = 'uploads'): Promise<string> {
const blobName = `${folder}/${randomUUID()}${extname(file.originalname)}`;
const blockBlobClient = this.getBlockBlobClient(blobName);
await blockBlobClient.uploadData(file.buffer, {
blobHTTPHeaders: { blobContentType: file.mimetype },
});
return blockBlobClient.url;
}
async delete(blobName: string): Promise<void> {
const blockBlobClient = this.getBlockBlobClient(blobName);
await blockBlobClient.deleteIfExists();
}
async generateSasUrl(blobName: string, expiresInMinutes = 60): Promise<string> {
const blockBlobClient = this.getBlockBlobClient(blobName);
const expiresOn = new Date();
expiresOn.setMinutes(expiresOn.getMinutes() + expiresInMinutes);
const sasUrl = await blockBlobClient.generateSasUrl({
permissions: { read: true } as any,
expiresOn,
});
return sasUrl;
}
async listBlobs(prefix = 'uploads/'): Promise<string[]> {
const blobs: string[] = [];
for await (const blob of this.containerClient.listBlobsFlat({ prefix })) {
blobs.push(blob.name);
}
return blobs;
}
}
5. Tạo Module
src/azure-blob/azure-blob.module.ts:
import { Module } from '@nestjs/common';
import { AzureBlobService } from './azure-blob.service';
import { AzureBlobController } from './azure-blob.controller';
@Module({
providers: [AzureBlobService],
controllers: [AzureBlobController],
exports: [AzureBlobService],
})
export class AzureBlobModule {}
6. Tạo Controller
src/azure-blob/azure-blob.controller.ts:
import {
Controller,
Post,
Delete,
Get,
Param,
UseInterceptors,
UploadedFile,
Query,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import { AzureBlobService } from './azure-blob.service';
@Controller('blob')
export class AzureBlobController {
constructor(private readonly blobService: AzureBlobService) {}
@Post('upload')
@UseInterceptors(FileInterceptor('file', { storage: memoryStorage() }))
async upload(@UploadedFile() file: Express.Multer.File) {
const url = await this.blobService.upload(file);
return { url };
}
@Delete(':blobName')
async delete(@Param('blobName') blobName: string) {
await this.blobService.delete(blobName);
return { success: true };
}
@Get('sas/:blobName')
async getSasUrl(
@Param('blobName') blobName: string,
@Query('minutes') minutes: string,
) {
const url = await this.blobService.generateSasUrl(blobName, Number(minutes) || 60);
return { url };
}
@Get('list')
async list() {
const blobs = await this.blobService.listBlobs();
return { blobs };
}
}
7. SAS Token — Truy cập có thời hạn
SAS (Shared Access Signature) cho phép truy cập file với thời hạn định sẵn:
// Tạo SAS cho phép đọc trong 1 giờ
const sasUrl = await blobService.generateSasUrl('uploads/image.jpg', 60);
// https://mystorageaccount.blob.core.windows.net/uploads/uploads/image.jpg?sv=2024-11-04&se=...
8. So sánh AWS S3 vs Azure Blob Storage
| Tiêu chí | AWS S3 | Azure Blob Storage |
|---|---|---|
| Giá (per GB/tháng) | $0.023 | $0.018 (LRS) |
| Free tier | 5GB (12 tháng) | 5GB (12 tháng) |
| CDN tích hợp | CloudFront | Azure CDN |
| SDK | @aws-sdk/client-s3 | @azure/storage-blob |
| Presigned URL | Có (Presigned URL) | Có (SAS Token) |
| Tích hợp với | Lambda, EC2, ECS | Azure Functions, App Service |
9. Kết luận
Azure Blob Storage hoạt động tương tự AWS S3 nhưng với SDK và cách quản lý khác:
- Connection String thay vì Access Key + Secret Key
- Container thay vì Bucket
- SAS Token thay vì Presigned URL
- BlockBlobClient là interface chính để upload/download
Nếu hệ thống của bạn đang dùng Azure (Azure AD, Azure Functions, Azure SQL), dùng Blob Storage là lựa chọn tự nhiên để tối ưu chi phí và tích hợp.