# node.js練習筆記
- window.setTimeout 設定定時器
```javascript=
# 參數1是要執行的function 參數2是delay的時間點
setTimeout(function(){},3000);
```
- 箭頭函示
```javascript=
# 函示1跟2是同等意思 只是簡寫
1. () => { statements }
2. function(){ statements }
```
- new運算符號
```javascript=
# 創建一個 "具備後者相同屬性" 的新對象
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var mycar = new Car("Eagle", "Talon TSi", 1993);
```
- cb()用法 問老師
- 通常,第一個參數是一個error對象(通常為 false - 如果一切按計劃進行),隨後的參數是某種形式的數據。
- 讚美要公開,批評指教在私下。
- 時間、品質、成本、範疇 需要做取捨。
- 站立會議要點 Today、Yesterday、Need Help。
- 問題越早曝光,解決成本越低。
promise 是非同步的函式
```bash=
# 建立檔案夾
$ mkdir git-workshop
# 進入該檔案夾
$ cd git-workshop
# 初始化 git 專案
$ git init
# 檢查看看是否有建立 .git 檔案夾
$ ls -al
```
### 在電腦端連結github
```bash=
# 增加一個遠端repo 的連結,這個遠端的名稱叫做 origin
$ git remote add origin {url}
$ git branch -M main
# 以下指令二選一做,設定 main 這個分支跟 origin 遠端的main 分支做連結且 push 上去
$ git push --set-upstream origin main
$ git push -u origin main
# 設定 set-upstream 過後,就可以不用指定 origin {分支名稱}
$ git push
```
### 在github連結電腦
1. 到 github 建立 node-workshop 專案
2. clone 下來
```bash=
$ git clone {url}
```
### 刪除遠端檔案夾
```bash=
# 在 cache 中指定你要刪除的資料夾名稱
$ git rm -r --cached {檔案夾}
```
### 在 push 分支之前,該如何讓分支同步 master?
https://hackmd.io/@Heidi-Liu/git-workflow
第二種:繼續在舊的分支開發,需要先同步分支
較推薦這個方法,直接在舊分支同步遠端,就不須再另外新開分支
```bash=
$ git pull origin main // 同步遠端 main
$ git commit -am 'new_commit'
$ git push origin <old_branch> // push 分支
```
依照上述其中一種方式,之後就可以在 GitHub 頁面發 PR 進行 merge,解決衝突問題。
---
Promise 是一個表示"非同步"運算的"最終完成"或"失敗"的"物件"。
- 非同步
- 物件
- 最終完成
- 最終失敗
new promise 這句本身是同步函式,

