## 第十三堂:Firebase 檔案與空間上傳
* 錄影
* [Firebase Storage 官網文件](https://firebase.google.com/docs/storage/web/start?hl=zh-tw)
* [Ray 教練文件](https://israynotarray.com/nodejs/20221225/1867465275/)
- API 應用可參考[線稿圖](https://miro.com/app/board/uXjVLbiDml0=/)
- API 開發規格請依照 [此 API 文件](https://liberating-turtle-5a2.notion.site/38e4dd6775894e94a8f575f20ca5b867?v=17c6a246851881208205000cacf67220)
- db 欄位設計請依照 [此資料庫欄位圖片](https://firebasestorage.googleapis.com/v0/b/hexschool-courses.appspot.com/o/hex-website%2Fnode%2F1739179853094-fitness_5.png?alt=media&token=a65de209-3ae6-4263-bdc0-d4f08638ff20) 定義
## 看懂這張圖,就知道傳輸邏輯

## 常見 QA
Q:要欄位+ 檔案一次傳送?還是檔案先上傳?([範例](https://miro.com/app/board/uXjVLbiDml0=/?moveToWidget=3458764602225541418&cot=14))
## formidable
* [formidable 官方文件](https://github.com/node-formidable/formidable)
## 操作步驟
1. 加入 `formidable`、`firebase-admin` NPM,`npm install firebase-admin formidable`
3. 開啟一個 [Firebase](https://firebase.google.com/) 專案,領取 [300美額度](https://console.cloud.google.com/freetrial/signup/tos?utm_source=firebase_blog&utm_medium=referral&utm_id=gcp_free_trial_firebase)
4. 建立 ENV 檔`FIREBASE_STORAGE_BUCKET=`、`FIREBASE_SERVICE_ACCOUNT=`,放入環境變數,並在 config 加上 secret,`cmd + shift + P,輸入 Join Lines 合併成一行`
6. 在 `app.js`,設置一個 `upload.js` router
7. 在 upload.js 設置整合 Firebase 上傳功能
> 額度顯示
:::spoiler

:::
## 將內容變成一行的方法
```
// config/secret.js
module.exports = {
jwtSecret: process.env.JWT_SECRET,
jwtExpiresDay: process.env.JWT_EXPIRES_DAY,
firebase: {
serviceAccount: JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT),
storageBucket: process.env.FIREBASE_STORAGE_BUCKET
}
}
```
## postman 模擬傳送

## routes >upload.js
```jsx=
const express = require('express')
const config = require('../config/index')
const { dataSource } = require('../db/data-source')
const logger = require('../utils/logger')('Upload')
const auth = require('../middlewares/auth')({
secret: config.get('secret').jwtSecret,
userRepository: dataSource.getRepository('User'),
logger
})
const formidable = require('formidable')
const firebaseAdmin = require('firebase-admin')
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(config.get('secret.firebase.serviceAccount')),
storageBucket: config.get('secret.firebase.storageBucket')
})
const MAX_FILE_SIZE = 2 * 1024 * 1024 // 2MB
const ALLOWED_FILE_TYPES = {
'image/jpeg': true,
'image/png': true
}
const bucket = firebaseAdmin.storage().bucket()
const router = express.Router()
router.post('/', auth, async (req, res, next)=> {
try {
const form = formidable.formidable({
multiple: false,
maxFileSize: MAX_FILE_SIZE,
filter: ({ mimetype }) => {
return !!ALLOWED_FILE_TYPES[mimetype]
}
})
const [fields, files] = await form.parse(req)
logger.info('files')
logger.info(files)
logger.info('fields')
logger.info(fields)
const filePath = files.file[0].filepath
const remoteFilePath = `images/${new Date().toISOString()}-${files.file[0].originalFilename}`
await bucket.upload(filePath, { destination: remoteFilePath })
const options = {
action: 'read',
expires: Date.now() + 24 * 60 * 60 * 1000
}
const [imageUrl] = await bucket.file(remoteFilePath).getSignedUrl(options)
logger.info(imageUrl)
res.status(200).json({
status: 'success',
data: {
image_url: imageUrl
}
})
} catch (error) {
logger.error(error)
next(error)
}
})
module.exports = router
```
---
## 其他
:::success
#### 有參加「2025 Node.js 後端工程師專題班」的同學:
請在 3/2 (日) 23:59 前填寫[專題班分組表單](https://discord.com/channels/801807326054055996/1310780967425671218/1343754281903198278) ٩(๑•̀ω•́๑)۶
(※ 如果你有參加專題班但無法進入連結,請洽詢[六角客服](https://hex.school/qgbMS))