iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0
Modern Web

30 天,讓 TypeScript 把你的 Node.js 開發再升級系列 第 27

Day 27|AWS S3 實作檔案上傳:打造你的雲端檔案儲存中心

  • 分享至 

  • xImage
  •  

前言

在前幾天,我們已經完成了 EC2 主機部署 以及 RDS 資料庫串接

一個完整的後端雲端架構也漸漸成形。

但如果今天你的應用要讓使用者能上傳圖片、影片、報表、PDF…

這些「檔案」要放哪裡呢?

這時候,主角就登場了 —— AWS S3 (Simple Storage Service)

💡 S3 是 AWS 三大基礎服務之一(EC2、RDS、S3),

它就像你在雲端的「硬碟」,負責安全地儲存、管理檔案。

今天我們就要實際操作:

從建立 S3 Bucket → 設定權限 → 實作 Node.js 上傳功能,

一步步完成「檔案雲端化」的實戰練習!


為什麼要使用 AWS S3?

1.超穩定的雲端儲存空間

S3 的資料儲存設計達到「99.999999999%(11個9)」的耐久性,

意思是:你的圖片幾乎不可能遺失。

2.價格便宜又按量計費

你只需為實際用到的空間與流量付費,

不用像傳統伺服器那樣一直維護硬碟或備份。

3.全球可存取

S3 的檔案可以透過 URL 在全世界直接被讀取,

搭配 CDN(如 CloudFront)後,速度更是飛快。

4.完美整合 AWS 生態系

與 EC2、Lambda、CloudFront、IAM、Route53 等無縫結合

不論是後端 API、影片平台、或電商後台都能輕鬆擴充。


第一部分:AWS S3 設定步驟

步驟 1:建立 S3 Bucket

  1. 登入 AWS Console

  2. 進入 S3 服務

    • 在上方搜尋欄輸入「S3」
    • 點選「S3」服務
  3. 建立新的 Bucket

    • 點擊「 建立儲存貯體 」按鈕

    • AWS Region: 選擇離您最近的區域(例如:ap-northeast-1 東京)

    • Bucket name: 輸入唯一名稱(例如:my-ithome-2025-bucket

      • 注意:Bucket 名稱必須是全球唯一的

  4. 設定 Object Ownership

    • 選擇「ACLs disabled (recommended)」

  5. Block Public Access settings

    • 取消勾選「Block all public access」

    • 勾選確認框(我了解這會讓檔案變成公開存取)

    • ⚠️ 注意:如果您想讓上傳的圖片可以公開存取,需要取消封鎖

  6. Bucket Versioning

    • 選擇「Disable」
  7. 其他設定保持預設,點擊「 建立儲存貯體

步驟 2:設定 Bucket Policy(讓檔案可公開讀取)

  1. 點進剛建立的 Bucket

  2. 前往「Permissions」標籤

  3. 找到「Bucket policy」區塊,點擊「Edit」

  4. 貼上以下 Policy(記得替換 YOUR-BUCKET-NAME):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*"
    }
  ]
}
  1. 點擊「Save changes」

步驟 3:建立 IAM 使用者與取得 Access Key

  1. 進入 IAM 服務
    • 搜尋並點選「IAM」服務
  2. 建立新使用者
    • 左側選單點選「Users」

    • 點擊「Create user」

    • User name: 輸入 s3-upload-user

    • 點擊「Next」

  3. 設定權限
    • 選擇「Attach policies directly」

    • 搜尋並勾選「AmazonS3FullAccess」(簡單起見;生產環境建議使用更細緻的權限)

    • 點擊「Next」,然後「Create user」

  4. 建立 Access Key
    • 點進剛建立的使用者

    • 選擇「Security credentials」標籤

    • 找到「Access keys」區塊,點擊「Create access key」

    • 選擇「Application running outside AWS」

    • 點擊「Next」

    • 描述標籤:輸入「Node.js Upload App」

    • 點擊「Create access key」

    • 重要! 複製並儲存:(等等需在 .env 設定)

      • Access key ID (類似:AKIAIOSFODNN7EXAMPLE)
      • Secret access key (類似:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY)
      • ⚠️ Secret key 只會顯示一次,請妥善保存!

第二部分:程式碼整合

1. 安裝 AWS SDK

