Amazon S3は何百万ものアプリケーションのファイルストレージの基盤です。このガイドではS3 SDK、署名付きURL、バケットポリシー、CloudFrontとの統合を解説します。
S3の基礎:バケットとオブジェクト
S3はバケット内のオブジェクトとしてデータを保存します。
S3 Core Concepts:
Bucket — Top-level container (globally unique name, tied to a region)
Object — A file stored in a bucket (up to 5TB per object)
Key — The object's path/name: "uploads/2026/user-123/photo.jpg"
Prefix — Virtual folder: "uploads/2026/" (S3 has no real folders)
Region — Where the bucket lives: us-east-1, eu-west-1, ap-southeast-1
Storage Classes:
Standard — Frequently accessed data, 99.99% availability
Standard-IA — Infrequent access, lower cost, retrieval fee
One Zone-IA — Lower cost, single AZ (no cross-AZ replication)
Intelligent-Tiering — Auto-move between tiers based on access patterns
Glacier Instant — Archive with millisecond retrieval
Glacier Flexible — Archive with minutes-to-hours retrieval
Glacier Deep Archive — Cheapest storage, 12-48h retrievalAWS SDK v3でのファイルアップロード
AWS SDK v3は必要なコマンドのみをインポートするモジュラー設計を使用します。
// AWS SDK v3 — File Upload to S3
import {
S3Client,
PutObjectCommand,
GetObjectCommand,
DeleteObjectCommand,
ListObjectsV2Command,
} from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage'; // For multipart upload
import { fromEnv } from '@aws-sdk/credential-providers';
const s3 = new S3Client({
region: process.env.AWS_REGION || 'us-east-1',
credentials: fromEnv(), // Reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
});
const BUCKET = process.env.S3_BUCKET_NAME!;
// 1. Simple upload (files < 5MB)
async function uploadFile(key: string, file: Buffer, contentType: string) {
const command = new PutObjectCommand({
Bucket: BUCKET,
Key: key, // e.g., 'uploads/2026/user-123/avatar.jpg'
Body: file,
ContentType: contentType,
// Optional: set cache headers
CacheControl: 'max-age=31536000',
// Optional: make publicly readable
// ACL: 'public-read',
// Optional: custom metadata
Metadata: {
'uploaded-by': 'server',
'original-name': 'avatar.jpg',
},
});
const result = await s3.send(command);
return {
url: `https://${BUCKET}.s3.amazonaws.com/${key}`,
etag: result.ETag,
versionId: result.VersionId,
};
}
// 2. Multipart upload for large files (recommended for > 100MB)
async function uploadLargeFile(key: string, stream: NodeJS.ReadableStream, contentType: string) {
const upload = new Upload({
client: s3,
params: {
Bucket: BUCKET,
Key: key,
Body: stream,
ContentType: contentType,
},
partSize: 10 * 1024 * 1024, // 10 MB parts
queueSize: 4, // 4 concurrent uploads
});
upload.on('httpUploadProgress', (progress) => {
console.log(`Uploaded: ${progress.loaded}/${progress.total} bytes`);
});
return upload.done();
}セキュアな直接アップロードのための署名付きURL
署名付きURLにより、クライアントはサーバーを経由せずにS3に直接アップロードできます。
// Presigned URLs — Direct Browser-to-S3 Upload
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { randomUUID } from 'crypto';
// Generate presigned upload URL (browser uploads directly to S3)
async function generateUploadUrl(
fileName: string,
fileType: string,
userId: string
) {
const key = `uploads/${userId}/${randomUUID()}-${fileName}`;
const command = new PutObjectCommand({
Bucket: BUCKET,
Key: key,
ContentType: fileType,
// Optional: limit file size with Content-Length condition
// This must be enforced server-side with a policy
});
const signedUrl = await getSignedUrl(s3, command, {
expiresIn: 15 * 60, // 15 minutes
});
return {
uploadUrl: signedUrl,
key, // Return key so client can reference the uploaded file
expiresIn: 900, // seconds
};
}
// Express.js endpoint
app.post('/api/upload-url', authenticate, async (req, res) => {
const { fileName, fileType, fileSize } = req.body;
// Validate file type and size on server
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
if (!allowedTypes.includes(fileType)) {
return res.status(400).json({ error: 'File type not allowed' });
}
if (fileSize > 10 * 1024 * 1024) { // 10 MB limit
return res.status(400).json({ error: 'File too large' });
}
const { uploadUrl, key } = await generateUploadUrl(fileName, fileType, req.user.id);
res.json({ uploadUrl, key });
});
// Client-side: use the presigned URL to upload
async function uploadToS3(file, presignedUrl) {
const response = await fetch(presignedUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type,
},
});
if (!response.ok) throw new Error('Upload failed');
return response;
}CloudFront CDN統合
CloudFrontはAWSのCDNで、S3コンテンツを世界中のエッジロケーションでキャッシュします。
// CloudFront + S3 Setup
// 1. Bucket policy to allow CloudFront (Origin Access Control)
const bucketPolicy = {
Version: '2012-10-17',
Statement: [
{
Sid: 'AllowCloudFrontServicePrincipal',
Effect: 'Allow',
Principal: {
Service: 'cloudfront.amazonaws.com',
},
Action: 's3:GetObject',
Resource: `arn:aws:s3:::my-bucket/*`,
Condition: {
StringEquals: {
'AWS:SourceArn': 'arn:aws:cloudfront::123456789:distribution/ABCDEF123456',
},
},
},
],
};
// 2. Generate signed URLs for private CloudFront content
import { getSignedUrl } from '@aws-sdk/cloudfront-signer';
function generateCloudFrontSignedUrl(key: string, expirySeconds = 3600) {
const url = `https://${process.env.CLOUDFRONT_DOMAIN}/${key}`;
const expiryDate = new Date();
expiryDate.setSeconds(expiryDate.getSeconds() + expirySeconds);
return getSignedUrl({
url,
keyPairId: process.env.CLOUDFRONT_KEY_PAIR_ID!,
privateKey: process.env.CLOUDFRONT_PRIVATE_KEY!,
dateLessThan: expiryDate.toISOString(),
});
}
// 3. Invalidate CloudFront cache when S3 objects change
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront';
const cloudfront = new CloudFrontClient({ region: 'us-east-1' });
async function invalidateCache(paths: string[]) {
const command = new CreateInvalidationCommand({
DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID!,
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: paths.length,
Items: paths.map(p => `/${p}`),
},
},
});
return cloudfront.send(command);
}
// Usage: invalidate a specific file after update
await invalidateCache(['uploads/profile-pictures/user-123.jpg']);
// Wildcard: invalidate all files in a folder
await invalidateCache(['uploads/*']);よくある質問
S3バケットへのパブリックアクセスを許可すべきか?
静的ウェブサイトホスティングや本当に公開されたアセットのみです。バケットをプライベートに保ち、署名付きURLを使用してください。
署名付きURLの有効期間はどれくらいにすべきか?
アップロードURLは15-30分が一般的です。最大有効期間は7日間です。
S3ストレージコストを削減するには?
S3インテリジェントティアリングとライフサイクルルールを使用してください。
マルチパートアップロードとは?
大きなファイルを並行してアップロードされる部分に分割します。100MB以上のファイルに推奨されます。