gitignore
放忽略的檔案,但也要一起push到github上面
async function 可以用來定義一個"非同步"函式,讓這個函式本體是屬於非同步,但其內部以 "==同步的方式運行非同步==" 程式碼。
await一個promise物件
node-modual 是package.json下的產物
---
### 爬蟲資料練習
[安裝MySql](https://www.npmjs.com/package/mysql)
-> 用途使用mysql資料庫
```javascript=
// 下載myaql
$ npm install mysql
// 照文件建立連線
const mysql = require("mysql");
const connection = mysql.createConnection({
host:,
user:,
port:,
password:,
database:,
});
connection.connect((err) => {
if (err) {
console.error("資料庫連不上");
}
});
// 連線後再用sql語法撈資料
// 不關閉連線,認為程式一直在執行
connection.end();
```
SQL_injection -> 惡意攻擊
[安裝dotenv](https://www.npmjs.com/package/dotenv)
-> 設定環境變數,讓重要的帳密資料不要流出去github (!避免陷阱)
```javascript=
// 下載dotenv
$ npm install dotenv
// 引用dotenv
$ require('dotenv').config()
//建立.env檔案並修改設定名稱
//修改引用.env的名稱
$ process.env.{設定名稱}
const connection = mysql.createConnection({
host: process.env.DB_host,
user: process.env.DB_user,
port: process.env.DB_port,
password: process.env.DB_password,
database: process.env.DB_database,
});
// 為避免git clone的同事不清楚要建立什麼設定
// 會再建立一個 .env.example 推上github 並在README.md中告知
(!不要再把.env推上去!資安問題!)
(!.gitignore 要隔行放需要忽略的檔案)
```
forEach .map 高階函式盡量不要用async await 預期結果可能不一樣
高階函式-> 吃一個函式的函式,只要是可以接收函式作為參數,或是回傳函式作為輸出的函式,我們就稱為高階函式。
.map -> pending (有處理未處理完)
forEach -> 忽略await (印完才處理)
forloop -> 比較保險
---
- IP & Port
IP adress -> 網路連線地址
Port -> 瀏覽器程式
- Cookies 和 Session 的關係
*瀏覽器上的 Session Storage !== Session*
- Cookies 和 local Storage 都存在client端
[使用 localStorage 的缺點](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/javascript-localstorage-%E7%9A%84%E4%BD%BF%E7%94%A8-e0da6f402453)
> Cookies 和 Session 存在的目的都是幫助 Server 記住 Client 的狀態,差別在 Cookies 是將狀態資料存在 Client 端(i.e Browser),而 Session 則是將資料存在 Server 端 ( e.g Redis )。
Cookies 只能儲存結構簡單的資訊,為了彌補 Cookies 的不足,Server 端會產生一組 Session key 放入 Cookie 中。
- client端和sever端的呼叫
client端 -> 發出 Request -> sever端
sever端 -> 回覆 Response -> client端

[Github建立token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token)
- Token 主要的用途驗證權限
- 修改電腦中的 ==keychain密碼==改成==token==

- 兩種登入方式
1. cookie / session based
2. token 登入
- 產生一個 token => reponse 回給前端
- 前端需要自己儲存這個 token --> localstorage, session storage
- 每一次前端發送需要身份驗正的 request 時,都必須自己帶著這個 token
- 因為 cookie "預設"是無法跨網域讀取的
- 登出就會 delete session
JS 有兩種資料型別:
1. 基本資料型別 `number、boolean、null、undefined、string`
2. 物件 `物件、陣列、函式`
- 基本資料型別 -> 傳值 (容量小 複製的)
- 物件 -> 傳址 (容量大 共用的)

={...} 會創建一個新物件,不會指向同一個物件

### Module 模組化
- 優點:職責切割、好維護管理、設定公開不公開屬性
```javascript=
// (隱形的底層設定 無法改) exports = module.exports = {};
// 看到模組會設定空物件 module.exports = {};
//
// exports = 又創建一個新物件! 不等於 module.exports! 不能用
// 不要為 exports 重新宣告一個物件!
// exports = {
// showBrand,
// showColor,
// showOwner
// }
//因為最後是回傳module.exports 所以如果要改要用module.exports =
module.exports = {
setOwner,
showBrand,
showColor
}
// (隱形的底層設定 無法改) return module.exports;
// 雖然前面改到 exports 但最後回傳是 module.exports; 所以會變成空物件
```
- (隱形的底層設定 無法改) `exports = module.exports = {};`
- (隱形的底層設定 無法改) `return module.exports;`
- `CJS` 不是JS內建,但NodeJS可以實現,*目前較多使用*
- 匯出用`module.exports = { function1,function2,function3... };`
- 匯入用`require("./car1")`
- `ESM` JS內建的模組
- 匯出用 `export`
- 匯入用 `import`
*p.s.如果module已經被引用過就不會再重複引用了*
### bluebird 套件
- 幫助包成promisw物件
```bash=
# 下載bluebird
$ npm i bluebird
```
Promise.all -> 回傳 全部promise都做完的時候
Promise.race -> 回傳最早執行完成的
==(!面試必考題)==
- 靜態網站

- 動態網站

---
問六個需球
不看表面的問題!人會轉譯問題!
為什麼需要椅子? -> 為什麼要站著? -> 原本怎麼做的? -> 真正的需求
思考別人講的話
給人看: http://www.gooogle.com
電腦看的: IP address 172.217.160.99
DNS: domain name server
階層式
cache 用途:有效率、比較快
設定 cache 的暫存時效,看需求。
- 1分鐘就失效:每一分鐘就得去問一次、資料更新比較快
- 10小時失效: 10小時才需要去問一次、資料更新比較慢
---
# Express (node.js Web應用框架)
```bash=
# 初始化專案
$ npm init
# 下載express
$ npm i express
```
```bash=
# 引用express
const express = require("express");
# 利用 express 建立了一個 express application
let app = express();
# HTTP Method: get, post, put, patch, delete
app.get("/", function (request, response, next) {
response.send("Hello");
});
# 啟動.listen()
app.listen(3000, function () {
console.log("我的 web server 啟動了~");
})
```
#### nodemon套件
- node sever.js 開啟websever
- control+C ->每次更新都要重啟快捷鍵
- nodemon類似live sever,不用一直crtl+c關掉再重新啟動(取代node功能)
```bash=
$ npm i -g nodemon
```
#### 自訂linux指令
- package.json >> "script"區塊 >> 增加指令行 ex: `"dev":"nodemon sever.js"`
#### 中間鍵 middleware
- 照程式碼由上往下走
```javascript=
// 要造訪頁面才會經過流程 要有請求才會觸發 類似生命週期
// app.use包著中間鍵 (request,response,next)=>{}
app.use((request,response,next)=>{
let time = new Date();
console.log(`我是第一名 在 ${time.toISOString()}`);
// 要往下走 就給一個next(); 沒寫next就不會往下跑
next();
});
```
- 遇到 response.send() 就結束
```javascript=
// 遇到 response.send() 之後就近到sever回覆response
app.get("/", function (request, response, next) {
response.send("Hello nodemon");
});
```
- .json() 讀取json檔案 app.get()用在router上面(特殊中間件)
```bash=
app.get("/stock", async (req,res,next)=>{
let result = await connection.queryAsync("SELECT * FROM stock");
res.json(result);
})
```
- 中間鍵每個都會經過,最後再進到路由(router)
- router是特殊中間鍵(如果不呼叫response就沒有終點 會一直pending)

- router針對各支股票撈取資料(後面用:帶變數)
```javascript=
// 後面用:帶變數 搭配固定用法 req.params.{stockCode}
//網址後面有股票代碼就抓取對應的資料
app.get("/stock/:stockCode",async (req,res,next)=>{
//規定用法 req.params.stockCode
let result = await connection.queryAsync("SELECT * FROM stock_price where stock_id=?",[req.params.stockCode]);
res.json(result);
})
```
- mysql.createConnection() -> 只有一個連線
- mysql.createPool() -> 可以有多個連線
- 用中間鍵處理404 (按順序需要放在最下面)
```javascript=
// 處理 404 的中間件
app.use((req,res,next)=>{
res.status(404).send("Sorry 查無資料");
})
```
- 處理cors問題
```bash=
# 一樣先下載cors 並放在所有"路由"和"中間件"前面
# 使用 cors
const cors = require("cors");
app.use(cors());
```
---
8/28
偶合性 -> function內呼叫function 會有連動關係
用next();減少耦合性問題。
[HTTP狀態碼 status code](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status)
200 >> 成功
401 >> 沒登入
403 >> 沒授權
404 >> 客戶端錯誤
500 >> 伺服器錯誤
- next()沒帶參數,正常前往下一個
- next()中帶參數,呼叫express有錯誤,用捕捉錯誤的middleware去接
```bash=
# 也可以寫判斷式進到next錯誤訊息
let isLogin = false;
if(isLogin){
next();
}else{
next({
status:401,
message:"沒登入",
});
}
# next()沒帶參數,正常前往下一個
# next()中帶參數,呼叫express有錯誤,用捕捉錯誤的middleware去接
app.get("/about", function (request, response, next) {
console.log("我是about");
# 將next中放入錯誤訊息物件 統一丟給特殊middleware處理
next({
code:"1001",
status:500,
message:"測試錯誤"
});
});
# 超級特殊的middleware有"四個參數" 用來捕捉錯誤的 通常放在最下面
app.use((err,req,res,next)=>{
# err接的是next()中的東西
console.error(err);
res.status(err.status).json(err.message);
});
```
react中加入`REACT_APP_`類似於.env功能,自定義環境變量
https://create-react-app.dev/docs/adding-custom-environment-variables/
### 前後端整合 localhost
### 做分頁
- 注意localhost port 要統一(可以開啟network檢查工具檢查連線)
(十年的)資料多>>後端分頁
(3天的)資料少>>前端分頁
```sql=
-- sql計算比數的語法 用count
SELECT COUNT(*) AS total FROM stock_price where stock_id=?
```
```javascript=
// 無條件進位
Math.ceil()
```
1. react 做了一個 config.js --> .env failed???
2. 在用到設定的地方都 import config.js 的變數
3. 後端分頁: 取得總筆數、計算總頁數、取得該頁資料、回覆 pagination 給前端
4. 前端: 先取得正確格式 res.data --> res.data.result 先驗證原本的東西沒有壞
5. 開始做前端頁碼: 渲染頁碼、setTotalPage、修改 useEffect 跟呼叫的 API 網址
```javascript=
app.get("/stock/:stockCode",async (req,res,next)=>{
//做分頁
let page = req.query.page || 1; // 目前在第幾頁,預設是第一頁
const perPage = 10 ; // 每一頁的資料10筆
let count = await connection.queryAsync("SELECT COUNT(*) AS total FROM stock_price where stock_id=?",[req.params.stockCode]);
console.log(count); // 是一個物件 RowDataPacket { total: 17 }
const total = count[0].total // 就可以抓到總筆數了
const lastPage = Math.ceil(total/perPage); // Math.ceil無條件進位 (總筆數/一頁10筆)的無條件進位=總頁數
console.log(total,lastPage);
// LIMIT 要取幾筆資料
// OFFSET 要跳過多少
let offset = (page-1) * perPage;
let result = await connection.queryAsync("SELECT * FROM stock_price where stock_id=? ORDER BY date LIMIT ? OFFSET ?",[req.params.stockCode,perPage,offset]);
res.json(result);
})
```
- react 做了一個 config.js --> .env failed???
- .env 柏元測試出來了,要放在 src 的外面,也就是跟 src 同一層才可以!
https://create-react-app.dev/docs/adding-custom-environment-variables/
req.params.stockCoode
req.query.page
API 對模組來說就是`開一個網址給人使用`
### 註冊
先建資料庫
再切前端表格頁面
再做後端api
`<label></label>的 for跟id要一樣才會有功用`
API_url 做法( 不用每次都要重寫 http://localhost:3002 )
可以再額外建立一個utils>config檔
裡面export API_url
```bash=
#記得export
export const API_URL =
process.env.REACT_APP_API_URL
```
再在src“同層”建立.env檔 並在config.js中引用
```bash=
# 記得加上REACT_APP_ (react用法)
REACT_APP_API_URL=http://localhost:3002
```
連線 axios
後面代要傳過去的資料,{name,email,password,confirmPassword}
```bash=
let response = await axios.post(`${API_URL}/auth/register`, {
name,
email,
password,
confirmPassword,
});
```
RESTFUL API???
- HTTP Method: get, post, put, patch, delete
https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods
後端寫register的網址
- 要抓取前端來的資料因為是發送到後端 所以要用req去接
- 資料放在body裡面 所以是`req.body`
```bash=
# 做register的網址
app.post("/auth/register",(req,res,next)=>{
# 要抓取前端來的資料因為是發送到後端 所以要用req去接 資料放在body裡面
console.log(req.body);
res.json({});
})
```
- express規定要讀取req.body 需要引入這兩個中間件
```javascript=
// 要使用這兩個中間件,不然req.body會是undefined
// 使用這個中間件才可以讀到body的茲料
app.use(express.urlencoded({extended:true}));
// 使用這個中間件才可以讀json格式
app.use(express.json());
```
- 存進資料庫
```javascript=
// 存進資料庫
let result = await connection.queryAsync("INSERT INTO members (email, password, name) VALUE (?);" ,
[[
req.body.email,
req.body.password,
req.body.name,
]]);
```
- 加入驗證
- [使用 express-validator 套件](https://express-validator.github.io/docs/)
```javascript=
// 引入express-validator部分功能 body, validationResult
const { body, validationResult } = require('express-validator');
// 引入express-validator部分功能 body, validationResult
const { body, validationResult } = require('express-validator');
// 建立驗證規則
const registerRules =[
body("email").isEmail().withMessage("Email 欄位請填寫正確格式"),
body("password").isLength({min:6}).withMessage("密碼長度至少為6"),
// .custom() express-validator客製化規則語法
body("confirmPassword").custom((value,{req})=>{
// 客製化規則值要等於req.body.password
return value === req.body.password;
}).withMessage("密碼驗證不一致"),
];
// 路由中間件可以再放其他中間件
// app.post("path","中間鍵1","中間鍵2",....)
// 將registerRules放到指定路由下 只讓特定路由使用
app.post("/auth/register",registerRules,async(req,res,next)=>{
// 用validateResult韓式接住req
const validateResult = validationResult(req);
console.log(validateResult);
// ....
```
- 驗證
方法1.app.post("path","中間鍵1","中間鍵2",....)
方法2.app.post("path","中間鍵1(真正的處理函式)")
```javascript=
try{
let response = await axios.post(`${API_URL}/auth/register`, {
name,
email,
password,
confirmPassword,
});
} catch (e) {
// 前端可以透過 e.response 拿到 axios 的 response
console.error(e.response);
```
- 密碼加密
- 使用bcrypt套件 https://www.npmjs.com/package/bcrypt
```javascript=
// 密碼加密 bcrypt.hash(明文,salt)
// 因為是非同步記得加await
let hassPassword = await bcrypt.hash(req.body.password,10);
```
- 要上傳檔案(圖片)的版本,需要透過 FormData
```javascript=
// 要上傳檔案的版本,需要透過 FormData
let formData = new FormData();
formData.append("name", name);
formData.append("email", email);
formData.append("password", password);
formData.append("confirmPassword", confirmPassword);
formData.append("photo", photo);
// 後免傳遞的格式改成formData
let response = await axios.post(`${API_URL}/auth/register`, formData);
```
- 原本json格式 ==Content-Type: application/json==
- 使用formData之後header格式變成-> ==Content-Type: multipart/form-data==
- 所以要再使用 [multer 套件處理]( https://www.npmjs.com/package/multer)
- 其他類似的套件: multiparty, busyboy, formidable,…
- 再建立 public>uploads 放上傳圖片檔案
```javascript=
// !為了處理 multipart/form-data 所以要再使用 [multer 套件處理]
const multer = require("multer");
// 先使用diskStorage設定儲存方式
// 通常儲存在硬碟中用 .diskStorage()
const storage = multer.diskStorage({
// 設定儲存目的地
destination: function (req, file, cb) {
cb(null,path.join(__dirname,"../","public","uploads"));
},
// 設定儲存名稱
filename: function (req, file, cb) {
console.log(file);
cb(null, file.originalname);
},
});
// 再開始使用multer處理上傳檔案
const uploader = multer({
// 檔案儲存位置
storage: storage,
// 檔案格式驗證!很重要!
fileFilter: function (req, file, cb) {
if(
file.mimetype !== "image/jpeg" &&
file.mimetype !== "image/jpg" &&
file.mimetype !== "image/png"
){
cb(new Error("不接受的檔案型態"), false);
}
cb(null,true);
},
// 檔案大小限制
limits: {
fileSize: 1024 * 1024,
},
});
```
- 再將multer中間件 `uploader.single("photo")` 放進去
```javascript=
router.post(
"/register",
// multer 中間件需要放在 validation驗證前面 因為驗證規則要用到解譯過的資料
uploader.single("photo") ,
registerRules,
async (req, res, next) => { .....
```
- 引入 node.js內建物件 path 使用路徑
```javascript=
// 引入 node.js內建物件 path 使用路徑
const path = require("path");
// 路徑用法
cb(null,path.join(__dirname,"..","public","uploads"));
},
// -> __dirname/../public/uploads
```
- 讀檔案 要引用fs套件
```bash=
const fs = require("fs/promises");
fs.reaadFile("檔案路徑");
```
```bash=
__dirname
dirname
├── first.js
├── stock.txt
└── sub
└── second.js
```
我人在 dirname/ 的時候,執行 node sub/second.js 是可以讀得到 stock.txt
我人已經在 dirname/sub 裡了,node second.js,請問讀得到嗎?
A 讀得到
B 讀不到 V
- readFile 時,不是從這個程式本身的位置來出發,他是從你下執行指令 node 的位置開始找。
從頭到尾,second.js 跟 stock.txt 的相對位置不重要
重要的是你在哪裡下 node second.js 指令
解法1:
寫絕對路徑 -> 就跟你從哪裡下 node 沒有關係了
太長了,而且沒有彈性、檔案都不能搬動
/Users/xxx/ooo/node-workshop/dirname/stock.txt
解法2:
加上 …/ ==> 相對路徑 => 又跟你人在哪裡下 node 有關了
當我在 node-workshop/dirname/ 下 node 的時候,變成要去 node-workshop/stock.txt
但不會有…
==解法3(最佳解法):==
當我在 /dirname `執行 node sub/second.js` 的時候
__dirname = /Users/azole/Sites/node-workshop/dirname/sub
當我在 /dirname/sub 裡,`執行 node second.js` 的時候
__dirname = /Users/azole/Sites/node-workshop/dirname/sub
==__dirname => 其實就是 second.js 所在的檔案夾位置==,跟你人在哪裡下指令有沒有關係?沒有!
所以我們就可以利用 __dirname 來組合路徑,那這樣就跟你在哪裡執行 node 沒有關係了
- 三種組合路徑的方式
```bash=
let filepath = __dirname + "/../" + "stock.txt";
let filepath = [__dirname, "..", "stock.txt"].join("/");
let filepath = path.join(__dirname, "..", "stock.txt")
```
- 程式碼太長要改成module
- 建立 routers檔案夾 > stock.js
==router的模組固定寫法==
```javascript=
// router的模組固定寫法
// 這裡是stock router的模組
const express = require("express");
const router = express.Router();
// router就是在app底下得一個小型app
// 中間app.use等都改成router.use....
module.exports = router;
```
並在sever.js引用進這個router
```javascript=
let stockRouter = require("./routers/stock");
// 使用router中間鍵
// /stock
// /stock/:stockCode
app.use("/stock",stockRouter);
```
==!記得sever改為/stock之後 stockrouter的路徑就要刪除/stock 不然會重複==
MVC model-view-controller
- 在更改mysql中日期的格式!
```bash=
let connection = mysql.createPool({
host: process.env.DB_host,
user: process.env.DB_user,
port: process.env.DB_port,
password: process.env.DB_password,
database: process.env.DB_database,
# 使用預設值或10
connectionLimit : process.env.CONNECTION_LIMIT || 10 ,
# 更改mysql中日期的格式!
# 原本格式 "date": "2021-08-02T16:00:00.000Z",
# 修改後格式 "date": "2021-08-02",
dateStrings: true,
});
```
---
8/29
- Multer丟出來的錯誤是MulterError 不符合我們自訂格式 要做特別處理
- [instanceof](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof)
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
```javascript=
app.use((err,req,res,next)=>{
console.error("來自四個參數的錯誤處理",err);
// Multer丟出來的錯誤是MulterError 不符合我們自訂格式
if(err instanceof MulterError){
if(err.code === "LIMIT_FILE_SIZE"){
return res.status(400).json({message:"檔案太大拉"});
}
return res.status(400).json({message:err.message});
}
res.status(err.status).json(err.message);
});
```
- [javascript-prototype-chain 面試必考題](https://azole.medium.com/javascript-prototype-chain-ee5a90f6fa5e)
- 將上傳檔案取新檔名 兩個常用方法
- 根據 uuid 或 datetime
- [1.uuid 使用uuidv4套件](https://www.npmjs.com/package/uuidv4)
- [2.datetime 使用Date.now()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
```javascript=
// 設定儲存名稱
filename: function (req, file, cb) {
console.log(file);
// 將上傳檔案取新檔名 兩個常用方法 uuid 或 datetime
// uuidv4 or Date.now()
// 建議使用命名 -> member-75442486-0878-440c-9db1-a7006c25a39f.jpg
// 處理.jpg等格式
const ext = file.originalname.split(".").pop();
// cb(null, 新名字)
cb(null, `member-${Date.now()}.${ext}`);
},
```
- 使用中間件 express.static 來設定靜態檔案的位置
```bash=
# 使用中間件來設定靜態檔案的位置 讓後端伺服器讀得到上傳後的靜態檔案
app.use(express.static(path.join(__dirname,"public")));
```
### 正式環境部署
- 在前端檔案夾使用yarn build 編譯
- 會出現build檔案夾
```bash=
$ yarn build
```
- 再在後端sever.js中設置靜態檔案位置
```bash=
app.use(express.static(path.join(__dirname,"react")));
```
- 再將前端檔案夾中得build檔案夾內容複製放到react檔案夾中
==這樣就可以將前端東西放到後端伺服器上了!!==
- react底層好像是webpack-dev-server
- https://www.npmjs.com/package/webpack-dev-server
開發時會這樣做:
啟動 react => webpack-dev-server 跑在 3000
啟動 epxress => 3002
前端 http://localhost:3000 -> 測試前端
axios 是對 http://localhost:3002 打 API
正式環境部署的時候「可以」這樣做:(還有其他不同的做法)
當我們把 react build 完、且複製這個 build 檔案夾到 express 去
(express 有指定這個檔案夾是靜態資源目錄 express.static() )
因為 build 裡有一個 index.html
http://localhost:3002 (express) 也可以開啟這個編譯過後的 index.html (react)
開發的時候會這樣部署嗎?
部署是一種風險高的,特別是人為動作很多的時候
- 設定圖片存進mysql資料庫的檔案位置
```javascript=
// 設定圖片存進mysql資料庫的檔案位置 要儲存 uploads/member-1631240809142.jpg
let filename = req.file ? "/uploads/" + req.file.filename : "";
// http://localhost:3002/uploads/member-1631240809142.jpg
// 再存入mysql
let result = await connection.queryAsync(
"INSERT INTO members (email, password, name, photo) VALUE (?);",
[[req.body.email, hassPassword, req.body.name, filename]]
);
```
- 檢查帳號是否重複
```javascript=
// 檢查帳號是否重複
let member =await connection.queryAsync("SELECT * FROM members WHERE email = ?",[req.body.email]);
if(member.length > 0){
return next({
code:330001,
status:400,
message:"已經註冊過了",
})
}
```
---
0911
跨來源請求「預設」是不帶 cookie
要開放的話:
後端的 cors 要設定
```bash=
const cors = require("cors");
app.use(
cors({
origin: ["http://localhost:3000"],
// 跨源送 cookie
// 如果要把 credentials 設成 true, 那 origin 就不能是 *
// 不然太恐怖,誰都可以跨源送 cookie
credentials: true,
})
);
```
前端的 axios 也要設定
login 也要加
需要驗證身份的 api 呼叫也要加
```bash=
let result = await axios.post(
`${API_URL}/auth/login`,
// 設定可以跨源存取 cookie
{ email, password },
{
withCredentials: true,
}
);
cross-origin vs cross-site
這兩個東西是不一樣的,然後都會影響 cookie 能否讀取
```
45:30


- session&cookie -> cretidation (較新專案)
身份驗證完,將資料記錄在session中,例如session.member,此後靠是否有資料來判斷登入。
- JWT(token 較舊專案)
### JWT
同源預設會帶cookie所以JWT通常運用在跨源上,
身份驗證完,後端會產生一個token,將token回傳給前端,前端需要自己把token存起來,存在localstorage(前端)。
當前端發出任何需要身份驗證的API,需要自己帶著這個token。
後端如何處理token
1. 把token存在資料庫
- 當有token請求來時,就去資料庫看是否有這個token
- 登出就是把資料庫的這個token刪除
2. 後端完全不存token
- 當有token請求來時,他會解這個token然後取出資料
- 這個方法完全不需要存取資料庫
- 無法做token登出(可以但很麻煩)
- jwt
- refresh token: 24hr
- access token: 10min
- 每次資料要用access token
- 如果access token過期了,就用refresh token再去return一個新的 access token
```javascript=
// 下載jwt
$ npm install jsonwebtoken
const jwt = require('jsonwebtoken');
// 後端完全不存token
const token = jwt.sign(returnMember,process.env.JWT_SECERT,{expiresIn:"24h"});
// 將tokie回覆給前端
res.json({
....
token:token
})
```
- passport
- passport-facebook
- react-facebook-login
---
https://developer.mozilla.org/en-US/docs/Web/API/Element
### 尋找元素節點
- nextElementSibling 下一個
- previousElementSibling 上一個
- Event.currentTarget
- Event.target
- Spread syntax (...) 展開運算子(...) 允許可迭代的陣列或字串展開成0到多個參數(如果是function的話)或是0到多個元素(如果是array或字組的話),或如果是物件的話則展開成0到多個key-value pair。
https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Spread_syntax
- FileReader 藉由 FileReader 物件,Web 應用程式能以非同步(asynchronously)方式讀取儲存在用戶端的檔案(或原始資料暫存)內容,可以使用 File 或 Blob 物件指定要讀取的資料。
https://developer.mozilla.org/zh-TW/docs/Web/API/FileReader
- FileReader.onload 於讀取完成時觸發
- FileReader.result 讀入的資料內容。只有在讀取完成之後此屬性才有效,而資料的格式則取決於是由哪一個方法進行讀取。
- FileReader.readAsDataURL() 開始讀取指定的 Blob,讀取完成後屬性 result 將以 data: URL 格式(base64 編碼)的字串來表示讀入的資料內容。
-
bootstraap
垂直置中 d-flex align-items-center
---
==react面試前記得要練習用redux==
ORM(面試考題)
[Sequelize ORM](https://sequelize.org/)
資料庫協同作業工具
MVC 就是組織程式結構跟職責切割
重構 不改變功能的情況下,改寫程式
- github金鑰過期要重新申請
https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
{"metaMigratedAt":"2023-06-16T05:45:15.993Z","metaMigratedFrom":"Content","title":"node.js練習筆記","breaks":true,"contributors":"[{\"id\":\"3bf0a96b-7210-4533-8698-51c161dcf339\",\"add\":25804,\"del\":3317}]"}