ããã«ã¡ã¯ïŒæ ªåŒäŒç€Ÿestieã§EMããã£ãŠããŸããt-poyoã§ãã
ä»åã¯ãestieã®åµæ¥ä»¥æ¥èµ°ãç¶ããŠãããããã¯ãã®apiãã©ãæ¹åããŠãããã«ã€ããŠæžããããšæããŸãã
åœç€Ÿã¯"estie"ãš"estie pro"ãšãã2ã€ã®ãµãŒãã¹ãäœã£ãŠããŸãããä»åã¯"estie"ã®éçºã«ãŸã€ããã話ã«ãªããŸãã
ãããªæ¹ã«èªãã§ã»ãã
- estieã®éçºããŒã ãäœããã£ãŠããã®ãç¥ãããæ¹
- node.jsã§ã€ããããããã¯ããäœãããæ¹
- apiã®ã¢ãŒããã¯ãã£ã«æ©ã¿ã€ã€ããã¯ãªãŒã³ã¢ãŒããã¯ãã£ã»ã©ã¬ãã¬ãã«ããã®ã¯âŠããªæ¹
TL;DR
- ã³ãŒã«ããã¯é¢æ°ãå©çšããŠã¢ããªã±ãŒã·ã§ã³å±€ãExpressããåé¢ã§ãã
- åé¢ããé¢æ°ã«å¯ŸããŠè€éãªã¢ãã¯ã䜿ãããã¹ããæžãã
ãããã
estieã¯ã2020幎2æã«UIå·æ°ããããªããããŒãžã§ã³ã2.0ã«ã¡ãžã£ãŒã¢ããããŒãããŸããã
ãã®éãããã³ããšã³ãåŽã¯ãã¡ããã®ããšãããã¯ãšã³ãåŽãapiããã¹ãŠæžãçŽããªãªãŒã¹ããŸããã
ãããåœæã¯ããŸãã«ã人æ°ãšæéã足ããªãäžã®éçºã§ã以äžã®ãããªåé¡ããããŸããã
- ãã«ã¿ã€ã ã®ãšã³ãžãã¢ãããã(åœæt-poyoã¯æ¥åå§èšã§åå )ãã¢ãã«ããšã³ãã£ãã£ãšåŒã°ããæŠå¿µãåºããããšãã§ããªãã£ã
- node.js + Express ã§ã®é¢å¿åé¢ã«é¢ããŠããŠããŠããªãããŸãwebäžã«ãæ¥æ¬èªèšäºãå°ãªãã£ã
ç¹ã«åŸè
ã¯å€§ããªåé¡ã§ãçµæããã¹ãŠã®apiãªãœãŒã¹ã§ããexpressãªããžã§ã¯ãã«ãã¹ãŠã®åŠçããã¿æžãããé¢æ°ãæž¡ãããšãããããªæ§é ã§æžãéããŠããŸããŸããã
ã€ãŸããHTTPã¬ã¹ãã³ã¹çã«é¢å¿ãæã€ã€ã³ã¿ãŒãã§ã€ã¹å±€ãšããªã¯ãšã¹ãããªããŒã·ã§ã³ãããŒã¿æŽåœ¢ããããªãã¢ããªã±ãŒã·ã§ã³å±€ãçµåããŠããŸã£ãã®ã§ãã
課é¡
äžèšã®ãããªç¶æ
ã§ã¯ãæ§ã
ãªå°é£ãçºçããããšã¯çããã®æ³åã«é£ããªããšæããŸãã
äžã§ãç§ãã¡ãæãèŸæããã®ã¯ããã¹ããæžãã«ããããšããåé¡ã§ãã
äŸãã°ããããã¹ãã¿ã€ãã³ã°ã§ã¡ãŒã«ãé£ã°ãããèŠããããªãæ
å ±ãèŠããªãããªã©ãããžãã¹äžéèŠãªããžãã¯ãä¿èšŒãããã¹ããæ¬åœã«æžãã¥ãããéçºã®éå£ãšãªã£ãŠããŸããã
äžéšãsupertestãçšããŠãã¹ããæžããŠããŸããããã¢ãµãŒãã§ãã§ãã¯ã§ããã®ã¯expressãåãhttpã¬ã¹ãã³ã¹ã®ã¿ã
å
éšã®ãšã©ãŒãã³ããªã³ã°ããå€éšãµãŒãã¹ã«ãªã¯ãšã¹ããæããã¢ãžã¥ãŒã«ã®åäœç¢ºèªãã¬ã¹ãã³ã¹ã«å«ãŸããããŒã¿ã®åã»æ£åœæ§ãŸã§ãã§ãã¯ããã®ã¯è³é£ã®æã§ããã
Node.js ã®ã¹ãã·ã£ãªã¹ãã«æåãåãã
éæ¹ã«ãããestieéçºããŒã ã§ããããæãé£ããçžããããå
æãèŠåºããŸãã
ã€ããŒæ ªåŒäŒç€Ÿã§ãé»åž¯ããšããŠæŽ»èºãããŠãã伊藤さんããæãããæ¹åæ¹éãã¬ã¯ãã£ãŒããã ããŸããã
é»åž¯ã£ãŠäœïŒãšããæ¹ã¯äžèšãªã³ã¯ãã芧ãã ããã
https://about.yahoo.co.jp/hr/article/550625/
ãã®æ¹åæ¹éãšã¯ããã³ãŒã«ããã¯é¢æ°ãå©çšããã¢ããªã±ãŒã·ã§ã³å±€ã®åé¢ãã§ãã
å®éã«ãããªã£ããªãã¡ã¯ã¿ãªã³ã°
ããã§ã¯ãå®è£
äŸãèŠãŠãããŸãããã
ãªã¯ãšã¹ãããã£ã«user idãåããŠãuserã®ã€ã³ã¹ã¿ã³ã¹ãè¿ãapiãªãœãŒã¹ããããšéçšãããªãã¡ã¯ã¿ãªã³ã°ããŠãããŸãã
ããªããŒã·ã§ã³ã¯ joi ã䜿çšããŠããŸãã
(joiã¯hapi/joiãšããŠæäŸãããŠããŸããããçŸåšã¯ãããžã§ã¯ãã®çµäºã«äŒŽã移è¡ãæšå¥šãããŠããŸãã)
router.get('/', async (req, res, next) => { // idãnumberåã§ããããšãç¢ºèª const { error: validationError, } = joi.object().keys({ id: joi.number().required(), }).validate(req.body); if (validationError) { res.status(400).send({ error: 'Bad Request', }); return; } // DBããuseræ å ±ãååŸ const user = await getUserInstanceById(req.body.id); // userãèŠã€ãããªãå Žåã¯404ãè¿ã if (!user) { res.status(404).send({ error: 'Not Found', }); return; } // æ£åžžç³»ã¯200ã§è¿ã res.status(200).send(user); }); module.exports = router;
以åã®ã³ãŒãã¯ãã®ããã«1ã€ã®é¢æ°ã®äžã«ãã¹ãŠã®èŠçŽ ãè©°ã蟌ãã§ããŸããã
ãã®æžãæ¹ã§ã¯è€éãªãªããžã§ã¯ãæäœã¯ãããªã£ãŠããŸãããã
æ¬æ¥ããŒã¿ã®ååŸã»æŽåœ¢ã«éäžããããã®é¢æ°ã¯ãexpressã®routerã«äŸåããŠããã
æŽã«HTTPã¹ããŒã¿ã¹ã³ãŒãã®æå®ã«ãŸã§æãåºããŠããŸã£ãŠããŸãã
èŽ æ²¢ã¯å³æ¹ã欲匵ãã¯æã«è¯ãçµæããããããŸããããœãããŠã§ã¢ã¢ãŒããã¯ãã£ã«ãšã£ãŠã¯å€§æµã§ãã
handleré¢æ°ã®äœæ
ããã§ã¯ãestieã®apiã«å·£é£ã欲匵ãé¢æ°ãå解ããŠãããŸãããã
ãŸãããªã¯ãšã¹ãã®ã¿ãåŒæ°ã«åããã¢ããªã±ãŒã·ã§ã³å±€ãšããŠã®åŠçã«éäžããé¢æ°ãäœæããŸãã
// åŸã ããã«ãŠã§ã¢ã§ã®ãšã©ãŒãã³ããªã³ã°ã«äœ¿çšããããHandleErrorã¯ã©ã¹ãå®çŸ© class HandleError extends Error { } class ValidationError extends HandleError { constructor(message) { super(message); this.code = 400; } } const handler = async (req) => { // idãnumberåã§ããããšãç¢ºèª const { error: validationError, } = joi.object().keys({ id: joi.number().required(), }).validate(req.body); // ããªããŒã·ã§ã³å€±æãããã«ã¹ã¿ã ãšã©ãŒãªããžã§ã¯ããæãã if (validationError) { throw new ValidationError(validationError.message); } // DBããuseræ å ±ãååŸ const user = await getUserInstanceById(req.body.id); return user }; module.exports = { handler };
ãã®ããã«æžãããšã§ããã®é¢æ°åäœã§ã®ãã¹ããå¯èœã«ãªããŸãã
æ£åžžç³»ã¯ãã®promiseãresolveãããšãã«æ»ãå€ã®äžèº«ããã§ãã¯ããã°è¯ãã§ããã
ç°åžžç³»ã¯æãããããšã©ãŒã®ã€ã³ã¹ã¿ã³ã¹ããã§ãã¯ããããšã§ã«ããŒã§ããŸãã
wrapperé¢æ°ã®äœæ
ãããŠãäžèšã§exportããhandlerãã³ãŒã«ããã¯é¢æ°ãšããé¢æ°ããããã«expressã«åŒãæž¡ããŸãã
const { handler } = require('../äžèšã®é¢æ°'); const wrap = fn => async (req, res, next) => { try { await fn(req); res.status(200).send(r); } catch (e) { next(e); } }; module.exports = wrap(handler);
ãã®é¢æ°ã§ãresãªããžã§ã¯ããhandlerããåãé¢ããŠããŸãã
ãŸãããšã©ãŒæã®ã¹ããŒã¿ã¹ã³ãŒããexpressã®æäŸããnexté¢æ°ã決ããŠãããã®ã§ã
ã¢ããªã±ãŒã·ã§ã³å±€ãHTTPã¬ã€ã€ãŒãæ°ã«ããå¿
èŠããªããªããŸããã
nexté¢æ°å ã§ã®ãšã©ãŒãã³ããªã³ã°åŠç
next()ã«æããããã«ã¹ã¿ã ãšã©ãŒã¯ã©ã¹ãHTTPã¬ã¹ãã³ã¹ã«å€æããããã®ããã«ãŠã§ã¢ãå®çŸ©ããŸãã
const errorHandler = (err, req, res, next) => { if (err instanceof HandleError) { res.status(err.code).send(err.toString()); return; } res.status(500).send({ error: 'Server Error', }); }; app.use(errorHandler);
ã¹ããŒã¿ã¹ã³ãŒããæå®ããåŠçãéçŽããã ããªã®ã§ããã
ããã ãã§handler()é¢æ°å
ã®èŠéãããããªããŸãã
ãã¹ãã©ã€ãã£ã³ã°
åãé¢ããhandleré¢æ°ã®åœ¹å²ã¯éåžžã«ã·ã³ãã«ã§ãã
ãã®åœ¹å²ãšã¯ä»¥äžã®ãããªãã®ã§ãã
- ãªã¯ãšã¹ããªããžã§ã¯ããåãåã
- æ£åžžç³»ãªãã¬ã¹ãã³ã¹ã«æ ŒçŽãããããŒã¿ãè¿ã
- ç°åžžç³»ãªããšã©ãŒãã¹ããŒãã
ãªã®ã§ããã¹ããéåžžã«ã·ã³ãã«ã«ãªããŸãã
JESTãçšãããã¹ãã³ãŒããæžããŠãããŸãããã
const { handler } = require('./hoge'); // getUserInstanceById() ã¯mockããããã¹ãããŒã¿ãçšæ describe('handler', () => { it('id=1ã®ãªã¯ãšã¹ãã«å¯ŸããŠæ£ããããŒã¿ãè¿ã', async () => { const mockReq = { body: { id: 1 } }; const r = await handler(mockReq); expect(r).toBe(correctData); }); it('numberåã§ãªãidãåããæãšã©ãŒãæãã', async () => { const mockReq = { body: { id: 'text' } }; expect.assertions(1); try { await handler(mockReq); } catch (e) { expect(e instanceof NotFound).toEqual(true); } }); });
ã¢ãµãŒã·ã§ã³ã®Tips
ãšã©ãŒãã¹ããŒãããåŠçã®ã¢ãµãŒã·ã§ã³ã¯ããã€ãæ¹æ³ãããã®ã§ããã
ãæå³ã«åããŠpromiseãresolveããŠããŸã£ããšãããã¹ãã±ãŒã¹ã倱æããããã«ãæ°ãã€ããŠæžãå¿
èŠããããŸãã
ã¢ãµãŒã·ã§ã³ãæ°ããããšã§ãcatch()ç¯ã«å
¥ã£ãããšã確èªããäžèšã®æžãæ¹ããå§ãã§ãã
JESTã§ã¯done()という関数も提供ãããŠããŸããã
ãã®é¢æ°ã¯çŽæ¥ãšã©ãŒãæž¡ãããªãå Žåãã¿ã€ã ã¢ãŠãã«ãã£ãŠãã¹ãã±ãŒã¹ã®å€±æãå€å®ããããæ±ããå°ã
é£ããã§ãã
äŸãã°äžèšã®ãã¹ãã±ãŒã¹ãdoneã䜿ã£ãŠæžããŠã¢ãµãŒã·ã§ã³ãæ°ããªãå Žåã
it('numberåã§ãªãidãåããæãšã©ãŒãæãã', async (done) => { const mockReq = { body: { id: 'text' } }; return handler(mockReq).catch((e) => { expect(e instanceof NotFound).toEqual(true); done(); }); });
ãã®ããã«æžãããšã«ãªããŸããããã®ãã¹ãã±ãŒã¹ã倱æããå Žå
- ã¿ã€ã ã¢ãŠãã®æéååŸ ãããããã¹ãã®å®è¡æéãå€§å¹ ã«äŒžã³ã
- ãšã©ãŒã¡ãã»ãŒãžã«ã¿ã€ã ã¢ãŠãã®ã¡ãã»ãŒãž(äžèš)ãå«ãŸããåå ç¹å®ã«æéãå¢ãã
ãšãããã¡ãªããããããŸãã
// done() ãã¿ã€ã ã¢ãŠãã§å€±æãããšãã®ãšã©ãŒã¡ãã»ãŒãž : Timeout - Async callback was not invoked within the 10000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 10000ms timeout specified by jest.setTimeout.Error:
then() ç¯ã®äžã§done()ã«çŽæ¥ãšã©ãŒãæž¡ããŠãããã°ã¿ã€ã ã¢ãŠãååŸ
ã€å¿
èŠã¯ãªããªããŸããã
ãšã©ãŒã¡ãã»ãŒãžãèãããããã®ãçµæ§ãªæéã§ãããã
ãŸãšã
ã³ãŒã«ããã¯ã«äœ¿çšããé¢æ°ãåãåºããã¢ããªã±ãŒã·ã§ã³å±€ã®è²¬åã ããæèããããŸãJavascriptã®Errorãªããžã§ã¯ããå©çšããããšã§ãããªããã¹ããæžãããããªããŸããã
以äžã®ãããªå®è£
ãã¯ãããŠä»¥æ¥ããã¹ãã³ãŒããé 調ã«å¢ããŠããŸãã
ãã¹ãã³ãŒããæžããããæ§é ãäœãããšã¯ãããã¯ãã®å質ã»éçºäœéšã«ãšã£ãŠéåžžã«å€§äºã ãšå®æããŸããã
ãããã«
ãµãŒãã¹ãäœã£ãŠããäžã§ã»ãŒå¿
ãééããããšã«ããæ©ãåºãã»ãããã¿ã€ãã³ã°ããããšãã段éã§ã¯ãã¹ãã®æžãããããã¡ã³ããã³ã¹æ§ã軜èŠããŠããŸããã¡ã§ãã
é©åãªã¿ã€ãã³ã°ã§ã¢ãŒããã¯ãã£ãèŠçŽãããšã§ã
- ãã¹ããæžãããã
- æžããããªã
- ã¡ã³ããã³ã¹ããããªã
ã³ãŒãããŒã¹ãäœãããã®äŸ¡å€ããŠãŒã¶ãŒã«å±ããããšã¯ãšãŠã楜ããã§ãããããããããããŸããã
äžåç£æ¥çãITã®ã¡ããã§æ¹é©ããæ ªåŒäŒç€Ÿestieã§ã¯ããšã³ãžãã¢ã絶è³åéããŠããŸãã
æ°ããªé åãåãéããããæé«ã®ä»²éã«åºäŒãããã§ãïŒ
ãé£çµ¡ããåŸ ã¡ããŠããŸãïŒ