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');
});
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}`);
});

Multiple Path

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 เป็นรูปแบบต่างๆ

github body-parser

body-parser

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

express middleware

Middleware moduleDescription
corsทำให้ api ทำงานไวขึ้น เว็บไวขึ้น
cookie-sessionused for session management using cookies
cookie-parserUsed to parse cookies in HTTP requests
body-parserParse HTTP request body. See also: bodyco-body, and raw-body.
morganHTTP request logger.
compressionCompress HTTP responses.
serve-staticServe static files.
helmetUsed to add security-related HTTP headers to responses
express-sessionUsed to manage session data in an Express application
passportUsed to handle user authentication in an Express application
multerUsed 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.');
  }
});