Codecademy Middleware
การ maintain code ทำให้มันดูแลรักษาได้ง่าย เพราะมันจะอยู่กับเราไปอีกนานแสนนาน ไม่ให้คนอื่นมาด่าเราด้ายยยยว่าเราทำอะไรลงไปตอนนั้น
DRYing Code With Functions
Take a look at the following bad example code :
const addFive = number => {
const fiveAdded = number + 5;
console.log(`Your number plus 5 is ${fiveAdded}`);
}
const addTen = number => {
const tenAdded = number + 10;
console.log(`Your number plus 10 is ${tenAdded}`);
}
const addTwenty = number => {
const twentyAdded = number + 20;
console.log(`Your number plus 20 is ${twentyAdded}`);
}
good example
const addNumber = (number, addend) => {
const numAdded = number + addend;
console.log(`Your number plus ${addend} is ${numAdded}`);
}
ไม่สร้างฟังก์ชั่นทำงานคล้ายกันซ้ำกันไปมา(duplicate) ควรทำแค่อันเดียวโดยใช้การเพิ่ม arguments
สามารถนำมา reuse ได้ ซึ่งจะมีประโยชน์เวลา maintenance แก้ไขได้ง่าย โดยแก้แค่จุดเดียว
DRYing Routes With app.use()
app.use((req, res, next) => {
console.log('Request received');
});
- Middleware functions สามารถเข้าถึง
- request object (
req)
สิ่งที่ user/client ส่งมา
- response object (
res
) สิ่งที่เราใช้ตอบกลับ user กลับ (status-code, data) (ทำเสร็จแล้ว)
- request-response cycle (
next
) คือ ส่งให้ middleware function ตัวถัดไปที่มาทำงานต่อ
- request object (
const myRouter (req, res, next) => {
//code
next();
};
app.use(myRouter);
app.use('/', myRouter); //ทุก path
app.use('/user', myRouter); //เฉพาะ path user ไรงี้
next();
//เข้าทุก path เลย แต่ตัวอย่างด้านล่าง (Route-Level) จะเข้าตาม path ที่กำหนด
app.use((req, res, next) => {
console.log("A sorcerer approaches!");
next();
});
//แสดงผล
app.get('/magic/:spellname', (req, res, next) => {
console.log("The sorcerer is casting a spell!");
next();
});
//แสดงผล
app.get('/magic/:spellname', (req, res, next) => {
console.log(`The sorcerer has cast ${req.params.spellname}`);
res.status(200).send();
});
//ไม่แสดงผลเนื่องจาก อันก่อนหน้าไม่ส่ง next(); มา
app.get('/magic/:spellname', (req, res, next) => {
console.log("The sorcerer is leaving!");
});
// Accessing http://localhost:4001/magic/fireball
// Console Output:
// "A sorcerer approaches!"
// "The sorcerer is casting a spell!"
// "The sorcerer has cast fireball"
Route-Level app.use()
Single Path
app.use([path,] callback [, callback...])
สามารถเลือก specific path ได้
app.use('/beans/:beanName', (req, res, next) => {
const beanName = req.params.beanName;
if (!jellybeanBag[beanName]) {
console.log('Response Sent');
return res.status(404).send('Bean with that name does not exist');
}
req.bean = jellybeanBag[beanName];
req.beanName = beanName;
next();
});
app.get('/beans/:beanName', (req, res, next) => {
res.send(req.bean);
console.log('Response Sent');
});
app.post('/beans/:beanName/add', (req, res, next) => {
let bodyData = '';
req.on('data', (data) => {
bodyData += data;
});
});
app.post('/beans/:beanName/remove', (req, res, next) => {
let bodyData = '';
req.on('data', (data) => {
bodyData += data;
});
});
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
- จากตัวอย่าง กำหนด path
/beans/:beanName
ดังนั้น/beans/:beanName
/add
กับ/beans/:beanName
/remove
ก็ใช้ได้ เพราะมี/beans/:beanName
/beans/:beanName
คือ beans/string ใดๆ เช่น /beans/almond /beans/peanut
- ถ้าอยากให้ function จบการทำงานให้ใส่ return; ถ้าไม่ใส่ code จะทำงานต่อ
Multiple Path
- A string representing a path. match แบบตรงๆ คำต้องเหมือนแบบนี้เป๊ะๆ /beans
- A path pattern.
/beans/:beanName
คือ beans/string ใดๆ เช่น /beans/almond
- A regular expression pattern to match paths. [A-Za-z]{3} , a-z ตัวใหญ่หรือเล็กก็ได้ มีความยาว 3 ตัวอักษร
- An array of combinations of any of the above. ['/beans/', '/beans/:beanName']
app.use(['/beans/', '/beans/:beanName'], (req, res, next) => {
let bodyData = '';
req.on('data', (data) => {
bodyData += data;
});
req.on('end', () => {
if (bodyData) {
req.body = JSON.parse(bodyData);
}
next();
});
});
Middleware Stacks
middleware สามารถใส่ callback function ใน methods เช่นapp.use()
, app.get()
,app.post()
, app.put()
ได้มากกว่า 1 ตัว
const authenticate = (req, res, next) => {
...
};
const validateData = (req, res, next) => {
...
};
const getSpell = (req, res, next) => {
res.status(200).send(getSpellById(req.params.id));
};
const createSpell = (req, res, next) => {
createSpellFromRequest(req);
res.status(201).send();
};
const updateSpell = (req, res, next) => {
updateSpellFromRequest(req);
res.status(204).send();
}
app.get('/spells/:id', authenticate);
app.get('/spells/:id', getSpell);
app.get('/spells/:id', authenticate, getSpell); //รวมร่างงง
app.post('/spells', authenticate, validateData, createSpell);
app.put('/spells/:id', authenticate, validateData, updateSpell);
authenticate ก็ยังซ้ำเยอะอยู่ แบบนี้ก็ยังไม่ดี ถ้าจะให้ดีก็ไปใข้app.use()
แทน
Open-Source Middleware
Logging
ตัวจดบันทึก
morgan
คือ middleware สำหรับ log ข้อมูลที่ user request เข้ามาใช้งาน - kplug
an open-source library for logging information about the HTTP request-response cycle in a server application.
const morgan = require('morgan');
app.use(morgan('tiny'));
app.listen(PORT, () => {
console.log(`Response Sent`);
});
GET /beans/lemon 200 12 - 2.093 ms
Body Parsing
ช่วยให้สามารถแยกวิเคราะห์เนื้อหาของ HTTP request เมื่อ client ส่ง request มายัง Express server
อำนวยความสะดวกในการแปลงรูปแบบ body ของ http เป็นรูปแบบต่างๆ
const bodyParser = require('body-parser');
app.use(bodyParser.json());
Error-Handling
ตัวจัดการ error ของ express ปกติใช้ try-catch แต่ผลที่ได้จะไม่รู้ว่า error เกิดขึ้นที่ตรงไหนของ code ดังนั้นจึงมี middleware เข้ามาช่วย
const errorHandler = require('errorhandler')
app.use(errorHandler());
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
//ถ้าก้อนนี้ error จะลงไปที่ก้อนข้างล่าง ที่มี err รองรับ
app.post('/beans/:beanName/remove', (req, res, next) => {
const numberOfBeans = Number(req.body.number) || 0;
if (req.bean.number < numberOfBeans) {
const error = new Error('Not enough beans in the jar to remove!')
error.status = 400;
return next(error);
}
req.bean.number -= numberOfBeans;
res.send(req.bean);
});
// Add your error handler here:
app.use((err, req, res, next) => {
if (!err.status) {
err.status = 500;
}
res.status(err.status).send(err.message);
});
ถ้ามี error หลายแบบ ใช้ if-else ได้
app.use((err, req, res, next) => {
if (err.status === 404) {
res.status(404).send('404 Not Found')
} else {
res.status(500).send('Sever is currently down')
}
});
Open-Source Middleware
Middleware module | Description |
---|---|
cors | ทำให้ api ทำงานไวขึ้น เว็บไวขึ้น |
cookie-session | used for session management using cookies |
cookie-parser | Used to parse cookies in HTTP requests |
body-parser | Parse HTTP request body. See also: body, co-body, and raw-body. |
morgan | HTTP request logger. |
compression | Compress HTTP responses. |
serve-static | Serve static files. |
helmet | Used to add security-related HTTP headers to responses |
express-session | Used to manage session data in an Express application |
passport | Used to handle user authentication in an Express application |
multer | Used to handle file uploads in an Express application |
Router Parameters
router.param()
จะเช็คว่า routes ไหนมีการ input param ชื่อนั้นๆมาใช้บ้าง จะทำให้ function ใน route.param ทำงานก่อนจะเริ่มทำงาน routes นั้น เหมือนเป็น middleware อีกแบบหนึ่ง - kplug
app.param('spiceId', (req, res, next, id) => {
const spiceId = Number(id);
const spiceIndex = spiceRack.findIndex(spice => spice.id === spiceId);
if (spiceIndex !== -1){
req.spiceIndex = spiceIndex;
next();
} else {
res.sendStatus(404);
}
})
app.get('/spices/', (req, res, next) => {
res.send(spiceRack);
});
//จะเรียกใช้บรรทัดนี้ก่อน เพราะ specific spiceId
app.get('/spices/:spiceId', (req, res, next) => {
res.send(spiceRack[req.spiceIndex]);
});
router.param('userId', (req, res, next, id) => {
req.user = user;
next();
});
app.get('/users/:userId', (req, res) => {
console.log(req.user);
});
Merge Parameters
ถ้าต้องการใช้ req.params แบบข้ามไฟล์ หรือ ระดับ router คือ ต้องการดึง params จาก app มาใช้ใน router ก็ต้องมีการเรียกใช้งาน merge.param(); ใน router ด้วย - kplug
const router = require('express').Router({ mergeParams: true });
router.get('/:spiceId', (req, res, next) => {
const spiceId = Number(req.params.id);
const spiceIndex = spices.findIndex(spice => spice.id === spiceId);
if (spiceIndex !== -1) {
res.send(spices[spiceIndex]);
} else {
res.status(404).send('Spice not found.');
}
});