npm install @aws-sdk/client-s3
npm install --save-dev @types/node

2. 設定環境變數

在您的 .env 檔案加入:

# AWS S3 設定
AWS_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_S3_BUCKET_NAME=my-ithome-2025-bucket

3. 建立 S3 工具檔案

建立新檔案:src/utils/s3Utils.ts

import { S3Client } from "@aws-sdk/client-s3";

// 初始化 S3 Client
export const s3Client = new S3Client({
  region: process.env.AWS_REGION || "ap-northeast-1",
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID || "",
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "",
  },
});

export const bucketName = process.env.AWS_S3_BUCKET_NAME || "";

4. 修改上傳 Controller

修改 src/controllers/uploadController.ts,新增一個使用 S3 的函式:

import { Response, NextFunction } from "express";
import path from "path";
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3Client, bucketName } from "../utils/s3Utils";
import { AuthRequest } from "../middleware/isAuth";
import { AppDataSource } from "../config/db";
import { User } from "../entities/User";

/**
 * 上傳大頭照到 AWS S3
 */
export async function uploadAvatarToS3(req: AuthRequest, res: Response, next: NextFunction) {
  try {
    // 1. 檢查是否有上傳檔案
    if (!req.file) {
      res.status(400).json({
        status: "failed",
        message: "請選擇要上傳的圖片檔案",
      });
      return;
    }

    // 2. 檢查使用者是否已登入
    if (!req.user) {
      res.status(401).json({
        status: "failed",
        message: "請先登入",
      });
      return;
    }

    // 3. 產生檔案路徑與名稱
    const timestamp = Date.now();
    const ext = path.extname(req.file.originalname).toLowerCase();
    const key = `images/avatars/user-${req.user.id}-${timestamp}${ext}`;

    // 4. 上傳到 S3
    const command = new PutObjectCommand({
      Bucket: bucketName,
      Key: key,
      Body: req.file.buffer,
      ContentType: req.file.mimetype,
    });

    await s3Client.send(command);

    // 5. 產生公開 URL
    const region = process.env.AWS_REGION || "ap-northeast-1";
    const publicUrl = `https://${bucketName}.s3.${region}.amazonaws.com/${key}`;

    // 6. 更新資料庫
    await AppDataSource.getRepository(User).update(
      { id: req.user.id },
      { profileUrl: publicUrl }
    );

    // 7. 回傳成功訊息
    res.status(200).json({
      status: "success",
      message: "大頭照上傳成功",
      data: { avatarUrl: publicUrl },
    });
  } catch (err) {
    next(err);
  }
}

5. 更新路由

修改 src/routes/uploadRoutes.ts

import { Router } from "express";
import { uploadAvatar, uploadAvatarToS3 } from "../controllers/uploadController";
import { imageUpload } from "../middleware/imageUpload";
import { isAuth } from "../middleware/isAuth";

const router = Router();

// Firebase 版本(保留原有功能)
router.post(
  "/avatar",
  isAuth,
  imageUpload.single("file"),
  uploadAvatar,
);

// AWS S3 版本(新增)
router.post(
  "/avatar-s3",
  isAuth,
  imageUpload.single("file"),
  uploadAvatarToS3,
);

export default router;

測試上傳檔案

使用 Postman 測試:

查看資料庫 profile_url 欄位 : (成功填入檔案位址)

到 S3 查看檔案是否已經上傳 :

成功驗證檔案上傳!


重點提醒

  1. 不要將 AWS credentials 提交到 Git
    • 確保 .env.gitignore
  2. 成本考量
    • S3 儲存與流量都會收費,但非常便宜
    • 可設定 Lifecycle policy 自動刪除舊檔案

結語

這次的實作,我們練習了:

  • 建立並設定 S3 Bucket
  • 讓檔案可公開讀取
  • 透過 Node.js 上傳檔案到雲端
  • 並將結果寫回資料庫

雖然看起來步驟很多,但這正是「真實世界後端」會遇到的流程。

加油!


參考資源

commit :

feat: 🎸 integrate AWS S3 upload feature
新增 AWS S3 檔案上傳功能(s3Utils + uploadController + route)

Github 連結


上一篇
Day 26|雲端串接實戰:Node.js 成功連上 AWS RDS!
系列文
30 天,讓 TypeScript 把你的 Node.js 開發再升級27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言