diff --git a/.github/workflows/automerge-prs.yml b/.github/workflows/automerge-prs.yml index 688899c29..3f8333e46 100644 --- a/.github/workflows/automerge-prs.yml +++ b/.github/workflows/automerge-prs.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: automerge - uses: "pascalgn/automerge-action@v0.13.1" + uses: "pascalgn/automerge-action@v0.15.5" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" MERGE_LABELS: "auto-merge,!work in progress" @@ -30,4 +30,4 @@ jobs: MERGE_FORKS: "false" MERGE_RETRIES: "6" MERGE_RETRY_SLEEP: "10000" - MERGE_DELETE_BRANCH: "true" \ No newline at end of file + MERGE_DELETE_BRANCH: "true" diff --git a/.github/workflows/lint-and-generate-html-from-markdown.yml b/.github/workflows/lint-and-generate-html-from-markdown.yml index e50557e2d..9c62c5b4b 100644 --- a/.github/workflows/lint-and-generate-html-from-markdown.yml +++ b/.github/workflows/lint-and-generate-html-from-markdown.yml @@ -18,32 +18,13 @@ jobs: NODE_ENV: test steps: - - name: Checkout - uses: actions/checkout@v1 + - name: Checkout + uses: actions/checkout@v3 - - name: Setup Node.js environment - uses: actions/setup-node@v2.1.2 - with: - node-version: '14' + - name: Setup Node.js environment + uses: actions/setup-node@v3 + with: + node-version: "18" - - run: npm install - - run: npm run lint - - build: - name: Build - runs-on: ubuntu-20.04 - - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Setup Node.js environment - uses: actions/setup-node@v2.1.2 - with: - node-version: '14' - - - run: npm install - - run: npm run build - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - IS_FORK: ${{ github.repository != 'goldbergyoni/nodebestpractices' }} + - run: npm install + - run: npm run lint diff --git a/.github/workflows/update-date-in-last-update-badge.yml b/.github/workflows/update-date-in-last-update-badge.yml index 95873a926..c440307be 100644 --- a/.github/workflows/update-date-in-last-update-badge.yml +++ b/.github/workflows/update-date-in-last-update-badge.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Update last update badge run: | @@ -24,7 +24,7 @@ jobs: "${GITHUB_WORKSPACE}/.github/workflows/update-last-update-badge.sh" "${GITHUB_WORKSPACE}/README.md" - name: Commit & Create Pull Request - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: commit-message: update the last update badge to today [skip ci] author: Update Last Update Badge Action <${{ github.actor }}@users.noreply.github.com> diff --git a/.operations/.env.example b/.operations/.env.example deleted file mode 100644 index 77e1c98da..000000000 --- a/.operations/.env.example +++ /dev/null @@ -1 +0,0 @@ -GITHUB_TOKEN=REPLACEME \ No newline at end of file diff --git a/.operations/gen-html.js b/.operations/gen-html.js deleted file mode 100644 index d577df162..000000000 --- a/.operations/gen-html.js +++ /dev/null @@ -1,261 +0,0 @@ -const path = require('path'); -const cheerio = require('cheerio'); -const showdown = require('showdown'); -const Repository = require('github-api/dist/components/Repository'); -const { readdir, readFile, writeFile } = require('graceful-fs'); - -const imagemin = require('imagemin'); -const imageminJpegtran = require('imagemin-jpegtran'); -const imageminPngquant = require('imagemin-pngquant'); -const CIInfo = require('ci-info'); - -const converter = new showdown.Converter(); - -const templateFilePath = './.operations/res/template.html'; - -const imageminOpts = { - plugins: [ - imageminJpegtran(), - imageminPngquant({ quality: '65-80' }) - ] -}; - -console.info(`Working in [${process.cwd()}]`); - -const { isCI } = CIInfo; -const { GITHUB_TOKEN, OWNER_AND_REPO, BRANCH, IS_PR, IS_FORK } = getConfigFromEnv(); - -readDirPromise('./') - .then(async (fileNames) => { - const indexFileNames = fileNames.filter(fn => fn.includes('README.') && fn.includes('.md')); - - for (let fileName of indexFileNames) { - const startTime = new Date(); - console.info(`Beginning Generate Document [${fileName}] at [${startTime.toISOString()}]`); - try { - const templateHTML = await readFilePromise(templateFilePath); - const processedTemplateHTML = await inlineResources(templateHTML); - const outputHTML = await processMDFile(fileName, processedTemplateHTML); - console.info(`Completed Generation in [${computeElapsedTime(startTime)}s]`); - - const outFileName = path.parse(fileName).name + '.html'; - const outFilePath = path.join('.operations', 'out', outFileName); - console.info(`Writing output to [${outFilePath}]`); - await writeFilePromise(outFilePath, outputHTML); - - if(shouldUpdateGitHubPages()) { - const repo = new Repository(OWNER_AND_REPO, { - token: GITHUB_TOKEN - }); - - console.info(`Committing HTML file to branch [gh-pages]`); - await repo.writeFile('gh-pages', outFileName, outputHTML, ':loudspeaker: :robot: Automatically updating built HTML file', {}); - } - } catch (err) { - console.error(`Failed to generate from [${fileName}] in [${computeElapsedTime(startTime)}s]`, err); - process.exit(1); - } - } - }) - .then(() => { - console.log(`๐ŸŽ‰ Finished gen-html ๐ŸŽ‰`); - }) - -function getConfigFromEnv() { - if (CIInfo.GITHUB_ACTIONS) { - return getConfigFromGithubActionEnv() - } - return process.env; -} - -function getConfigFromGithubActionEnv() { - const config = { - ...process.env, - - OWNER_AND_REPO: process.env.GITHUB_REPOSITORY, - - IS_PR: CIInfo.IS_PR !== null ? CIInfo.IS_PR : process.env.GITHUB_EVENT_NAME === 'pull_request', - - // We assume we're in PR and and we get the source for the PR - BRANCH: process.env.GITHUB_HEAD_REF, - }; - - if(!config.IS_PR) { - // GITHUB_REF example: `refs/heads/main` - config.BRANCH = process.env.GITHUB_REF.substring('refs/heads/'.length); - } - - return config; -} - -function shouldUpdateGitHubPages() { - return isCI && !IS_FORK && !IS_PR && BRANCH === 'master'; -} - -function computeElapsedTime(startTime) { - return (Date.now() - startTime) / 1000; -} - - -async function processMDFile(filePath = '/', templateHTML = null) { - let mdSrc; - try { - mdSrc = await readFilePromise(filePath); - } catch (err) { - console.warn(`Failed to read file [${filePath}], does it exist?`); - return ''; - } - const generatedHTML = converter.makeHtml(mdSrc); - let nexHTML = generatedHTML; - if (templateHTML) { - const $ = cheerio.load(templateHTML); - $('.content').html(generatedHTML); - nexHTML = $.html(); - } - - const fileDir = path.parse(filePath).dir.replace(process.cwd(), '/') || '/'; - - console.log(`Processing file [${filePath}]`); - const outHtml = await ( - inlineLocalReferences(nexHTML, fileDir) - .then((html) => fixMdReferences(html)) - .then((html) => fixHashAs(html)) - .then((html) => inlineAssets(html, fileDir)) - ); - - return outHtml; -} - -const internalRefRegExp = /^((?!http)(?!data:)(?!#)(?!\/\/).)*$/; // Doesn't start with 'http', 'data:', '#', or '//' -async function inlineLocalReferences(html, filePath = '/') { - const $ = cheerio.load(html); - const as = $('a'); - const internalAs = as.toArray().filter((a) => internalRefRegExp.test(a.attribs.href) && !a.attribs.href.includes('README')); - - const processedInternalRefs = await Promise.all( - internalAs.map((a) => processMDFile(path.resolve(filePath, a.attribs.href))) - ); - - processedInternalRefs.forEach((processedHTML, index) => { - const originalA = $(internalAs[index]); - - const contentId = originalA.text().replace(/[^A-Za-z0-9]/g, '_'); - $('.references').append([ - $('
'), - $('
') - .addClass('reference-section') - .attr('id', contentId) - .html(processedHTML) - ]); - - originalA.attr('href', `#${contentId}`); - }); - - return $.html(); -} - -async function fixMdReferences(html) { // Primarily for links to translations - const $ = cheerio.load(html); - const as = $('a'); - const mdReferences = as.toArray().filter((a) => internalRefRegExp.test(a.attribs.href) && a.attribs.href.includes('.md')); - - mdReferences - .forEach((a) => { - const $a = $(a); - const href = $a.attr('href') - const newHref = href.replace('.md', '.html'); - $a.attr('href', './' + newHref); - }) - - return $.html(); -} - -async function inlineAssets(html, filePath = '/') { - const $ = cheerio.load(html); - const imgs = $('img'); - const internalImgs = imgs.toArray().filter((img) => internalRefRegExp.test(img.attribs.src)); - - for (let img of internalImgs) { - const ext = path.parse(img.attribs.src).ext.slice(1); // parse().ext includes '.' - const imgPath = path.resolve('/', filePath, img.attribs.src); - const imgBuffer = await readFilePromise(imgPath, null); - const compressedImgBuffer = await imagemin.buffer(imgBuffer, imageminOpts); - const base64 = compressedImgBuffer.toString('base64'); - const mediaUri = `data:image/${ext};base64,${base64}`; - const originalImg = $(img); - originalImg.attr('src', mediaUri); - } - - return $.html(); -} - -async function fixHashAs(html) { - const $ = cheerio.load(html); - const as = $('a'); - - const hashAs = as.toArray().filter((a) => a.attribs.href[0] === '#'); - hashAs.forEach(a => { - $(a).attr('href', a.attribs.href.replace(/-/g, '')); - }); - - return $.html() -} - - - -async function inlineResources(html, filePath = '/') { - const $ = cheerio.load(html); - const scripts = $('script[src]'); - const links = $('link[href]'); - - const internalScripts = scripts.toArray().filter((script) => internalRefRegExp.test(script.attribs.src)); - const internalLinks = links.toArray().filter((link) => internalRefRegExp.test(link.attribs.href)); - - for (let scriptEl of internalScripts) { - const scriptPath = path.resolve('/', filePath, scriptEl.attribs.src); - const scriptBuffer = await readFilePromise(scriptPath, null); - const base64 = scriptBuffer.toString('base64'); - const mediaUri = `data:text/javascript;base64,${base64}`; - $(scriptEl).attr('src', mediaUri); - } - - for (let linkEl of internalLinks) { - const linkPath = path.resolve('/', filePath, linkEl.attribs.href); - const linkBuffer = await readFilePromise(linkPath, null); - const base64 = linkBuffer.toString('base64'); - const mediaUri = `data:text/css;base64,${base64}`; - $(linkEl).attr('href', mediaUri); - } - - return $.html(); -} - - - - -function readFilePromise(filePath, encoding = 'utf8') { - return new Promise((resolve, reject) => { - readFile(path.resolve(process.cwd(), './' + filePath), encoding, (err, content) => { - if (err) reject(err); - else resolve(content); - }); - }); -} - -function writeFilePromise(filePath, encoding = 'utf8') { - return new Promise((resolve, reject) => { - writeFile(path.resolve(process.cwd(), './' + filePath), encoding, (err, content) => { - if (err) reject(err); - else resolve(content); - }); - }); -} - -function readDirPromise(dirPath) { - return new Promise((resolve, reject) => { - readdir(path.resolve(process.cwd(), dirPath), (err, files) => { - if (err) reject(err); - else resolve(files); - }); - }); -} diff --git a/.operations/out/.gitignore b/.operations/out/.gitignore deleted file mode 100644 index d6b7ef32c..000000000 --- a/.operations/out/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/.operations/res/github.css b/.operations/res/github.css deleted file mode 100644 index 791932b87..000000000 --- a/.operations/res/github.css +++ /dev/null @@ -1,99 +0,0 @@ -/* - -github.com style (c) Vasily Polovnyov - -*/ - -.hljs { - display: block; - overflow-x: auto; - padding: 0.5em; - color: #333; - background: #f8f8f8; -} - -.hljs-comment, -.hljs-quote { - color: #998; - font-style: italic; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-subst { - color: #333; - font-weight: bold; -} - -.hljs-number, -.hljs-literal, -.hljs-variable, -.hljs-template-variable, -.hljs-tag .hljs-attr { - color: #008080; -} - -.hljs-string, -.hljs-doctag { - color: #d14; -} - -.hljs-title, -.hljs-section, -.hljs-selector-id { - color: #900; - font-weight: bold; -} - -.hljs-subst { - font-weight: normal; -} - -.hljs-type, -.hljs-class .hljs-title { - color: #458; - font-weight: bold; -} - -.hljs-tag, -.hljs-name, -.hljs-attribute { - color: #000080; - font-weight: normal; -} - -.hljs-regexp, -.hljs-link { - color: #009926; -} - -.hljs-symbol, -.hljs-bullet { - color: #990073; -} - -.hljs-built_in, -.hljs-builtin-name { - color: #0086b3; -} - -.hljs-meta { - color: #999; - font-weight: bold; -} - -.hljs-deletion { - background: #fdd; -} - -.hljs-addition { - background: #dfd; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} diff --git a/.operations/res/highlight.pack.js b/.operations/res/highlight.pack.js deleted file mode 100644 index b64bd7d80..000000000 --- a/.operations/res/highlight.pack.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! highlight.js v9.13.1 | BSD3 License | git.io/hljslicense */ -!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=M.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function c(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function u(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function c(e){l+=""}function u(e){("start"===e.event?o:c)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substring(s,g[0].offset)),s=g[0].offset,g===e){f.reverse().forEach(c);do u(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),u(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function l(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):B(a.k).forEach(function(e){c(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.endSameAsBegin&&(a.e=a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return s("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var u=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=u.length?t(u.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e){return new RegExp(e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")}function c(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t].endSameAsBegin&&(n.c[t].eR=o(n.c[t].bR.exec(e)[0])),n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function s(e,n){return!a&&r(n.iR,e)}function p(e,n){var t=R.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function d(e,n,t,r){var a=r?"":j.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=p(E,r),e?(M+=e[1],a+=d(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function b(){var e="string"==typeof E.sL;if(e&&!L[E.sL])return n(k);var t=e?f(E.sL,k,!0,B[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(B[E.sL]=t.top),d(t.language,t.value,!1,!0)}function v(){y+=null!=E.sL?b():h(),k=""}function m(e){y+=e.cN?d(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function N(e,n){if(k+=e,null==n)return v(),0;var t=c(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),v(),t.rB||t.eB||(k=n)),m(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),v(),a.eE&&(k=n));do E.cN&&(y+=I),E.skip||E.sL||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&(r.endSameAsBegin&&(r.starts.eR=r.eR),m(r.starts,"")),a.rE?0:n.length}if(s(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var R=w(e);if(!R)throw new Error('Unknown language: "'+e+'"');l(R);var x,E=i||R,B={},y="";for(x=E;x!==R;x=x.parent)x.cN&&(y=d(x.cN,"",!0)+y);var k="",M=0;try{for(var C,A,S=0;;){if(E.t.lastIndex=S,C=E.t.exec(t),!C)break;A=N(t.substring(S,C.index),C[0]),S=C.index+A}for(N(t.substr(S)),x=E;x.parent;x=x.parent)x.cN&&(y+=I);return{r:M,value:y,language:e,top:E}}catch(O){if(O.message&&-1!==O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function g(e,t){t=t||j.languages||B(L);var r={r:0,value:n(e)},a=r;return t.filter(w).filter(x).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return j.tabReplace||j.useBR?e.replace(C,function(e,n){return j.useBR&&"\n"===e?"
":j.tabReplace?n.replace(/\t/g,j.tabReplace):""}):e}function d(e,n,t){var r=n?y[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function h(e){var n,t,r,o,s,l=i(e);a(l)||(j.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=l?f(l,s,!0):g(s),t=c(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=u(t,c(o),s)),r.value=p(r.value),e.innerHTML=r.value,e.className=d(e.className,l,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){j=o(j,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,h)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=L[n]=t(e);r.aliases&&r.aliases.forEach(function(e){y[e]=n})}function R(){return B(L)}function w(e){return e=(e||"").toLowerCase(),L[e]||L[y[e]]}function x(e){var n=w(e);return n&&!n.disableAutodetect}var E=[],B=Object.keys,L={},y={},k=/^(no-?highlight|plain|text)$/i,M=/\blang(?:uage)?-([\w-]+)\b/i,C=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,I="
",j={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=h,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.autoDetection=x,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"meta",b:/<\?xml/,e:/\?>/,r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0},{b:'b"',e:'"',skip:!0},{b:"b'",e:"'",skip:!0},s.inherit(s.ASM,{i:null,cN:null,c:null,skip:!0}),s.inherit(s.QSM,{i:null,cN:null,c:null,skip:!0})]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}}); \ No newline at end of file diff --git a/.operations/res/normalize.css b/.operations/res/normalize.css deleted file mode 100644 index 81c6f31ea..000000000 --- a/.operations/res/normalize.css +++ /dev/null @@ -1,427 +0,0 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - -moz-box-sizing: content-box; - box-sizing: content-box; - height: 0; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - color: inherit; /* 1 */ - font: inherit; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -td, -th { - padding: 0; -} \ No newline at end of file diff --git a/.operations/res/skeleton.css b/.operations/res/skeleton.css deleted file mode 100644 index f28bf6c59..000000000 --- a/.operations/res/skeleton.css +++ /dev/null @@ -1,418 +0,0 @@ -/* -* Skeleton V2.0.4 -* Copyright 2014, Dave Gamache -* www.getskeleton.com -* Free to use under the MIT license. -* http://www.opensource.org/licenses/mit-license.php -* 12/29/2014 -*/ - - -/* Table of contents -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ -- Grid -- Base Styles -- Typography -- Links -- Buttons -- Forms -- Lists -- Code -- Tables -- Spacing -- Utilities -- Clearing -- Media Queries -*/ - - -/* Grid -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -.container { - position: relative; - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 20px; - box-sizing: border-box; } -.column, -.columns { - width: 100%; - float: left; - box-sizing: border-box; } - -/* For devices larger than 400px */ -@media (min-width: 400px) { - .container { - width: 85%; - padding: 0; } -} - -/* For devices larger than 550px */ -@media (min-width: 550px) { - .container { - width: 80%; } - .column, - .columns { - margin-left: 4%; } - .column:first-child, - .columns:first-child { - margin-left: 0; } - - .one.column, - .one.columns { width: 4.66666666667%; } - .two.columns { width: 13.3333333333%; } - .three.columns { width: 22%; } - .four.columns { width: 30.6666666667%; } - .five.columns { width: 39.3333333333%; } - .six.columns { width: 48%; } - .seven.columns { width: 56.6666666667%; } - .eight.columns { width: 65.3333333333%; } - .nine.columns { width: 74.0%; } - .ten.columns { width: 82.6666666667%; } - .eleven.columns { width: 91.3333333333%; } - .twelve.columns { width: 100%; margin-left: 0; } - - .one-third.column { width: 30.6666666667%; } - .two-thirds.column { width: 65.3333333333%; } - - .one-half.column { width: 48%; } - - /* Offsets */ - .offset-by-one.column, - .offset-by-one.columns { margin-left: 8.66666666667%; } - .offset-by-two.column, - .offset-by-two.columns { margin-left: 17.3333333333%; } - .offset-by-three.column, - .offset-by-three.columns { margin-left: 26%; } - .offset-by-four.column, - .offset-by-four.columns { margin-left: 34.6666666667%; } - .offset-by-five.column, - .offset-by-five.columns { margin-left: 43.3333333333%; } - .offset-by-six.column, - .offset-by-six.columns { margin-left: 52%; } - .offset-by-seven.column, - .offset-by-seven.columns { margin-left: 60.6666666667%; } - .offset-by-eight.column, - .offset-by-eight.columns { margin-left: 69.3333333333%; } - .offset-by-nine.column, - .offset-by-nine.columns { margin-left: 78.0%; } - .offset-by-ten.column, - .offset-by-ten.columns { margin-left: 86.6666666667%; } - .offset-by-eleven.column, - .offset-by-eleven.columns { margin-left: 95.3333333333%; } - - .offset-by-one-third.column, - .offset-by-one-third.columns { margin-left: 34.6666666667%; } - .offset-by-two-thirds.column, - .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } - - .offset-by-one-half.column, - .offset-by-one-half.columns { margin-left: 52%; } - -} - - -/* Base Styles -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -/* NOTE -html is set to 62.5% so that all the REM measurements throughout Skeleton -are based on 10px sizing. So basically 1.5rem = 15px :) */ -html { - font-size: 62.5%; } -body { - font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ - line-height: 1.6; - font-weight: 400; - font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #222; } - - -/* Typography -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -h1, h2, h3, h4, h5, h6 { - margin-top: 0; - margin-bottom: 2rem; - font-weight: 300; } -h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} -h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } -h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } -h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } -h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } -h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } - -/* Larger than phablet */ -@media (min-width: 550px) { - h1 { font-size: 5.0rem; } - h2 { font-size: 4.2rem; } - h3 { font-size: 3.6rem; } - h4 { font-size: 3.0rem; } - h5 { font-size: 2.4rem; } - h6 { font-size: 1.5rem; } -} - -p { - margin-top: 0; } - - -/* Links -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -a { - color: #1EAEDB; } -a:hover { - color: #0FA0CE; } - - -/* Buttons -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -.button, -button, -input[type="submit"], -input[type="reset"], -input[type="button"] { - display: inline-block; - height: 38px; - padding: 0 30px; - color: #555; - text-align: center; - font-size: 11px; - font-weight: 600; - line-height: 38px; - letter-spacing: .1rem; - text-transform: uppercase; - text-decoration: none; - white-space: nowrap; - background-color: transparent; - border-radius: 4px; - border: 1px solid #bbb; - cursor: pointer; - box-sizing: border-box; } -.button:hover, -button:hover, -input[type="submit"]:hover, -input[type="reset"]:hover, -input[type="button"]:hover, -.button:focus, -button:focus, -input[type="submit"]:focus, -input[type="reset"]:focus, -input[type="button"]:focus { - color: #333; - border-color: #888; - outline: 0; } -.button.button-primary, -button.button-primary, -input[type="submit"].button-primary, -input[type="reset"].button-primary, -input[type="button"].button-primary { - color: #FFF; - background-color: #33C3F0; - border-color: #33C3F0; } -.button.button-primary:hover, -button.button-primary:hover, -input[type="submit"].button-primary:hover, -input[type="reset"].button-primary:hover, -input[type="button"].button-primary:hover, -.button.button-primary:focus, -button.button-primary:focus, -input[type="submit"].button-primary:focus, -input[type="reset"].button-primary:focus, -input[type="button"].button-primary:focus { - color: #FFF; - background-color: #1EAEDB; - border-color: #1EAEDB; } - - -/* Forms -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -input[type="email"], -input[type="number"], -input[type="search"], -input[type="text"], -input[type="tel"], -input[type="url"], -input[type="password"], -textarea, -select { - height: 38px; - padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ - background-color: #fff; - border: 1px solid #D1D1D1; - border-radius: 4px; - box-shadow: none; - box-sizing: border-box; } -/* Removes awkward default styles on some inputs for iOS */ -input[type="email"], -input[type="number"], -input[type="search"], -input[type="text"], -input[type="tel"], -input[type="url"], -input[type="password"], -textarea { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; } -textarea { - min-height: 65px; - padding-top: 6px; - padding-bottom: 6px; } -input[type="email"]:focus, -input[type="number"]:focus, -input[type="search"]:focus, -input[type="text"]:focus, -input[type="tel"]:focus, -input[type="url"]:focus, -input[type="password"]:focus, -textarea:focus, -select:focus { - border: 1px solid #33C3F0; - outline: 0; } -label, -legend { - display: block; - margin-bottom: .5rem; - font-weight: 600; } -fieldset { - padding: 0; - border-width: 0; } -input[type="checkbox"], -input[type="radio"] { - display: inline; } -label > .label-body { - display: inline-block; - margin-left: .5rem; - font-weight: normal; } - - -/* Lists -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -ul { - list-style: circle inside; } -ol { - list-style: decimal inside; } -ol, ul { - padding-left: 0; - margin-top: 0; } -ul ul, -ul ol, -ol ol, -ol ul { - margin: 1.5rem 0 1.5rem 3rem; - font-size: 90%; } -li { - margin-bottom: 1rem; } - - -/* Code -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -code { - padding: .2rem .5rem; - margin: 0 .2rem; - font-size: 90%; - white-space: nowrap; - background: #F1F1F1; - border: 1px solid #E1E1E1; - border-radius: 4px; } -pre > code { - display: block; - padding: 1rem 1.5rem; - white-space: pre; } - - -/* Tables -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -th, -td { - padding: 12px 15px; - text-align: left; - border-bottom: 1px solid #E1E1E1; } -th:first-child, -td:first-child { - padding-left: 0; } -th:last-child, -td:last-child { - padding-right: 0; } - - -/* Spacing -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -button, -.button { - margin-bottom: 1rem; } -input, -textarea, -select, -fieldset { - margin-bottom: 1.5rem; } -pre, -blockquote, -dl, -figure, -table, -p, -ul, -ol, -form { - margin-bottom: 2.5rem; } - - -/* Utilities -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -.u-full-width { - width: 100%; - box-sizing: border-box; } -.u-max-full-width { - max-width: 100%; - box-sizing: border-box; } -.u-pull-right { - float: right; } -.u-pull-left { - float: left; } - - -/* Misc -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -hr { - margin-top: 3rem; - margin-bottom: 3.5rem; - border-width: 0; - border-top: 1px solid #E1E1E1; } - - -/* Clearing -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ - -/* Self Clearing Goodness */ -.container:after, -.row:after, -.u-cf { - content: ""; - display: table; - clear: both; } - - -/* Media Queries -โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“โ€“ */ -/* -Note: The best way to structure the use of media queries is to create the queries -near the relevant code. For example, if you wanted to change the styles for buttons -on small devices, paste the mobile query code up in the buttons section and style it -there. -*/ - - -/* Larger than mobile */ -@media (min-width: 400px) {} - -/* Larger than phablet (also point when grid becomes active) */ -@media (min-width: 550px) {} - -/* Larger than tablet */ -@media (min-width: 750px) {} - -/* Larger than desktop */ -@media (min-width: 1000px) {} - -/* Larger than Desktop HD */ -@media (min-width: 1200px) {} diff --git a/.operations/res/template.html b/.operations/res/template.html deleted file mode 100644 index 735d4f393..000000000 --- a/.operations/res/template.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - Node Best Practices - - - - - - -
-
-
-
- - - - - - - - - \ No newline at end of file diff --git a/README.hebrew.md b/README.hebrew.md new file mode 100644 index 000000000..22d5ce0ba --- /dev/null +++ b/README.hebrew.md @@ -0,0 +1,1927 @@ +[โœ”]: assets/images/checkbox-small-blue.png + +# ืฉื™ื˜ื•ืช ืขื‘ื•ื“ื” ืžื•ืžืœืฆื•ืช ื‘ Node.js + +

+ Node.js Best Practices +

+ +
+ +
+ 102 items Last update: August 16, 2023 Updated for Node 14.0.0 +
+ +
+ +[](https://twitter.com/nodepractices/) **ืขื™ืงื‘ื• ืื—ืจื™ื ื• ื‘ื˜ื•ื•ื™ื˜ืจ!** [**@nodepractices**](https://twitter.com/nodepractices/) +
+**:writing_hand: ืชื•ืจื’ื ืขืœ ื™ื“ื™ [ื”ื•ื“ ื‘ื•ืืจ](https://github.com/hodbauer)** +
+ +ืœืงืจื™ืื” ื‘ืฉืคื•ืช ื ื•ืกืคื•ืช: [![CN](./assets/flags/CN.png)**ืกื™ื ื™ืช**](./README.chinese.md), [![FR](./assets/flags/FR.png)**ืฆืจืคืชื™ืช**](./README.french.md), [![BR](./assets/flags/BR.png)**ืคื•ืจื˜ื•ื’ื–ื™ืช**](./README.brazilian-portuguese.md), [![RU](./assets/flags/RU.png)**ืจื•ืกื™ืช**](./README.russian.md), [![PL](./assets/flags/PL.png)**ืคื•ืœื ื™ืช**](./README.polish.md), [![JA](./assets/flags/JA.png)**ื™ืคื ื™ืช**](./README.japanese.md), [![EU](./assets/flags/EU.png)**ื‘ืืกืงื™ืช**](./README.basque.md) [(![ES](./assets/flags/ES.png)**ืกืคืจื“ื™ืช**, ![HE](./assets/flags/HE.png)**ืขื‘ืจื™ืช**, ![KR](./assets/flags/KR.png)**ืงื•ืจื™ืื ื™ืช** ื• ![TR](./assets/flags/TR.png)**ื˜ื•ืจืงื™ืช** ื‘ืชื”ืœื™ืš! )](#translations) + +
+ +# Latest Best Practices and News + +- **๐Ÿ›ฐ 2023 edition is released soon**: We're now writing the next edition, stay tuned? + +- **โœจ 89,000 stars**: Blushing, surprised and proud! + +- **๐Ÿ”– New menu and tags**: Our menu is collapsible now and includes `#tags`. New visitors can read `#strategic` items first. Returning visitors can focus on `#new` content. Seniors can filter for `#advanced` items. Courtesy of the one and only [Rubek Joshi](https://github.com/rubek-joshi) + +- **![FR](./assets/flags/FR.png) French translation!1! :** The latest translation that joins our international guide is French. Bienvenue + +

+ +# ื‘ืจื•ื›ื™ื ื”ื‘ืื™ื! ืฉืœื•ืฉื” ื“ื‘ืจื™ื ืฉื›ื“ืื™ ืœื“ืขืช ืœืคื ื™ ืฉื’ื•ืœืœื™ื ืžื˜ื” + +**1. ื”ื ื›ื ืงื•ืจืื™ื ืขืฉืจื•ืช ืžืืžืจื™ื ืฉืœ ืฉื™ื˜ื•ืช ื”ืขื‘ื•ื“ื” ื”ืžื•ืžืœืฆื•ืช ื‘ Node.js -** ื”ืžืื’ืจ ื”ื–ื” ื”ื•ื ืกื™ื›ื•ื ืœื ื™ืกื•ืœื ื‘ืคื– ืฉืœ ืฉื™ื˜ื•ืช ื”ืขื‘ื•ื“ื” ื”ืžื•ืžืœืฆื•ืช ื‘ Node.js , ื›ืžื• ื›ืŸ ื”ื•ื ื ืขืฉื” ืขืœ ื‘ืฉื™ืชื•ืฃ ืคืขื•ืœื”. + +**2. ื–ื”ื• ื”ืื•ืกืฃ ื”ื’ื“ื•ืœ ื‘ื™ื•ืชืจ, ื•ื”ื•ื ืžืžืฉื™ืš ืœื’ื“ื•ืœ ื›ืœ ืฉื‘ื•ืข -** ื ื›ื•ืŸ ืœืจื’ืข ื–ื”, ื™ืฉ ืœืžืขืœื” ืž 100 ืฉื™ื˜ื•ืช ืขื‘ื•ื“ื” ืžื•ืžืœืฆื•ืช, ื”ืžืœืฆื•ืช ืืจื›ื™ื˜ืงื˜ื•ืจื” ื•ื”ืžืœืฆื•ืช ืกื’ื ื•ืŸ ื›ืชื™ื‘ื”. ื ื•ืฉืื™ื ื—ื“ืฉื™ื ื•ื‘ืงืฉื•ืช ื—ื“ืฉื•ืช (PR's) ืžืชื•ื•ืกืคื™ื ื›ืœ ื™ื•ื ื‘ืžื˜ืจื” ืœืฉืžื•ืจ ืืช ื”ืชื•ื›ืŸ ืžืขื•ื“ื›ืŸ. ืื ื—ื ื• ื ืฉืžื— ืœืจืื•ืชื›ื ืชื•ืจืžื™ื ืœืคื”, ื‘ื™ืŸ ืื ืœืชืงืŸ ืฉื’ื™ืื•ืช ืงื•ื“, ืขื–ืจื” ื‘ืชืจื’ื•ื, ืื• ืœื”ืฆื™ืข ืจืขื™ื•ื ื•ืช ืžื‘ืจื™ืงื™ื ื—ื“ืฉื™ื. ืจืื• ืืช [ื”ืžื“ืจื™ืš ืœื›ืชื™ื‘ืช ื”ื ื—ื™ื•ืช](./.operations/writing-guidelines.md). + +**3. ืฉื™ื˜ื•ืช ื”ืขื‘ื•ื“ื” ื›ื•ืœืœื•ืช ืžื™ื“ืข ื ื•ืกืฃ -** ืจื•ื‘ ื”ื ืงื•ื“ื•ืช ื›ื•ืœืœื•ืช ืงื™ืฉื•ืจ **๐Ÿ”—ืœืงืจื™ืื” ื ื•ืกืคืช** ืฉืžืจื—ื™ื‘ ืขืœ ื™ื“ื™ ื“ื•ื’ืžืื•ืช ืงื•ื“, ืฆื™ื˜ื•ื˜ื™ื ืžื‘ืœื•ื’ื™ื ื ื‘ื—ืจื™ื ื•ืžื™ื“ืข ื ื•ืกืฃ. + +

+ +# ืžืืช ื™ื•ื ื™ ื’ื•ืœื“ื‘ืจื’ + +### ืœื™ืžื“ื• ืื™ืชื™: ื›ื™ื•ืขืฅ, ืื ื™ ื ืคื’ืฉ ืขื ืงื‘ื•ืฆื•ืช ืžื›ืœ ื”ืขื•ืœื ื‘ืžื’ื•ื•ืŸ ืคืขื•ืœื•ืช ื›ืžื• ืกื“ื ืื•ืช ื•ืžืขื‘ืจ ืขืœ ืงื•ื“. ๐ŸŽ‰ ืœืื—ืจื•ื ื” ืคืจืกืžืชื™ ืืช [ื”ืงื•ืจืก ื”ืžืชืงื“ื ืœื›ืชื™ื‘ืช ื‘ื“ื™ืงื•ืช](https://testjavascript.com/) + +

+## ืชื•ื›ืŸ ื”ืขื ื™ื™ื ื™ื + +
+ + 1. ืžื‘ื ื” ื”ืคืจื•ื™ื™ืงื˜ (6) + + +  [1.1 ื‘ื ื• ืืช ื”ืคืจื•ื™ื™ืงื˜ ืœืคื™ ืจื›ื™ื‘ื™ื ืขืกืงื™ื™ื `#strategic` `#updated`](#-11-structure-your-solution-by-business-components)
+  [1.2 ื—ืœื•ืงืช ื”ืจื›ื™ื‘ื™ื ืœ3 ืฉื›ื‘ื•ืช, ืฉืžื™ืจื” ืขืœ ืฉื›ื‘ืช ื”ื•ื•ื‘ ื‘ื’ื‘ื•ืœื•ืชื™ื” `#strategic` `#updated`](#-12-layer-your-components-with-3-tiers-keep-the-web-layer-within-its-boundaries)
+  [1.3 ืขื˜ืคื• ื›ืœื™ื ืžืฉื•ืชืคื™ื ื‘ื—ื‘ื™ืœื•ืช, ืฉืงืœื• ืืช ื”ืคืฆืชื](#-13-wrap-common-utilities-as-packages-consider-publishing)
+  [1.4 ื”ืฉืชืžืฉื• ื‘ืงื•ื ืคื™ื’ื•ืจืฆื™ื” ืขื ืžืฉืชื ื™ ืกื‘ื™ื‘ื” ื‘ืื•ืคืŸ ืžื•ื“ืข, ืžืื•ื‘ื˜ื— ื•ื”ื™ืจืจื›ื™ `#updated`](#-14-use-environment-aware-secure-and-hierarchical-config)
+  [1.5 ืฉืงืœื• ืืช ื›ืœ ื”ื”ืฉืœื›ื•ืช ื‘ืขืช ื‘ื—ื™ืจืช ืžืกื’ืจืช `#new`](#-15-consider-all-the-consequences-when-choosing-the-main-framework)
+  [1.6 ื”ืฉืชืžืฉื• ื‘-TypeScript ื‘ืžื™ื“ืชื™ื•ืช ื•ื‘ืฆื•ืจื” ืžื•ืฉื›ืœืช `#new`](#-16-use-typescript-sparingly-and-thoughtfully)
+ +
+ +
+ + 2. ื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช (12) + + +  [2.1 ื”ืฉืชืžืฉื• ื‘ Async-Await ืื• ื”ื‘ื˜ื—ื•ืช ืœื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช ืืกื™ื ื›ืจื•ื ื™ื•ืช](#-21-use-async-await-or-promises-for-async-error-handling)
+  [2.2 ื”ืจื—ื™ื‘ื• ืืช ืžื‘ื ื” ืื•ื‘ื™ืงื˜ ื”ืฉื’ื™ืื” ื”ืžื•ื‘ื ื” Error `#strategic` `#updated`](#-22-extend-the-built-in-error-object)
+  [2.3 ื”ื‘ื—ื™ื ื• ื‘ื™ืŸ ืฉื’ื™ืื•ืช ืงื˜ืกื˜ืจื•ืคืœื™ื•ืช ืœื‘ื™ืŸ ืฉื’ื™ืื•ืช ืชืคืขื•ืœื™ื•ืช `#strategic` `#updated`](#-23-distinguish-catastrophic-errors-from-operational-errors)
+  [2.4 ื ื”ืœื• ืืช ื”ืฉื’ื™ืื•ืช ื‘ืžืจื•ื›ื– ื•ืœื ื‘ืืžืฆืขื•ืช ื›ืœื™ ื‘ื™ื ื™ื™ื `#strategic`](#-24-handle-errors-centrally-not-within-a-middleware)
+  [2.5 ืชืขื“ื• ืืช ืฉื’ื™ืื•ืช ื”-API ื‘ืืžืฆืขื•ืช OpenAPI ืื• GraphQL](#-25-document-api-errors-using-openapi-or-graphql)
+  [2.6 ื”ื•ืจื™ื“ื• ืืช ื”ืชื”ืœื™ืš ื‘ืฆื•ืจื” ืžืกื•ื“ืจืช ื›ืืฉืจ ื–ืจ ื‘ื ืœื‘ืงืจ `#strategic`](#-26-exit-the-process-gracefully-when-a-stranger-comes-to-town)
+  [2.7 ื”ืฉืชืžืฉื• ื‘-Logger ืžื•ื›ืจ ื•ืืžื™ืŸ ื›ื“ื™ ืœื”ื’ื“ื™ืœ ืืช ื”ืงึฐืจึดื™ืื•ึผืช ืฉืœ ื”ืฉื’ื™ืื•ืช `#updated`](#-27-use-a-mature-logger-to-increase-errors-visibility)
+  [2.8 ื‘ื™ื“ืงื• ืืช ืชื’ื•ื‘ืช ื”ืžืขืจื›ืช ืœืฉื’ื™ืื•ืช ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ื›ืœื™ ื”ื‘ื“ื™ืงื•ืช ื”ืื”ื•ื‘ ืขืœื™ื›ื `#updated`](#-28-test-error-flows-using-your-favorite-test-framework)
+  [2.9 ื’ืœื• ืฉื’ื™ืื•ืช ื•ื–ืžื ื™ ื”ืฉื‘ืชื” ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ื›ืœื™ APM](#-29-discover-errors-and-downtime-using-apm-products)
+  [2.10 ืชืคืกื• ืžืงืจื™ื ืœื ืžื˜ื•ืคืœื™ื ืฉืœ ื“ื—ื™ื•ืช ืฉืœ ื”ื‘ื˜ื—ื•ืช `#updated`](#-210-catch-unhandled-promise-rejections)
+  [2.11 ื”ื™ื›ืฉืœื• ืžื”ืจ, ื•ื“ืื• ืืช ืžืฉืชื ื™ ื”ืงืœื˜ ื‘ืืžืฆืขื•ืช ืกืคืจื™ื” ื™ืขื•ื“ื™ืช](#-211-fail-fast-validate-arguments-using-a-dedicated-library)
+  [2.12 ืชืžื™ื“ ื”ืžืชื™ื ื• ืœืชืฉื•ื‘ื” ืžื”ื”ื‘ื˜ื—ื•ืช ืœืคื ื™ ืฉืืชื ืžืขื‘ื™ืจื™ื ืืช ื”ืชืฉื•ื‘ื” ื”ืœืื” ื›ื“ื™ ืœื”ื™ืžื ืข ืžืžืขืงื‘ ื—ืœืงื™ `#new`](#-212-always-await-promises-before-returning-to-avoid-a-partial-stacktrace)
+ +
+ +
+ + 3. ืชื‘ื ื™ื•ืช ืงื•ื“ ื•ืกื’ื ื•ืŸ ืขื™ืฆื•ื‘ (13) + + +  [3.1 ื”ืฉืชืžืฉื• ื‘-ESLint `#strategic`](#-31-use-eslint)
+  [3.2 ื”ืฉืชืžืฉื• ื‘ืชื•ืกืคื™ื ืฉืœ Node.js ืฉืžืจื—ื™ื‘ื™ื ืืช ESLint `#updated`](#-32-use-nodejs-eslint-extension-plugins)
+  [3.3 ื”ืชื—ื™ืœื• ื‘ืœื•ืง ืฉืœ ืงื•ื“ ืขื ืกื•ื’ืจื™ื™ื ืžืกื•ืœืกืœื™ื ื‘ืื•ืชื” ื”ืฉื•ืจื”](#-33-start-a-codeblocks-curly-braces-on-the-same-line)
+  [3.4 ื”ืคืจื™ื“ื• ื‘ื™ืŸ ื”ื”ืฆื”ืจื•ืช ื”ืฉื•ื ื•ืช ื‘ืฆื•ืจื” ืชืงื ื™ืช](#-34-separate-your-statements-properly)
+  [3.5 ืชื ื• ืœืคื•ื ืงืฆื™ื” ืฉื](#-35-name-your-functions)
+  [3.6 ื”ืฉืชืžืฉื• ื‘ืžื•ืกื›ืžื•ืช ืงื‘ื•ืขื•ืช ื‘ืžืชืŸ ืฉืžื•ืช ืœืžืฉืชื ื™ื, ืœืงื‘ื•ืขื™ื, ืœืคื•ื ืงืฆื™ื•ืช ื•ืœืžื—ืœืงื•ืช](#-36-use-naming-conventions-for-variables-constants-functions-and-classes)
+  [3.7 ื”ืขื“ื™ืคื• const ืขืœ ืคื ื™ let. ื ื™ื˜ืฉื• ืืช var](#-37-prefer-const-over-let-ditch-the-var)
+  [3.8 ื˜ืขื ื• ืžื•ื“ื•ืœื™ื ื‘ืชื—ื™ืœื”, ื•ืœื ื‘ืงืจื™ืื” ืœืคื•ื ืงืฆื™ื•ืช](#-38-require-modules-first-not-inside-functions)
+  [3.9 ื”ื’ื“ื™ืจื• ื›ื ื™ืกื” ืžืกื•ื“ืจืช ืœืกืคืจื™ื” ืฉืœื›ื `#updated`](#-39-set-an-explicit-entry-point-to-a-modulefolder)
+  [3.10 ื”ืฉืชืžืฉื• ื‘ืื•ืคืจื˜ื•ืจ `===`](#-310-use-the--operator)
+  [3.11 ื”ืฉืชืžืฉื• ื‘-Async Await, ื”ืžื ืขื• ืž-callbacks `#strategic`](#-311-use-async-await-avoid-callbacks)
+  [3.12 ื”ืฉืชืžืฉื• ื‘ืคื•ื ืงืฆื™ื•ืช ื—ืฅ (=>)](#-312-use-arrow-function-expressions-)
+  [3.13 ื”ื™ืžื ืขื• ืžื”ืฉืคืขื•ืช ืฆื“ื“ื™ื•ืช ืžื—ื•ืฅ ืœืคื•ื ืงืฆื™ื•ืช `#new`](#-313-avoid-effects-outside-of-functions)
+ +
+ +
+ + 4. ื‘ื“ื™ืงื•ืช ื•ื‘ืงืจืช ืื™ื›ื•ืช (13) + + +  [4.1 ืœืคื—ื•ืช, ื›ื™ืชื‘ื• ื‘ื“ื™ืงื•ืช API ืœืจื›ื™ื‘ื™ื ื”ืฉื•ื ื™ื `#strategic`](#-41-at-the-very-least-write-api-component-testing)
+  [4.2 ืกื•ื•ื’ื• 3 ื—ืœืงื™ื ื‘ืžืชืŸ ืฉื ืœื›ืœ ื‘ื“ื™ืงื” `#new`](#-42-include-3-parts-in-each-test-name)
+  [4.3 ื—ืœืงื• ืืช ื”ื‘ื“ื™ืงื•ืช ืœืคื™ ืชื‘ื ื™ืช ื”-AAA `#strategic`](#-43-structure-tests-by-the-aaa-pattern)
+  [4.4 ื•ื•ื“ืื• ื›ื™ ื’ืจืกืช ื”-Node ืื—ื™ื“ื” `#new`](#-44-ensure-node-version-is-unified)
+  [4.5 ื”ื™ืžื ืขื• ืžืืชื—ื•ืœ ืžื™ื“ืข ื’ืจืขื™ื ื™ ืžืฉื•ืชืฃ, ื”ื’ื“ื™ืจื• ืœืคื™ ืฆื•ืจืš ืฉืœ ื‘ื“ื™ืงื” `#strategic`](#-45-avoid-global-test-fixtures-and-seeds-add-data-per-test)
+  [4.6 ืชื™ื™ื’ื• ืืช ื”ื‘ื“ื™ืงื•ืช `#advanced`](#-46-tag-your-tests)
+  [4.7 ื‘ื™ื“ืงื• ืืช ืจืžืช ื›ื™ืกื•ื™ ื”ื‘ื“ื™ืงื•ืช ืฉืœื›ื, ื–ื” ื™ืขื–ื•ืจ ืœื–ื”ื•ืช ื“ืคื•ืกื™ ื‘ื“ื™ืงื•ืช ืฉื’ื•ื™ื™ื](#-47-check-your-test-coverage-it-helps-to-identify-wrong-test-patterns)
+  [4.8 Use production-like environment for e2e testing](#-48-use-production-like-environment-for-e2e-testing)
+  [4.9 ืฉื›ืชื‘ื• ืืช ื”ืงื•ื“ ื‘ืื•ืคืŸ ืงื‘ื•ืข ื‘ืขื–ืจืช ื›ืœื™ ื ื™ืชื•ื— ืกื˜ื˜ื™](#-49-refactor-regularly-using-static-analysis-tools)
+  [4.10 ื”ื“ืžื™ื™ืช ืชืฉื•ื‘ื•ืช ืฉืœ ืฉืจืชื™ HTTP ื—ื™ืฆื•ื ื™ื™ื `#new` `#advanced`](#-410-mock-responses-of-external-http-services)
+  [4.11 ื‘ื“ืงื• ืืช ืคื•ื ืงืฆื™ื•ืช ื”ื‘ื™ื ื™ื™ื ื‘ื ืคืจื“](#-411-test-your-middlewares-in-isolation)
+  [4.12 ืงื‘ืขื• ืืช ื”ืคื•ืจื˜ ื‘ื™ื™ืฆื•ืจ, ื”ื’ื“ื™ืจื• ืืงืจืื™ ืœื‘ื“ื™ืงื•ืช `#new`](#-412-specify-a-port-in-production-randomize-in-testing)
+  [4.13 ื‘ื™ื“ืงื• ืืช ื—ืžืฉืช ื”ืชื•ืฆืื•ืช ื”ืืคืฉืจื™ื•ืช `#strategic` `#new`](#-413-test-the-five-possible-outcomes)
+ +
+ +
+ + 5. ืขืœื™ื™ื” ืœืื•ื•ื™ืจ (19) + + +  [5.1. ื ื™ื˜ื•ืจ `#strategic`](#-51-monitoring)
+  [5.2. ื”ื’ื“ื™ืœื• ืืช ื™ื›ื•ืœืช ื”ืฆืคื™ื™ื” ื‘ืขื–ืจืช ืœื•ื’ื™ื ืื™ื›ื•ืชื™ื™ื `#strategic`](#-52-increase-the-observability-using-smart-logging)
+  [5.3. ื”ืืฆื™ืœื• ื›ืœ ืžื” ืฉืืคืฉืจ (ืœื“ื•ื’ืžื” gzip, SSL) ืœืฉื™ืจื•ืช ื ืคืจื“ `#strategic`](#-53-delegate-anything-possible-eg-gzip-ssl-to-a-reverse-proxy)
+  [5.4. ืงื™ื‘ื•ืข ืชืœื•ื™ื•ืช](#-54-lock-dependencies)
+  [5.5. ื”ื‘ื˜ื™ื—ื• ืืช ื–ืžื™ื ื•ืช ื”ืžืขืจื›ืช ื‘ืขื–ืจืช ื”ื›ืœื™ ื”ืžืชืื™ื](#-55-guard-process-uptime-using-the-right-tool)
+  [5.6. ื”ืฉืชืžืฉื• ื‘ื›ืœ ืžืขื‘ื“ื™ ื”-CPU](#-56-utilize-all-cpu-cores)
+  [5.7. ืชื™ืฆืจื• โ€˜maintenance endpointโ€™](#-57-create-a-maintenance-endpoint)
+  [5.8. ื’ืœื• ืืช ื”ืœื ื™ื“ื•ืข ื‘ืขื–ืจืช ืžื•ืฆืจื™ APM `#advanced` `#updated`](#-58-discover-the-unknowns-using-apm-products)
+  [5.9. ื›ืชื‘ื• ืืช ื”ืงื•ื“ ืžื•ืชืื ืœื”ืชืงื ื”](#-59-make-your-code-production-ready)
+  [5.10. ืžื“ื“ื• ื•ืฉื™ืžืจื• ืืช ื ื™ืฆื•ืœ ื”ื–ื™ื›ืจื•ืŸ `#advanced`](#-510-measure-and-guard-the-memory-usage)
+  [5.11. Get your frontend assets out of Node](#-511-get-your-frontend-assets-out-of-node)
+  [5.12. Strive to be stateless `#strategic`](#-512-strive-to-be-stateless)
+  [5.13. Use tools that automatically detect vulnerabilities](#-513-use-tools-that-automatically-detect-vulnerabilities)
+  [5.14. Assign a transaction id to each log statement `#advanced`](#-514-assign-a-transaction-id-to-each-log-statement)
+  [5.15. Set NODE_ENV=production](#-515-set-node_envproduction)
+  [5.16. Design automated, atomic and zero-downtime deployments `#advanced`](#-516-design-automated-atomic-and-zero-downtime-deployments)
+  [5.17. Use an LTS release of Node.js](#-517-use-an-lts-release-of-nodejs)
+  [5.18. Log to stdout, avoid specifying log destination within the app](#-518-log-to-stdout-avoid-specifying-log-destination-within-the-app)
+  [5.19. Install your packages with npm ci `#new`](#-519-install-your-packages-with-npm-ci)
+ +
+ +
+ + 6. ืื‘ื˜ื—ื” (27) + + +  [6.1. Embrace linter security rules](#-61-embrace-linter-security-rules)
+  [6.2. Limit concurrent requests using a middleware](#-62-limit-concurrent-requests-using-a-middleware)
+  [6.3 Extract secrets from config files or use packages to encrypt them `#strategic`](#-63-extract-secrets-from-config-files-or-use-packages-to-encrypt-them)
+  [6.4. Prevent query injection vulnerabilities with ORM/ODM libraries `#strategic`](#-64-prevent-query-injection-vulnerabilities-with-ormodm-libraries)
+  [6.5. Collection of generic security best practices](#-65-collection-of-generic-security-best-practices)
+  [6.6. Adjust the HTTP response headers for enhanced security](#-66-adjust-the-http-response-headers-for-enhanced-security)
+  [6.7. Constantly and automatically inspect for vulnerable dependencies `#strategic`](#-67-constantly-and-automatically-inspect-for-vulnerable-dependencies)
+  [6.8. Protect Users' Passwords/Secrets using bcrypt or scrypt `#strategic`](#-68-protect-users-passwordssecrets-using-bcrypt-or-scrypt)
+  [6.9. Escape HTML, JS and CSS output](#-69-escape-html-js-and-css-output)
+  [6.10. Validate incoming JSON schemas `#strategic`](#-610-validate-incoming-json-schemas)
+  [6.11. Support blocklisting JWTs](#-611-support-blocklisting-jwts)
+  [6.12. Prevent brute-force attacks against authorization `#advanced`](#-612-prevent-brute-force-attacks-against-authorization)
+  [6.13. Run Node.js as non-root user](#-613-run-nodejs-as-non-root-user)
+  [6.14. Limit payload size using a reverse-proxy or a middleware](#-614-limit-payload-size-using-a-reverse-proxy-or-a-middleware)
+  [6.15. Avoid JavaScript eval statements](#-615-avoid-javascript-eval-statements)
+  [6.16. Prevent evil RegEx from overloading your single thread execution](#-616-prevent-evil-regex-from-overloading-your-single-thread-execution)
+  [6.17. Avoid module loading using a variable](#-617-avoid-module-loading-using-a-variable)
+  [6.18. Run unsafe code in a sandbox](#-618-run-unsafe-code-in-a-sandbox)
+  [6.19. Take extra care when working with child processes `#advanced`](#-619-take-extra-care-when-working-with-child-processes)
+  [6.20. Hide error details from clients](#-620-hide-error-details-from-clients)
+  [6.21. Configure 2FA for npm or Yarn `#strategic`](#-621-configure-2fa-for-npm-or-yarn)
+  [6.22. Modify session middleware settings](#-622-modify-session-middleware-settings)
+  [6.23. Avoid DOS attacks by explicitly setting when a process should crash `#advanced`](#-623-avoid-dos-attacks-by-explicitly-setting-when-a-process-should-crash)
+  [6.24. Prevent unsafe redirects](#-624-prevent-unsafe-redirects)
+  [6.25. Avoid publishing secrets to the npm registry](#-625-avoid-publishing-secrets-to-the-npm-registry)
+  [6.26. 6.26 Inspect for outdated packages](#-626-inspect-for-outdated-packages)
+  [6.27. Import built-in modules using the 'node:' protocol `#new`](#-627-import-built-in-modules-using-the-node-protocol)
+ +
+ +
+ + 7. ื‘ื™ืฆื•ืขื™ื (2) (ื‘ืชื”ืœื™ืš โœ๏ธ) + + +  [7.1. Don't block the event loop](#-71-dont-block-the-event-loop)
+  [7.2. Prefer native JS methods over user-land utils like Lodash](#-72-prefer-native-js-methods-over-user-land-utils-like-lodash)
+ +
+ +
+ + 8. ื“ื•ืงืจ (15) + + +  [8.1 Use multi-stage builds for leaner and more secure Docker images `#strategic`](#-81-use-multi-stage-builds-for-leaner-and-more-secure-docker-images)
+  [8.2. Bootstrap using node command, avoid npm start](#-82-bootstrap-using-node-command-avoid-npm-start)
+  [8.3. Let the Docker runtime handle replication and uptime `#strategic`](#-83-let-the-docker-runtime-handle-replication-and-uptime)
+  [8.4. Use .dockerignore to prevent leaking secrets](#-84-use-dockerignore-to-prevent-leaking-secrets)
+  [8.5. Clean-up dependencies before production](#-85-clean-up-dependencies-before-production)
+  [8.6. Shutdown smartly and gracefully `#advanced`](#-86-shutdown-smartly-and-gracefully)
+  [8.7. Set memory limits using both Docker and v8 `#advanced` `#strategic`](#-87-set-memory-limits-using-both-docker-and-v8)
+  [8.8. Plan for efficient caching](#-88-plan-for-efficient-caching)
+  [8.9. Use explicit image reference, avoid latest tag](#-89-use-explicit-image-reference-avoid-latest-tag)
+  [8.10. Prefer smaller Docker base images](#-810-prefer-smaller-docker-base-images)
+  [8.11. Clean-out build-time secrets, avoid secrets in args `#strategic #new`](#-811-clean-out-build-time-secrets-avoid-secrets-in-args)
+  [8.12. Scan images for multi layers of vulnerabilities `#advanced`](#-812-scan-images-for-multi-layers-of-vulnerabilities)
+  [8.13 Clean NODE_MODULE cache](#-813-clean-node_module-cache)
+  [8.14. Generic Docker practices](#-814-generic-docker-practices)
+  [8.15. Lint your Dockerfile `#new`](#-815-lint-your-dockerfile)
+ +
+ +

+ +# `1. ืžื‘ื ื” ื”ืคืจื•ื™ื™ืงื˜` + +## ![โœ”] 1.1 ื‘ื ื• ืืช ื”ืคืจื•ื™ื™ืงื˜ ืœืคื™ ืจื›ื™ื‘ื™ื ืขืกืงื™ื™ื + +**ืืž;ืœืง:** ื‘ืกื™ืก ื”ืžืขืจื›ืช ืฆืจื™ืš ืœื›ืœื•ืœ ืชื™ืงื™ื•ืช ืื• ืžืื’ืจื™ื ืฉืžื™ื™ืฆื’ ื‘ืฆื•ืจื” ื”ื’ื™ื•ื ื™ืช ืืช ื”ืžื™ื“ื•ืœ ื”ืขืกืงื™. ื›ืœ ืจื›ื™ื‘ ืžื™ื™ืฆื’ ืชื—ื•ื ืžื•ืฆืจ (ื›ืœื•ืžืจ ื”ืงืฉืจ ืžื•ื’ื‘ืœ), ืœืžืฉืœ 'ืžืฉืชืžืฉื™ื', 'ื”ื–ืžื ื•ืช', ื•ื›ื•ืœื™... ื›ืœ ืจื›ื™ื‘ ืžื›ื™ืœ ืืช ื” API, ืœื•ื’ื™ืงื” ื•ืžืกื“ ื”ื ืชื•ื ื™ื ืฉืœื•. ืžื” ื”ืžื˜ืจื” ืฉืœ ื–ื”? ื›ืืฉืจ ื™ืฉ ืกื‘ื™ื‘ื” ืขืฆืžืื™ืช ื›ืœ ืฉื™ื ื•ื™ ืžืฉืคื™ืข ืืš ื•ืจืง ืขืœ ื”ื—ืœืง ื”ืจืœื•ื•ื ื˜ื™ - ื”ืขื•ืžืก ื”ื ืคืฉื™, ืกื™ื‘ื•ื›ื™ื•ืช ื”ืคื™ืชื•ื— ื•ื”ื—ืฉืฉ ืžืคืจื™ืกื” ื—ื“ืฉื” ืฉืœ ื”ืจื›ื™ื‘ ื”ืจื‘ื” ื™ื•ืชืจ ืงื˜ืŸ. ื›ืชื•ืฆืื” ืžื›ืš, ืžืชื›ื ืชื™ื ื™ื›ื•ืœื™ื ืœืคืชื— ื”ืจื‘ื” ื™ื•ืชืจ ืžื”ืจ. ื–ื” ืœื ื“ื•ืจืฉ ื‘ื”ื›ืจื— ื”ืคืจื“ื” ืคื™ื–ื™ืช ื•ื™ื›ื•ืœ ืœื”ื™ื•ืช ืžื•ืฉื’ ื’ื ื‘Monorepo ืื• multi-repo. + +```bash +my-system +โ”œโ”€ apps (components) +โ”‚ โ”œโ”€ orders +โ”‚ โ”œโ”€ users +โ”‚ โ”œโ”€ payments +โ”œโ”€ libraries (generic cross-component functionality) +โ”‚ โ”œโ”€ logger +โ”‚ โ”œโ”€ authenticator +``` + +**ืื—ืจืช:** ื›ืืฉืจ ืžื•ืฆืจื™ื ืฉืœ ืžื•ื“ื•ืœื™ื ืื• ื ื•ืฉืื™ื ืฉื•ื ื™ื ืžืขื•ืจื‘ื‘ื™ื ื™ื—ื“, ื™ืฉื ื• ืกื™ื›ื•ื™ ื’ื‘ื•ื” ืฉืชื™ื•ื•ืฆืจ ืžืขืจื›ืช ืกืคื’ื˜ื™ ื‘ืขืœืช ืฆื™ืžื•ื“ ื’ื‘ื•ื”. ืœื“ื•ื’ืžื”, ื‘ืืจื›ื™ื˜ืงื˜ื•ืจื” ืฉื‘ื” 'ืžื•ื“ื•ืœ ื`' ืงื•ืจื ืœืฉื™ืจื•ืช ืž'ืžื•ื“ื•ืœ ื‘;', ืื™ืŸ ื”ืคืจื“ื” ื‘ืจื•ืจื”ื‘ื™ืŸ ื”ืžื•ื“ื•ืœื™ื ื”ืฉื•ื ื™ื - ื›ืœ ืฉื™ื ื•ื™ ื‘ืงื•ื“ ืขืœื•ืœ ืœื”ืฉืคื™ืข ืขืœ ืžืฉื”ื• ืื—ืจ. ืขื ื”ื’ื™ืฉื” ื”ื–ืืช , ืžืชื›ื ืชื™ื ืฉืฆืจื™ื›ื™ื ืœื”ื•ืกื™ืฃ ืžื•ืฆืจ ื—ื“ืฉ ืœืžืขืจื›ืช ื™ืื‘ืงื• ื‘ื” ื‘ื”ื‘ื ื” ืขืœ ืžื” ื”ืฉื™ื ื•ื™ ืฉืœื”ื ื™ื›ื•ืœ ืœื”ืฉืคื™ืข. ื›ืชื•ืฆืื” ืžื›ืš, ื”ื ื—ืฉืฉื• ืœืฉื‘ื•ืจ ืžื•ื“ื•ืœื™ื ืื—ืจื™ื, ื•ื›ืœ ืคืจื™ืกื” ื ื”ื™ื™ืชื” ืื™ื˜ื™ืช ื™ื•ืชืจ ื•ืžืกื•ื›ื ืช ื™ื•ืชืจ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื‘ื ื™ื™ื” ืœืคื™ ืจื›ื™ื‘ื™ื**](./sections/projectstructre/breakintcomponents.md) + +

+ +## ![โœ”] 1.2 ื—ืœื•ืงืช ื”ืจื›ื™ื‘ื™ื ืœ3 ืฉื›ื‘ื•ืช, ืฉืžื™ืจื” ืขืœ ืฉื›ื‘ืช ื”ื•ื•ื‘ ื‘ื’ื‘ื•ืœื•ืชื™ื” + +**ืืž;ืœืง:** ื›ืœ ืจื›ื™ื‘ ืฆืจื™ืš ืœื›ืœื•ืœ 'ืฉื›ื‘ื•ืช' - ืชื™ืงื™ื™ื” ื™ืขื•ื“ื™ืช ืขื ืื—ืจื™ื•ืช ืžืฉื•ืชืคืช: 'entry-point' ืื™ืคื” ืฉื—ืœืงื™ ื”ืฉืœื™ื˜ื” ื ืžืฆืื™ื, 'domain' ืื™ืคื” ืฉื”ืœื•ื’ื™ืงื” ื ืžืฆืืช ื• 'data-access'. ื”ืขื™ืงืจื•ืŸ ื”ืžื ื—ื” ืฉืœ ื”ืืจื›ื™ื˜ืงื˜ื•ืจื•ืช ื”ืžื•ื‘ื™ืœื•ืช ื‘ืฉื•ืง ื”ื•ื ืœื”ืคืจื™ื“ ืืช ื”ืื—ืจื™ื•ืช ื”ื˜ื›ื ื™ืช (ืœืžืฉืœ: HTTP, DB ื•ืขื•ื“) ืžื”ืœื•ื’ื™ืงื” ื”ื™ืขื•ื“ื™ืช ืฉืœ ื”ืžื•ืฆืจ ื›ืš ืฉื”ืžืชื›ื ืชื™ื ื™ื•ื›ืœื• ืœืงื•ื“ื“ ื™ื•ืชืจ ืชื›ื•ืœื•ืช ื‘ืœื™ ืœื“ืื•ื’ ืœื’ื‘ื™ ื ื™ื”ื•ืœ ืชืฉืชื™ื•ืช. ื”ืฉืžื” ืฉืœ ื›ืœ ืฉื›ื‘ื” ื‘ืชื™ืงื™ื™ื” ื™ืขื•ื“ื™ืช, ืฉื™ื“ื•ืขื” ื’ื ื›-[ืžื•ื“ืœ 3 ื”ืฉื›ื‘ื•ืช](https://he.wikipedia.org/wiki/%D7%90%D7%A8%D7%9B%D7%99%D7%98%D7%A7%D7%98%D7%95%D7%A8%D7%94_%D7%A8%D7%91-%D7%A9%D7%9B%D7%91%D7%AA%D7%99%D7%AA#%D7%90%D7%A8%D7%9B%D7%99%D7%98%D7%A7%D7%98%D7%95%D7%A8%D7%AA_%D7%A9%D7%9C%D7%95%D7%A9_%D7%A9%D7%9B%D7%91%D7%95%D7%AA) ([ื‘ืื ื’ืœื™ืช](https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture)) ื–ืืช ื”ื“ืจืš _ื”ืคืฉื•ื˜ื”_ ืœื”ืฉื™ื’ ืืช ื”ืžื˜ืจื”. + +```bash +my-system +โ”œโ”€ apps (components) +โ”‚ โ”œโ”€ component-a + โ”‚ โ”œโ”€ entry-points + โ”‚ โ”‚ โ”œโ”€ api # controller comes here + โ”‚ โ”‚ โ”œโ”€ message-queue # message consumer comes here + โ”‚ โ”œโ”€ domain # features and flows: DTO, services, logic + โ”‚ โ”œโ”€ data-access # DB calls w/o ORM +``` + +**ืื—ืจืช:** ืœืขืชื™ื ื“ื—ื•ืคื•ืช ื ืชืงืœื™ื ื‘ื›ืš ืฉื”ืžืชื›ื ืชื™ื ืžืขื‘ื™ืจื™ื ืื•ื‘ื™ื™ืงื˜ื™ ืชืงืฉื•ืจืช ื›ื“ื•ื’ืžืช request/reqponse ืœืคื•ื ืงืฆื™ื•ืช ื‘ืฉื›ื‘ื•ืช ืฉืœ ื”ืœื•ื’ื™ืงื” ืื• ื ื™ื”ื•ืœ ื”ืžื™ื“ืข - ื“ื‘ืจ ื–ื” ืคื•ื’ืข ื‘ืขื™ืงืจื•ืŸ ื”ื”ืคืจื“ื” ื•ื’ื•ืจื ืœื›ืš ืฉื‘ืขืชื™ื“ ื™ื”ื™ื” ืงืฉื” ื™ื•ืชืจ ืœื”ื ื’ื™ืฉ ืืช ื”ืœื•ื’ื™ืงื” ืœืกื•ื’ื™ ืงืœื™ื ื˜ื™ื ืื—ืจื™ื ื›ื“ื•ื’ืžืช: ื‘ื“ื™ืงื•ืช ื™ื—ื™ื“ื”, ืžืฉื™ืžื•ืช ืžืชื•ื–ืžื ื•ืช ื•message queues. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื—ืœืง ืืช ื”ืžื•ืฆืจ ืœืฉื›ื‘ื•ืช**](./sections/projectstructre/createlayers.md) + +

+ +## ![โœ”] 1.3 ืขื˜ืคื• ื›ืœื™ื ืžืฉื•ืชืคื™ื ื‘ื—ื‘ื™ืœื•ืช, ืฉืงืœื• ืืช ื”ืคืฆืชื + +**ืืž;ืœืง:** ืžืงืžื• ืืช ื›ืœ ื”ื›ืœื™ื ืฉืืคืฉืจ ืœืฉืชืฃ ืื•ืชื ื‘ืชื™ืงื™ื™ื” ื™ื™ืขื•ื“ื™ืช, ืœืžืฉืœ 'libraries' ื•ื›ืœ ื›ืœื™ ื‘ืชื™ืงื™ื™ื” ืคื ื™ืžื™ืช ื ืคืจื“ืช, ืœืžืฉืœ '/libraries/logger'. ื”ืคื›ื• ืืช ื”ื›ืœื™ ืœื—ื‘ื™ืœื” ื‘ืœืชื™ ืชืœื•ื™ื” ืขื ืงื•ื‘ืฅ ื” package.json ืฉืœื• ื•ื–ืืช ื›ื“ื™ ืœื”ื’ื“ื™ืœ ืืช ื”ื›ื™ืžื•ืก (encapsulation), ื•ืืคืฉืจื• ื”ืคืฆื” ืขืชื™ื“ื™ืช ืœืžืื’ืจ. ื›ืืฉืจ ื”ืคืจื•ื™ื™ืงื˜ ืฉืœื›ื ื‘ื ื•ื™ ื‘ืชืฆื•ืจืช monorepo, ื›ืœื™ื ืืœื• ื™ื›ื•ืœื™ื ืœื”ื™ื•ืช ืžื•ื’ื“ืจื™ื ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ 'npm linking' ืœื›ืชื•ื‘ืช ื”ืคื™ื–ื™ืช ืฉืœื”ื ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ ts-paths ืื• ืขืœ ื™ื“ื™ ื”ืคืฆื” ื•ื”ืชืงื ื” ืขืœ ื™ื“ื™ืžื ื”ืœ ื—ื‘ื™ืœื•ืช ื›ื“ื•ื’ืžืช 'npm registry'. + +```bash +my-system +โ”œโ”€ apps (components) + โ”‚ โ”œโ”€ component-a +โ”œโ”€ libraries (generic cross-component functionality) +โ”‚ โ”œโ”€ logger +โ”‚ โ”‚ โ”œโ”€ package.json +โ”‚ โ”‚ โ”œโ”€ src +โ”‚ โ”‚ โ”‚ โ”œโ”€ index.js + +``` + +**ืื—ืจืช:** ืฆืจื›ื ื™ื ืฉืœ ื›ืœื™ ื™ื”ื™ื• ืฆืžื•ื“ื™ื ืœืคื•ื ืงืฆื™ื•ื ืœื™ื•ืช ื”ืคื ื™ืžื™ืช ืฉืœื•. ืขืœ ื™ื“ื™ ื”ื’ื“ืจื” ืฉืœ package.json ื‘ืฉื•ืจืฉ ื”ื›ืœื™ ืžื™ืฉื”ื• ื™ื›ื•ืœ ืœื”ื’ื“ื™ืจ ืงื•ื‘ืฅ package.json.main ืื• package.json.exports ื›ื“ื™ ืœื”ืฆื”ื™ืจ ื‘ืžืคื•ืจืฉ ืื™ืœื• ืงื‘ืฆื™ื ื•ืคื•ื ืงืฆื™ื•ืœื ื™ื•ืช ื”ื™ื ื—ืœืง ืžื”ื—ืœืงื™ื ื”ื ื’ื™ืฉื™ื ืฉืœ ื”ื›ืœื™. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื‘ื ื™ื™ื” ืœืคื™ ืชื›ื•ื ื”**](./sections/projectstructre/wraputilities.md) + +

+ +## ![โœ”] 1.4 ื”ืฉืชืžืฉื• ื‘ืงื•ื ืคื™ื’ื•ืจืฆื™ื” ืขื ืžืฉืชื ื™ ืกื‘ื™ื‘ื” ื‘ืื•ืคืŸ ืžื•ื“ืข, ืžืื•ื‘ื˜ื— ื•ื”ื™ืจืจื›ื™ + +**ืืž;ืœืง:** ื”ื’ื“ืจืช ืกื‘ื™ื‘ื” ืžื•ืฉืœืžืช ืฆืจื™ื›ื” ืœื”ื‘ื˜ื™ื— ื›ื™ (ื) ืฉืžื•ืช ืžืฉืชื ื™ื ื™ื›ื•ืœื™ื ืœื”ื™ืงืจื ืžืงื‘ืฆื™ื ื›ืžื• ื’ื ืžืžืฉืชื ื™ ืกื‘ื™ื‘ื” (ื‘) ืกื•ื“ื•ืช ื ืฉืžืจื™ื ืžื—ื•ืฅ ืœืงื•ื“ ืฉืฉื™ื™ืš ืœืžืื’ืจ (ื’) ื”ืงื•ื ืคื™ื’ื•ืจืฆื™ื” ื”ื™ื ื”ื™ืจืจื›ื™ืช ืœืฆื•ืจืš ื—ื™ืคื•ืฉ ืงืœ ื™ื•ืชืจ (ื“) ืชืžื™ื›ื” ื‘ืกื•ื’ื™ื ืฉื•ื ื™ื ืฉืœ ืžืฉืชื ื™ื (ื”) ื•ื™ื“ื•ื ืžื•ืงื“ื ืฉืœ ืžืฉืชื ื™ื ืœื ืชืงื™ื ื™ื (ื•) ื”ื’ื“ืจืช ื‘ืจื™ืจืช ืžื—ื“ืœ ืœื›ืœ ืฉื“ื”. ื™ืฉื ืŸ ืžืกืคืจ ืกืคืจื™ื•ืช ืฉืขื•ื ื•ืช ืขืœ ืจื•ื‘ ื”ื“ืจื™ืฉื•ืช ื”ืœืœื• ื›ืžื• [convict](https://www.npmjs.com/package/convict), [env-var](env-var), [zod](https://github.com/colinhacks/zod), ื•ืขื•ื“... + +**ืื—ืจืช:** ื ื ื™ื— ื•ื™ืฉื ื• ืžืฉืชื ื” ืกื‘ื™ื‘ื” ื”ื›ืจื—ื™ ืฉืœื ื”ื•ื’ื“ืจ, ื”ืžืขืจื›ืช ืชืชื—ื™ืœ ืœืจื•ืฅ ื‘ื”ืฆืœื—ื”, ืชืขื ื” ืœื‘ืงืฉื•ืช, ื—ืœืง ืžื”ืžื™ื“ืข ื™ืขื•ื“ื›ืŸ ื‘ืžืกื“ ื”ื ืชื•ื ื™ื, ื•ืœืคืชืข ื™ื”ื™ื” ื—ืกืจ ืœื” ืฉื“ื” ื”ื›ืจื—ื™ ืœื”ืžืฉืš ื”ืชื”ืœื™ืš ื•ืฉื‘ืœืขื“ื™ื• ื”ื™ื ืœื ื™ื›ื•ืœื” ืœืกื™ื™ื ืืช ื”ืคืขื•ืœื”, ืžื” ืฉื™ื™ืฆื•ืจ ืžืขืจื›ืช ื‘ืžืฆื‘ "ืžืœื•ื›ืœืš". + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื™ื˜ื•ืช ืขื‘ื•ื“ื” ืฉืœ ืงื•ื ืคื™ื’ื•ืจืฆื™ื”**](./sections/projectstructre/configguide.md) + +

+ +## ![โœ”] 1.5 ืฉืงืœื• ืืช ื›ืœ ื”ื”ืฉืœื›ื•ืช ื‘ืขืช ื‘ื—ื™ืจืช ืžืกื’ืจืช + +**ืืž;ืœืง:** ื›ืืฉืจ ื‘ื•ื ื™ื ืืคืœื™ืงืฆื™ื•ืช ื• API-ื™ื, ืฉื™ืžื•ืฉ ื‘ืคืจื™ื™ืžื•ื•ืจืง ื”ื•ื ื—ื•ื‘ื”. ืงืœ ืœื”ืชืขืœื ืžื”ืืคืฉืจื•ื™ื•ืช ื”ืฉื•ื ื•ืช ืฉืงื™ื™ืžื•ืช ื•ืžืฉื™ืงื•ืœื™ื ื—ืฉื•ื‘ื™ื ื•ื‘ืกื•ืคื• ืฉืœ ื“ื‘ืจ ืœื”ืฉืชืžืฉ ื‘ืืคืฉืจื•ืช ืฉืคื—ื•ืช ืชื•ืืžืช ืœื“ืจื™ืฉื•ืช ืฉืœ ื”ืžื•ืฆืจ. ื ื›ื•ืŸ ืœ2023/2024 ืื ื• ืžืืžื™ื ื™ื ื›ื™ ืืจื‘ืขืช ื”ืคืจื™ื™ืžื•ื•ืจืงื™ื ื”ืœืœื• ื”ื ื”ื›ื“ืื™ื™ื ื‘ื™ื•ืชืจ ืœื”ืฉื•ื•ืื”: [Nest.js](https://nestjs.com/), [Fastify](https://www.fastify.io/), [express](https://expressjs.com/), ื• [Koa](https://koajs.com/). ืœื—ืฆื• ืขืœ ืœืงืจื™ืื” ื ื•ืกืคืช ื‘ื”ืžืฉืš ื›ื“ื™ ืœืงืจื•ื ืคืจื˜ื™ื ื ื•ืกืคื™ื ื‘ืขื“ ื•ื ื’ื“ ื›ืœ ืื—ืช ืžื”ืืคืฉืจื•ื™ื•ืช. ื‘ืื•ืคืŸ ืคืฉื˜ื ื™, ืื ื• ืžืืžื™ื ื™ื ื›ื™ Node.js ื–ืืช ื”ื”ืชืืžื” ื”ื›ื™ ื˜ื•ื‘ื” ืœืฆื•ื•ืชื™ื ืฉืจื•ืฆื™ื ืœืขื‘ื•ื“ ื‘ืฉื™ื˜ืช OOP ืื• ืœื‘ื ื•ืช ืžื•ืฆืจื™ื ืฉืžื™ื•ืขื“ื™ื ืœื’ื“ื•ืœ ื‘ืฆื•ืจื” ื ื™ื›ืจืช ื•ืื™ ืืคืฉืจ ืœื—ืœืง ืื•ืชื ืœืจื›ื™ื‘ื™ื ืงื˜ื ื™ื _ื•ืขืฆืžืื™ื™ื_. ื”ื”ืžืœืฆื” ืฉืœื ื• ื”ื™ื Fastify ืขื‘ื•ืจ ืžืขืจื›ื•ืช ื‘ื’ื•ื“ืœ ืกื‘ื™ืจents (ื›ืžื• Microservices) ืฉืžื•ืฉืชืชื™ื ืขืœ ืขืงืจื•ื ื•ืช ืคืฉื•ื˜ื™ื ืฉืœ Node.js. + +**ืื—ืจืช:** ื‘ืฉืœ ื”ื›ืžื•ืช ื”ืขืฆื•ืžื” ืฉืœ ื”ืฉื™ืงื•ืœื™ื, ืงืœ ืœืงื‘ืœ ื”ื—ืœื˜ื” ืขืœ ื‘ืกื™ืก ืžื™ื“ืข ื—ืœืงื™ ื•ืœื”ืฉื•ื•ืช ืชืคื•ื—ื™ื ืœืชืคื•ื–ื™ื. ืœืžืฉืœ, ื™ืฉื ื” ื”ื ื—ื” ืจื•ื•ื—ืช ืฉFastify ื”ื•ื web-server ืžื™ื ื™ืžืœื™ ืฉืจืื•ื™ ืœื”ืฉื•ื•ืช ืœexpress ื‘ืœื‘ื“. ื‘ืคื•ืขืœ, ื–ื”ื• ืคืจื™ื™ืžื•ื•ืจืง ืขืฉื™ืจ ืขื ื”ืจื‘ื” ื”ืจื—ื‘ื•ืช ืจืฉืžื™ื•ืช ืฉืžื›ืกื•ืช ื”ืจื‘ื” ืฆืจื›ื™ื. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื‘ื—ื™ืจืช ื”ืคืจื™ื™ืžื•ื•ืจืง ื”ื ื›ื•ืŸ**](./sections/projectstructre/choose-framework.md) + +## ![โœ”] 1.6 ื”ืฉืชืžืฉื• ื‘-TypeScript ื‘ืžื™ื“ืชื™ื•ืช ื•ื‘ืฆื•ืจื” ืžื•ืฉื›ืœืช + +**ืืž;ืœืง:** ืงื™ื“ื•ื“ ืœืœื ืžืงื“ืžื™ ื‘ื˜ื™ื—ื•ืช ืฉืœ ืกื™ื•ื•ื’ ืžืฉืชื ื™ื ื”ื•ื ื›ื‘ืจ ืœื ืืคืฉืจื•ืช ื‘ืช ืงื™ื™ืžื, TypeScript ืžื”ื•ื•ื” ืืช ื”ืืคืฉืจื•ืช ื”ืคื•ืคื•ืœืจื™ืช ื‘ื™ื•ืชืจ ืœืžืฉื™ืžื” ื–ื•. ืžืฉืชืžืฉื™ื ื‘ื” ืœื”ื’ื“ืจืช ืกื•ื’ื™ ืžืฉืชื ื™ื ื•ืขืจื›ื™ ื”ื—ื–ืจื” ืฉืœ ืคื•ื ืงืฆื™ื•ืช. ืขื ื–ืืช, ื–ื•ื”ื™ ื—ืจื‘ ืคื™ืคื™ื•ืช ืฉื™ื›ื•ืœื” ื‘ืงืœื•ืช ืœื™ืฆื•ืจ ืžื•ืจื›ื‘ื•ืช ื‘ืฉืœ ื‘ืกื‘ื™ื‘ื•ืช 50 ืžื™ืœื•ืช ืžืคืชื— ื ื•ืกืคื•ืช ืฉื™ืฉ ืœื” ื•ืชื›ื•ื ื•ืช ืžืชื•ื—ื›ืžื•ืช ืฉืฆืจื™ืš ืœื“ืขืช ืœื”ืฉืชืžืฉ ื‘ื”ืŸ. ืฉื™ืžื•ืฉ ื‘ื” ืฆืจื™ืš ืœื”ื™ืขืฉื•ืช ื‘ืžื™ื“ื”, ื‘ืขื“ื™ืคื•ืช ืœื”ื’ื“ืจื•ืช ืคืฉื•ื˜ื•ืช ืฉืœ ืžืฉืชื ื™ื, ื•ืฉื™ืžื•ืฉ ื‘ื™ื›ื•ืœื•ืช ืžืชืงื“ืžื•ืช ืจืง ื›ืืฉืจ ืฆื•ืจืš ื”ื›ืจื—ื™ ืžื•ืคื™ืข. + +**ืื—ืจืช:** [ืžื—ืงืจื™ื](https://earlbarr.com/publications/typestudy.pdf) ืžืจืื™ื ื›ื™ ืฉื™ืžื•ืฉ ื‘-TypeScript ื™ื›ื•ืœ ืœืขื–ื•ืจ ื‘ื–ื™ื”ื•ื™ ื›20% ืžื”ื‘ืื’ื™ื ื‘ืฉืœื‘ื™ื ืžื•ืงื“ืžื™ื ื™ื•ืชืจ. ืœืœื TypeScript ื—ื•ื•ื™ืช ื”ืคื™ืชื•ื— ื‘ IDE ื ื”ื™ื™ืช ื‘ืœืชื™ ื ืกื‘ืœืช. ืžื”ืฆื“ ื”ืฉื ื™, 80% ืžื”ื‘ืื’ื™ื ื”ื™ื ืœื ืขื•ื–ืจืช ืœื–ื”ื•ืช. ื›ืชื•ืฆืื” ืžื›ืš, ืฉื™ืžื•ืฉ ื‘TypeScript ืžื•ืกื™ืฃ ืขืจืš ืžื•ื’ื‘ืœ. ืจืง ื”ื•ืกืคื” ืฉืœ ื‘ื“ื™ืงื•ืช ืื™ื›ื•ืชื™ื•ืช ื™ื›ื•ืœื” ืœืขื–ื•ืจ ืœื–ื”ื•ืช ืืช ืžื’ื•ื•ืŸ ื”ื‘ืื’ื™ื ื”ืจื—ื‘, ื›ื•ืœืœ ื›ืืœื• ืฉื ื’ืจืžื™ื ืžืืคื™ื•ืŸ ืœื ืชืงื™ืŸ ืฉืœ ืกื•ื’ ื”ืžืฉืชื ื”. ืฉื™ืžื•ืฉ ืœื ื˜ื•ื‘ ื’ื ืขืœื•ืœ ืœื”ืจื•ื’ ืืช ื”ืžื˜ืจื”, ืชื›ื•ื ื•ืช ืžื•ืจื›ื‘ื•ืช ืฉืœ ืงื•ื“ ืžืขืœื•ืช ืืชืžื•ืจื›ื‘ื•ืช ื”ืงื•ื“ ืžื” ืฉื‘ืื•ืคืŸ ื™ืฉื™ืจ ืžืขืœื” ืืช ืžืกืคืจ ื”ื‘ืื’ื™ื ื•ื–ืžืŸ ื”ืชื™ืงื•ืŸ ืฉืœ ื›ืœ ื‘ืื’. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื™ืงื•ืœื™ื ืœืฉื™ืžื•ืฉ ื‘-TypeScript**](./sections/projectstructre/typescript-considerations.md) + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# `2. ื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช` + +## ![โœ”] 2.1 ื”ืฉืชืžืฉื• ื‘ Async-Await ืื• ื”ื‘ื˜ื—ื•ืช ืœื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช ืืกื™ื ื›ืจื•ื ื™ื•ืช + +**ืืž;ืœืง:** ื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช ืืกื™ื ื›ืจื•ื ื™ื•ืช ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘-callbacks ื–ื• ื”ื“ืจืš ื”ืžื”ื™ืจื” ืœื’ื”ื™ื ื•ื (ื”ื™ื“ื•ืขื” ื‘ืฉื [ืคื™ืจืžื™ื“ืช ื“ื•ื](https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming))). ื”ืžืชื ื” ื”ื˜ื•ื‘ื” ื‘ื™ื•ืชืจ ืฉืืคืฉืจ ืœืชืช ืœืงื•ื“ ื”ื•ื ืฉื™ืžื•ืฉ ื‘-promises ื‘ืกื’ื ื•ืŸ async-await ื“ื‘ืจ ืฉืžืืคืฉืจ ืงื•ื“ ื”ืจื‘ื” ื™ื•ืชืจ ื ืงื™ ื•ืžืกื•ื“ืจ ื•ืกื™ื ื˜ืงืก ื“ื•ืžื” ืœ try-catch. + +**ืื—ืจืช:** ืกื’ื ื•ืŸ ื”ื›ืชื™ื‘ื” `function(err, response)` ื”ื›ื•ืœืœ ืฉื™ืžื•ืฉ ื‘-callbacks ืฉืœ Node.js, ืกื•ืœืœ ื“ืจืš ื‘ื˜ื•ื—ื” ืœืงื•ื“ ืฉืื™ ืืคืฉืจ ืœืชื—ื–ืง ื‘ืฉืœ ื”ืขืจื‘ื•ื‘ ื‘ื™ืŸ ื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช ืœื ื™ื”ื•ืœ ื”ืชื”ืœื™ืš ื”ืชืงื ื™ ืฉืœ ื”ืžืขืจื›ืช, ืขื ืงื™ื ื•ืŸ ืžื•ื’ื–ื ื•ืกื’ื ื•ืŸ ืงื•ื“ ืžื•ื–ืจ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื™ืžื ืขื•ืช ืž-callbacks**](./sections/errorhandling/asyncerrorhandling.md) + +

+ +## ![โœ”] 2.2 ื”ืจื—ื™ื‘ื• ืืช ืžื‘ื ื” ืื•ื‘ื™ืงื˜ ื”ืฉื’ื™ืื” ื”ืžื•ื‘ื ื” `Error` + +**ืืž;ืœืง:** ื™ืฉื ืŸ ืกืคืจื™ื•ืช ืฉื–ื•ืจืงื•ืช ืฉื’ื™ืื” ื›ืžื—ืจื•ื–ืช ืื• ื›ืื•ื‘ื™ื™ืงื˜ ืคืจื™ ืžื—ืฉื‘ืช ื›ื•ืชื‘ื™ ื”ืงื•ื“ ืฉืœ ื”ืกืคืจื™ื” - ื“ื‘ืจ ืฉื™ื•ืฆืจ ืžื•ืจื›ื‘ื•ืช ื‘ื ื™ื”ื•ืœ ื”ืฉื’ื™ืื•ืช ื•ื‘ื™ืฆื™ืจืช ืžื›ื ื” ืžืฉื•ืชืฃ ื‘ื™ืŸ ืžื•ื“ื•ืœื™ื ืฉื•ื ื™ื. ื‘ืžืงื•ื ื–ืืช, ื”ืฉืงื™ืขื• ื‘ื™ืฆื™ืจืช ืื•ื‘ื™ื™ืงื˜ ืื• ืžื—ืœืงืช (class) ืฉื’ื™ืื” ืฉื™ื•ืจืฉืช ืžืื•ื‘ื™ื™ืงื˜ ื”ืฉื’ื™ืื” ื”ืžื•ื‘ื ื” ืฉืœ ื”ืฉืคื” ื•ื”ืฉืชืžืฉื• ื‘ื–ื” ื‘ื›ืœ ืคืขื ืฉืฆืจื™ืš ืœื“ื—ื•ืช ืืช ื”ืžืฆื‘, ืœื–ืจื•ืง ืฉื’ื™ืื” ืื• ืœื”ืคื™ืฅ ืฉื’ื™ืื”. ื”ืฉื’ื™ืื” ื”ืืคืœื™ืงื˜ื™ื‘ื™ืช ืฆืจื™ื›ื” ืœื”ื•ืกื™ืฃ ืฉื“ื•ืช ื ื•ืกืคื™ื ื›ื“ื•ื’ืžืช ืฉื ื”ืฉื’ื™ืื” ื•ืจืžืช ื”ื—ื•ืžืจื” ืฉืœื”. ืขืœ ื™ื“ื™ ื›ืš, ืœื›ืœ ื”ืฉื’ื™ืื•ืช ื™ืฉื ื• ืžื‘ื ื” ืื—ื™ื“ ื•ื”ืŸ ืžืืคืฉืจื•ืช ืชืžื™ื›ื” ื˜ื•ื‘ื” ื™ื•ืชืจ ื‘ื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช. ื™ืฉื ื• ื›ืœืœ ืฉืœ `no-throw-literal` ESLint ืฉื‘ื•ื“ืง ื‘ืฆื•ืจื” ืžื™ื˜ื‘ื™ืช ืืช ื”ืฉื™ืžื•ืฉ ื”ื–ื” (ืขืœ ืืฃ ืฉื™ืฉ ืœื–ื” ืงืฆืช [ืžื’ื‘ืœื•ืช](https://eslint.org/docs/rules/no-throw-literal) ืฉื™ื›ื•ืœื•ืช ืœื”ืกืชื“ืจ ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘-TypeScript ื•ื”ื’ื“ืจืช ื”ื—ื•ืง `@typescript-eslint/no-throw-literal`) + +**ืื—ืจืช:** ื›ืืฉืจ ืžืคืขื™ืœื™ื ืจื›ื™ื‘ ื›ืœืฉื”ื•, ืื ื™ืฉื ื” ืื™ ื•ื•ื“ืื•ืช ืื™ื–ื” ืกื•ื’ ืฉืœ ืฉื’ื™ืื” ื™ื’ื™ืข - ื–ื” ื’ื•ืจื ืœื›ืš ืฉื ื™ื”ื•ืœ ื”ืฉื’ื™ืื•ืช ื™ื”ื™ื” ื”ืจื‘ื” ื™ื•ืชืจ ืžื•ืจื›ื‘. ื’ืจื•ืข ืžื›ืš, ืฉื™ืžื•ืฉ ื‘ืื•ื‘ื™ื™ืงื˜ื™ื ืžื•ืžืฆืื™ื ืœืชื™ืื•ืจ ืฉื’ื™ืื•ืช ืขืœื•ืœ ืœื”ื•ื‘ื™ืœ ืœืื™ื‘ื•ื“ ืฉืœ ืฉื’ื™ืื•ืช ืงืจื™ื˜ื™ื•ืช ื‘ืขืœื•ืช ืžื™ื“ืข ื—ืฉื•ื‘ ื›ืžื• ืžืขืงื‘ ืื—ืจ ืžืงื•ืจ ื”ืฉื’ื™ืื”! + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื™ืžื•ืฉ ื‘ืื•ื‘ื™ื™ืงื˜ ื”ืฉื’ื™ืื” ื”ืžื•ื‘ื ื”**](./sections/errorhandling/useonlythebuiltinerror.md) + +

+ +## ![โœ”] 2.3 ื”ื‘ื—ื™ื ื• ื‘ื™ืŸ ืฉื’ื™ืื•ืช ืงื˜ืกื˜ืจื•ืคืœื™ื•ืช ืœื‘ื™ืŸ ืฉื’ื™ืื•ืช ืชืคืขื•ืœื™ื•ืช + +**ืืž;ืœืง:** ืฉื’ื™ืื•ืช ืชืคืขื•ืœื™ื•ืช (ืœืžืฉืœ ืงืœื˜ ืœื ืชืงื™ืŸ ื‘ืคื ื™ื™ื” ืœ-API) ืžืชื™ื™ื—ืกื•ืช ืœืžืงืจื™ื ื™ื“ื•ืขื™ื ื‘ื”ื ื”ื”ืฉืคืขื” ืฉืœ ื”ืฉื’ื™ืื” ืžื•ื‘ื ืช ืœื—ืœื•ื˜ื™ืŸ ื•ื™ื›ื•ืœื” ืœื”ื™ื•ืช ืžื ื•ื”ืœืช ื‘ืฆื•ืจื” ืžื—ื•ืฉื‘ืช. ืžืฆื“ ืฉื ื™, ืฉื’ื™ืื•ืช ืงื˜ืกื˜ืจื•ืคืœื™ื•ืช (ื™ื“ื•ืขื•ืช ื’ื ื›ืฉื’ื™ืื•ืช ืชื›ื ื•ืช) ืžืชื™ื™ื—ืกื•ืช ืœืฉื’ื™ืื•ืช ืœื ืฆืคื•ื™ื•ืช ื‘ืžืขืจื›ืช ืฉื“ื•ืจืฉื•ืช ืืชื—ื•ืœ ื‘ื˜ื•ื— ืฉืœื”. + +**ืื—ืจืช:** ืืชื ืขืœื•ืœื™ื ืœืืชื—ืœ ืืช ื”ืžืขืจื›ืช ื‘ืขืงื‘ื•ืช ื›ืœ ืฉื’ื™ืื”. ืื‘ืœ ืœืžื” ืœื’ืจื•ื ืœื›-5000 ืžืฉืชืžืฉื™ื ืœื—ื•ื•ืช ื”ืชื ืชืงื•ืช ื‘ื’ืœืœ ืฉื’ื™ืื” ืชืคืขื•ืœื™ืช ืฆืคื•ื™ื” ื•ืฉื•ืœื™ืช? ื”ื”ื™ืคืš ื”ื•ื ื’ื ืœื ืื™ื“ื™ืืœื™ - ืœื”ืฉืื™ืจ ืืช ื”ืžืขืจื›ืช ืขื•ื‘ื“ืช ื›ืืฉืจ ืงื˜ืกื˜ืจื•ืคื” ืœื ืฆืคื•ื™ื” ืงืจืชื” ื‘ื” ื•ื”ื™ื ืขืœื•ืœื” ืœื’ืจื•ืจ ื”ืชื ื”ื’ื•ืช ื‘ืœืชื™ ืฆืคื•ื™ื”. ื”ื‘ื“ืœื” ื‘ื™ืŸ ืฉื ื™ ื”ืžืงืจื™ื ืžืืคืฉืจืช ื”ืชืžื•ื“ื“ื•ืช ืžื•ืฉื›ืœืช ื•ืžืื•ื–ื ืช ื‘ื”ืชืื ืœื”ืงืฉืจ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื’ื™ืื•ืช ืชืคืขื•ืœื™ื•ืช ืžื•ืœ ืฉื’ื™ืื•ืช ืชื›ื ื•ืช**](./sections/errorhandling/operationalvsprogrammererror.md) + +

+ +## ![โœ”] 2.4 ื ื”ืœื• ืืช ื”ืฉื’ื™ืื•ืช ื‘ืžืจื•ื›ื– ื•ืœื ื‘ืืžืฆืขื•ืช ื›ืœื™ ื‘ื™ื ื™ื™ื + +**ืืž;ืœืง:** ืžื™ืžื•ืฉ ื”ื ื™ื”ื•ืœ ืฉืœ ื”ืฉื’ื™ืื•ืช ื›ืžื• ืœืžืฉืœ ืชืขื•ื“ ื”ืฉื’ื™ืื”, ื”ื—ืœื˜ื” ืื ืœืงืจื•ืก ื•ืื™ืœื• ืžื“ื“ื™ื ืœื ื˜ืจ ืฆืจื™ืš ืœื”ื™ื•ืช ืžืจื•ื›ื– ื‘ืžืงื•ื ืื—ื“ ืฉื›ืœ ื”ื›ื ื™ืกื•ืช ืœืžืขืจื›ืช (ืœืžืฉืœ APIs, cron jobs, scheduled jobs) ืžืฉืชืžืฉื•ืช ื‘ื• ื›ืืฉืจ ื—ืœื” ื‘ื”ืŸ ืฉื’ื™ืื”. + +**ืื—ืจืช:** ืื ืœื ืžื ื”ืœื™ื ืืช ื”ืฉื’ื™ืื•ืช ื‘ืžืงื•ื ืื—ื“ ืื– ื‘ืžื”ืจื” ื™ื”ื™ื” ืฉื›ืคื•ืœ ืงื•ื“ ื•ื›ื ืจืื” ื ื™ื”ื•ืœ ืœื ืชืงื™ืŸ ืฉืœ ื—ืœืง ืžื”ืฉื’ื™ืื•ืช. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื ื™ื”ื•ืœ ื”ืฉื’ื™ืื•ืช ื‘ืžืงื•ื ืžืจื•ื›ื–**](./sections/errorhandling/centralizedhandling.md) + +

+ +## ![โœ”] 2.5 ืชืขื“ื• ืืช ืฉื’ื™ืื•ืช ื”-API ื‘ืืžืฆืขื•ืช OpenAPI ืื• GraphQL + +**ืืž;ืœืง:** ืืคืฉืจื• ืœืžืฉืชืžืฉื™ ื”-API ืฉืœื›ื ืœื“ืขืช ืื™ืœื• ืฉื’ื™ืื•ืช ืขืœื•ืœื•ืช ืœื”ื’ื™ืข ื›ืชืฉื•ื‘ื”, ื›ืš ืฉื”ื ื™ื•ื›ืœื• ืœื”ืชืžื•ื“ื“ ืื™ืชืŸ ื‘ืฆื•ืจื” ืžื•ืฉื›ืœืช ื‘ืžืงื•ื ืœืงืจื•ืก. ืœ-API ืžื‘ื•ืกืก REST ื–ื” ื ืขืฉื” ื‘ื“ืจืš ื›ืœืœ ื‘ืืžืฆืขื•ืช ื›ืœื™ ืชืขื•ื“ ื›ืžื• OpenAPI. ืื ืืชื ืžืฉืชืžืฉื™ื ื‘-GraphQL, ืืชื ื™ื›ื•ืœื™ื ืœื”ืฉืชืžืฉ ื‘ืกื›ืžื” ื•ื‘ื”ืขืจื•ืช ื‘ืฉื‘ื™ืœ ืœื”ืฉื™ื’ ืืช ื”ืžื˜ืจื”. + +**ืื—ืจืช:** ืžื™ ืฉืžืฉืชืžืฉ ื‘-API ืฉืœื ื• ืขืœื•ืœ ืœื”ื—ืœื™ื˜ ืœื’ืจื•ื ืœืžืขืจื›ืช ืฉืœื• ืœืงืจื•ืก ื•ืœืืชื—ืœ ืืช ืขืฆืžื” ืจืง ื‘ื’ืœืœ ืฉื”ื•ื ืงื™ื‘ืœ ืฉื’ื™ืื” ืฉื”ื•ื ืœื ื”ืฆืœื™ื— ืœื”ื‘ื™ืŸ. ืฉื™ืžื• ืœื‘: ื”ืžืฉืชืžืฉ ืฉืœ ื”-API ืฉืœื›ื ื™ื›ื•ืœ ืœื”ื™ื•ืช ืืชื (ืžื” ืฉืงื•ืจื” ื”ืจื‘ื” ื›ืฉืžืฉืชืžืฉื™ื ื‘ืžื™ืงืจื•ืกืจื•ื•ื™ืกื™ื). + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืชื™ืขื•ื“ ืฉื’ื™ืื•ืช ื”-API ื‘ืืžืฆืขื•ืช OpenAPI ืื• GraphQL**](./sections/errorhandling/documentingusingswagger.md) + +

+ +## ![โœ”] 2.6 ื”ื•ืจื™ื“ื• ืืช ื”ืชื”ืœื™ืš ื‘ืฆื•ืจื” ืžืกื•ื“ืจืช ื›ืืฉืจ ื–ืจ ื‘ื ืœื‘ืงืจ + +**ืืž;ืœืง:** ื›ืืฉืจ ืฉื’ื™ืื” ืœื ื™ื“ื•ืขื” ื—ืœื” (ืฉื’ื™ืื” ืงื˜ืกื˜ืจื•ืคืœื™ืช, ืจืื• ืชื•ื‘ื ื” 2.3) - ื™ืฉื ื” ื—ื•ืกืจ ื•ื“ืื•ืช ืœื’ื‘ื™ ื”ื‘ืจื™ืื•ืช ื•ื”ื™ืฆื™ื‘ื•ืช ืฉืœ ื”ืžืขืจื›ืช. ื‘ืžืงืจื” ื›ื–ื”, ืื™ืŸ ื“ืจืš ืœื‘ืจื•ื— ืžืœื’ืจื•ื ืœืฉื’ื™ืื” ืœื”ื™ื•ืช ื‘ืจืช ืฆืคื™ื™ื”, ืกื’ื™ืจืช ื—ื™ื‘ื•ืจื™ื•ืช ืœืจื›ื™ื‘ื™ื ื ื•ืกืคื™ื ื•ื”ื•ืจื“ื” ืฉืœ ื”ืชื”ืœื™ืš. ื›ืœ ืกื‘ื™ื‘ืช ืจื™ืฆื” ืžื”ื™ืžื ื” ื›ื“ื•ื’ืžืช ืฉื™ืจื•ืชื™ Docker ืื• ืฉื™ืจื•ืชื™ ืขื ืŸ ืฉืžืกืคืงื™ื ืคืชืจื•ื ื•ืช ืœืœื ืฉืจืช (serverless) ื™ื•ื•ื“ืื• ืฉื”ืชื”ืœื™ืš ื™ืขืœื” ืžื—ื“ืฉ ืขื‘ื•ืจื›ื. + +**ืื—ืจืช:** ื›ืืฉืจ ืฉื’ื™ืื” ืœื ืฆืคื•ื™ื” ืงื•ืจื™ืช, ืจื›ื™ื‘ ื›ืœืฉื”ื• ืขืœื•ืœ ืœื”ื™ื•ืช ื‘ืžืฆื‘ ืœื ืชืงื™ืŸ (ืœืžืฉืœ event emitter ื’ืœื•ื‘ืืœื™ ืฉืžืคืกื™ืง ืœื”ืคื™ืฅ ืื™ืจื•ืขื™ื ื‘ืฉืœ ื›ืฉืœื•ืŸ ืคื ื™ืžื™) ื•ื”ื—ืœ ืžืขื›ืฉื™ื• ืฉืืจ ื”ื‘ืงืฉื•ืช ืฉืžืฉืชืžืฉื•ืช ื‘ืจื›ื™ื‘ ื–ื” ืขืœื•ืœื•ืช ืœื”ื™ื›ืฉืœ ืื• ืœื”ืชื ื”ื’ ื‘ืื•ืคืŸ ืžืžืฉ ืœื ืฆืคื•ื™. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื•ืจื“ืช ื”ืชื”ืœื™ืš**](./sections/errorhandling/shuttingtheprocess.md) + +

+ +## ![โœ”] 2.7 ื”ืฉืชืžืฉื• ื‘-Logger ืžื•ื›ืจ ื•ืืžื™ืŸ ื›ื“ื™ ืœื”ื’ื“ื™ืœ ืืช ื”ืงึฐืจึดื™ืื•ึผืช ืฉืœ ื”ืฉื’ื™ืื•ืช + +**ืืž;ืœืง:** ื›ืœื™ ืœื•ื’ื™ื ืื™ื›ื•ืชื™ ื›ื“ื•ื’ืžืช [Pino](https://github.com/pinojs/pino) ืื• [Winston](https://github.com/winstonjs/winston) ืžื’ื“ื™ืœ ืืช ื”ืงืจื™ืื•ืช ื•ื”ื”ื‘ื ื” ืฉืœ ื”ืœื•ื’ื™ื ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ืจืžืช ื—ื•ืžืจื”, ืขื™ืžื•ื“, ืขื™ืฆื•ื‘, ืฆื‘ืขื™ื ื•ืขื•ื“. ืœ-`console.log` ืื™ืŸ ืืช ื”ื™ื›ื•ืœื•ืช ื”ืœืœื• ื•ืจืื•ื™ ืœื”ื™ืžื ืข ืžืฉื™ืžื•ืฉ ื‘ื•. ื”ืขื™ืคืจื•ืŸ ื”ื—ื“ ื‘ื™ื•ืชืจ ื‘ืชื—ื•ื ืžืืคืฉืจ ื”ื•ืกืคื” ืฉืœ ืฉื“ื•ืช ืฉื™ืžื•ืฉื™ื™ื ื ื•ืกืคื™ื ืœืœื ืชืงื•ืจื” ื’ื‘ื•ื”ื” ืฉืœ ื‘ื™ืฆื•ืขื™ื. ืžืคืชื—ื™ื ืฆืจื™ื›ื™ื ืœื›ืชื•ื‘ ืืช ื”ืœื•ื’ื™ื ืœ-`stdout` ื•ืœืชืช ืœืชืฉืชื™ืช ืœื”ืขื‘ื™ืจ ืืช ื”ืžื™ื“ืข ืœื›ืœื™ ื”ืžืชืื™ื ืขื‘ื•ืจ ื›ืœ ืžืงืจื”. + +**ืื—ืจืช:** ืจืคืจื•ืฃ ืขืœ ืฉื•ืจื•ืช console.log ืื• ื‘ืฆื•ืจื” ื™ื“ื ื™ืช ืขืœ ืงื‘ืฆื™ ื˜ืงืกื˜ ืขืžื•ืกื™ื ืœืขื™ื™ืคื” ืœืœื ื›ืœื™ ื—ื™ืคื•ืฉ ื•ืชืฆื•ื’ื” ืžื•ืชืืžื™ื ืขืœื•ืœื™ื ืœื”ืฉืื™ืจ ืืชื›ื ืœืขื‘ื•ื“ ืขื“ ื”ืฉืขื•ืช ื”ืงื˜ื ื•ืช ืฉืœ ื”ืœื™ืœื”. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื™ืžื•ืฉ ื‘-Logger ืืžื™ืŸ**](./sections/errorhandling/usematurelogger.md) + +

+ +## ![โœ”] 2.8 ื‘ื™ื“ืงื• ืืช ืชื’ื•ื‘ืช ื”ืžืขืจื›ืช ืœืฉื’ื™ืื•ืช ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ื›ืœื™ ื”ื‘ื“ื™ืงื•ืช ื”ืื”ื•ื‘ ืขืœื™ื›ื + +**ืืž;ืœืง:** ื‘ื™ืŸ ืื ื™ืฉ ืœื›ื ื›ืœื™ QA ืื•ื˜ื•ืžื˜ื™ ื•ืžืงืฆื•ืขื™ ื•ื‘ื™ืŸ ืื ืื—ื“ ื”ืžืคืชื—ื™ื ืžื‘ืฆืข ืืช ื”ื‘ื“ื™ืงื•ืช - ื•ื“ืื• ื›ื™ ืœื ืจืง ื”ืžืกืœื•ืœ ื”ื‘ื˜ื•ื— ืฉืœ ื”ืงื•ื“ ืžื›ื•ืกื”, ืืœื ื’ื ื ื™ื”ื•ืœ ื”ืฉื’ื™ืื•ืช ื•ืฉื—ื•ื–ืจื•ืช ื”ืฉื’ื™ืื•ืช ืฉืืžื•ืจื•ืช ืœื—ื–ื•ืจ ื‘ืžืงืจื” ืฉืœ ืชืงืœื”. ื ื•ืกืฃ ืขืœ ื›ืš, ื‘ื™ื“ืงื• ืžืงืจื™ื ืžื•ืจื›ื‘ื™ื ื™ื•ืชืจ ืฉืœ ืฉื’ื™ืื•ืช, ื›ืžื• ืœืžืฉืœ ืฉื’ื™ืื•ืช ื‘ืœืชื™ ืฆืคื•ื™ื•ืช, ื›ื“ื™ ืœื•ื•ื“ื ืฉื”ืจื›ื™ื‘ ืฉืžื˜ืคืœ ื‘ืฉื’ื™ืื•ืช ืžื‘ืฆืข ื–ืืช ื›ืจืื•ื™ (ืจืื• ื“ื•ื’ืžืื•ืช ืงื•ื“ ื‘ืงื™ืฉื•ืจ "ืœืงืจื™ืื” ื ื•ืกืคืช") + +**ืื—ืจืช:** ืœืœื ื‘ื“ื™ืงื•ืช ื›ืœืœ, ืœื ื™ื“ื ื™ื•ืช ื•ืœื ืื•ื˜ื•ืžื˜ื™ื•ืช, ืœื ืชื•ื›ืœื• ืœืกืžื•ืš ืขืœ ื”ืงื•ื“ ืฉืœื›ื ืฉื™ื—ื–ื™ืจ ืืช ื”ืฉื’ื™ืื” ื”ื ื›ื•ื ื”. ืœืœื ืฉื’ื™ืื•ืช ืžืฉืžืขื•ืชื™ื•ืช ืœื ืชื•ื›ืœื• ืœื˜ืคืœ ื‘ืฉื’ื™ืื•ืช. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื‘ื“ื™ืงืช ื”ืชื ื”ื’ื•ืช ื‘ืขืช ืฉื’ื™ืื”**](./sections/errorhandling/testingerrorflows.md) + +

+ +## ![โœ”] 2.9 ื’ืœื• ืฉื’ื™ืื•ืช ื•ื–ืžื ื™ ื”ืฉื‘ืชื” ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ื›ืœื™ APM + +**ืืž;ืœืง:** ื›ืœื™ ื ื™ื˜ื•ืจ ื•ื‘ื“ื™ืงืช ื‘ื™ืฆื•ืขื™ื (ืžื•ื›ืจื™ื ื›-APM) ืžื•ื“ื“ื™ื ื‘ืื•ืคืŸ ื™ื–ื•ื ืืช ื”ืงื•ื“ ืื• ื”-API ื›ืš ืฉื‘ืื•ืคืŸ ืงืกื•ื ื”ื ืžืฆื™ื’ื™ื ืฉื’ื™ืื•ืช, ื”ืชืจืกืงื•ื™ื•ืช ื•ื—ืœืงื™ื ืฉืขื•ื‘ื“ื™ื ืœืื˜ ืžื”ืฆืคื•ื™ ื•ืืชื ืœื ืฉืžื™ื ืœื‘ ืืœื™ื”ื. + +**ืื—ืจืช:** ืืชื ืขืœื•ืœื™ื ืœื”ืชืืžืฅ ืจื‘ื•ืช ื‘ืžื“ื™ื“ื” ืฉืœ ื‘ืขื™ื•ืช ื‘ื™ืฆื•ืขื™ื ื•ื–ืžื ื™ ื”ืฉื‘ืชื” ืฉืœ ื”ืžืขืจื›ืช, ื›ื ืจืื” ืฉืœืขื•ืœื ืœื ืชื”ื™ื• ืžื•ื“ืขื™ื ืœืื™ื–ื” ื—ืœืงื™ื ื‘ืžืขืจื›ืช ื”ื ื”ืื™ื˜ื™ื™ื ื‘ื™ื•ืชืจ ื•ืื™ืš ื–ื” ืžืฉืคื™ืข ืขืœ ื—ื•ื•ื™ืช ื”ืžืฉืชืžืฉ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื™ืžื•ืฉ ื‘-APM**](./sections/errorhandling/apmproducts.md) + +

+ +## ![โœ”] 2.10 ืชืคืกื• ืžืงืจื™ื ืœื ืžื˜ื•ืคืœื™ื ืฉืœ ื“ื—ื™ื•ืช ืฉืœ ื”ื‘ื˜ื—ื•ืช + +**ืืž;ืœืง:** ื›ืœ ืฉื’ื™ืื” ืื• ื“ื—ื™ื™ื” ืฉื—ื•ื–ืจืช ืžื”ื‘ื˜ื—ื” ืชื™ื‘ืœืข, ืืœื ืื ื›ืŸ ื‘ืฉืœื‘ ื”ืคื™ืชื•ื— ื™ื˜ืคืœื• ื‘ื” ื›ืžื• ืฉืฆืจื™ืš. ืืคื™ืœื• ืื ื™ืฉ ื‘ืงื•ื“ ื”ืื–ื ื” ืœ `process.uncaughtException`! ื›ื“ื™ ืœื”ืชื’ื‘ืจ ืขืœ ื–ื” ืฆืจื™ืš ืœื”ืื–ื™ืŸ ื’ื ืœ `process.unhandledRejection`. + +**ืื—ืจืช:** ื”ืฉื’ื™ืื•ืช ื‘ืžืขืจื›ืช ื™ื‘ืœืขื• ื•ื™ืขืœืžื• ืœืœื ืขืงื‘ื•ืช. ืœื ืžืฉื”ื• ืฉืฆืจื™ืš ืœื“ืื•ื’ ืžืžื ื•... + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืชืคื™ืกื” ืฉืœ ื“ื—ื™ื•ืช ืฉืœ ื”ื‘ื˜ื—ื•ืช**](./sections/errorhandling/catchunhandledpromiserejection.md) + +

+ +## ![โœ”] 2.11 ื”ื™ื›ืฉืœื• ืžื”ืจ, ื•ื“ืื• ืืช ืžืฉืชื ื™ ื”ืงืœื˜ ื‘ืืžืฆืขื•ืช ืกืคืจื™ื” ื™ืขื•ื“ื™ืช + +**ืืž;ืœืง:** ื”ื’ื“ื™ืจื• ืชื‘ื ื™ืช ืงืœื˜ ืงืฉื™ื—ื” ืœ-API ื›ื“ื™ ืœื”ื™ืžื ืข ืžื‘ืื’ื™ื ืžืœื•ื›ืœื›ื™ื ืฉืงืฉื” ื”ืจื‘ื” ื™ื•ืชืจ ืœืขืงื•ื‘ ืื—ืจื™ื”ื. ื›ืชื™ื‘ืช ืงื•ื“ ื”ืื™ืžื•ืช ื”ื•ื ืชื”ืœื™ืš ืžื™ื™ื’ืข, ืืœื ืื ื›ืŸ ืชืฉืชืžืฉื• ื‘ืื—ืช ื”ืกืคืจื™ื•ืช ื”ืžื•ื›ืจื•ืช ื›ื™ื•ื ื›ืžื• [ajv](https://www.npmjs.com/package/ajv), [zod](https://github.com/colinhacks/zod), ืื• [typebox](https://github.com/sinclairzx81/typebox). + +**ืื—ืจืช:** ื—ืฉื‘ื• ืขืœ ื–ื” - ื”ืคื•ื ืงืฆื™ื” ืฉืœื›ื ืžืฆืคื” ืœืงื‘ืœ ื›ืงืœื˜ ืžืฉืชื ื” `discount` ืžืกืคืจื™ ืฉืžื™ ืฉืงืจื” ืœืคื•ื ืงืฆื™ื” ืฉื›ื— ืœื”ืขื‘ื™ืจ. ื‘ื”ืžืฉืš, ื”ืงื•ื“ ื‘ื•ื“ืง ืื `discount != 0` (ื›ืžื•ืช ื”ื”ื ื—ื” ืฉืืคืฉืจ ืœืงื‘ืœ ื’ื“ื•ืœื” ืžืืคืก), ื•ืื ื›ืŸ ืื– ื”ืžืฉืชืžืฉ ื™ื”ื ื” ืžื”ื”ื ื—ื”. ื•ื•ืื•, ื–ื” ื‘ืื’ ืžืœื•ื›ืœืš, ืจืื™ืชื??? + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื›ืฉืœื•ืŸ ืžื”ื™ืจ**](./sections/errorhandling/failfast.md) + +

+ +## ![โœ”] 2.12 ืชืžื™ื“ ื”ืžืชื™ื ื• ืœืชืฉื•ื‘ื” ืžื”ื”ื‘ื˜ื—ื•ืช ืœืคื ื™ ืฉืืชื ืžืขื‘ื™ืจื™ื ืืช ื”ืชืฉื•ื‘ื” ื”ืœืื” ื›ื“ื™ ืœื”ื™ืžื ืข ืžืžืขืงื‘ ื—ืœืงื™ + +**ืืž;ืœืง:** ืชืžื™ื“ ื›ืชื‘ื• `return await` ื›ืืฉืจ ืžื—ื–ื™ืจื™ื ืชื•ืฆืื” ืฉืœ ื”ื‘ื˜ื—ื” ื•ื–ืืช ื›ื“ื™ ืœื”ืฉื™ื’ ืขืจืš ืžืœื ืฉืœ ืžืขืงื‘ ืื—ืจ ืžืงื•ืจ ื”ืฉื’ื™ืื” (stacktrace). ืื ืคื•ื ืงืฆื™ื” ืžื—ื–ื™ืจื” ื”ื‘ื˜ื—ื” ื”ื™ื ื—ื™ื™ื‘ืช ืœื”ื™ื•ืช ืžื•ื’ื“ืจืช ื›ืคื•ื ืงืฆื™ื” ืืกื™ื ื›ืจื•ื ื™ืช ื•ื‘ืžืคื•ืจืฉ ืœื—ื›ื•ืช ืœื”ื‘ื˜ื—ื” ืฉื”ื™ื ืžื—ื–ื™ืจื”. + +```js +async function promisifyFunction() { + // some logic + return await new Promise(...); +} +``` + +**ืื—ืจืช:** ื”ืคื•ื ืงืฆื™ื” ืฉืžื—ื–ื™ืจื” ื”ื‘ื˜ื—ื” ืœืœื ื”ืžืชื ื” ืœื ืชื•ืคื™ืข ื‘ื ืชื™ื‘ ื”ืžืขืงื‘ ืื—ืจื™ ื”ืฉื’ื™ืื” (stacktrace). ื—ื•ืกืจื™ื ื›ืืœื• ืขืœื•ืœื™ื ืœืกื‘ืš ืืช ื”ื”ื‘ื ื” ืฉืœ ื–ืจื™ืžืช ื”ืžืขืจื›ืช ืฉื’ืจืžื” ืœืฉื’ื™ืื”, ื‘ืžื™ื•ื—ื“ ืื ื”ื’ื•ืจื ืœื”ืชื ื”ื’ื•ืช ื”ืœื ืฆืคื•ื™ื” ืงืจื” ื‘ืคื•ื ืงืฆื™ื” ื”ื—ืกืจื”. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื—ื–ืจืช ื”ื‘ื˜ื—ื•ืช**](./sections/errorhandling/returningpromises.md) + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# `3. ืชื‘ื ื™ื•ืช ืงื•ื“ ื•ืกื’ื ื•ืŸ ืขื™ืฆื•ื‘` + +## ![โœ”] 3.1 ื”ืฉืชืžืฉื• ื‘-ESLint + +**ืืž;ืœืง:** [ESLint](https://eslint.org) ื”ื•ื ื”ืกื˜ื ื“ืจื˜ ื“ื”-ืคืงื˜ื• ืœืžืฆื™ืืช ืฉื’ื™ืื•ืช ื‘ืงื•ื“ ื•ืชื™ืงื•ืŸ ืฉืœ ืกื’ื ื•ื ื•ืช ืงื•ื“, ืœื ืจืง ื–ื™ื”ื•ื™ ืฉืœ ืจื•ื•ื— ืกื•ืจืจ ืฉืขืœื•ืœ ืœื™ืฆื•ืจ ืชืงืœื” ืืœื ื’ื ื–ื™ื”ื•ื™ ืฉืœ ืงื•ื“ ืฉืœื ืขื•ืžื“ ื‘ืกื˜ื ื“ืจื˜ื™ื (anti-pattern) ื›ืžื• ื–ืจื™ืงืช ืฉื’ื™ืื•ืช ืœืœื ืกื™ื•ื•ื’. ืืžื ื ESLint ื™ื›ื•ืœ ืœืชืงืŸ ื‘ืื•ืคืŸ ืื•ื˜ื•ืžื˜ื™ ืกื’ื ื•ื ื•ืช ืงื•ื“, ืืš ื›ืœื™ื ืื—ืจื™ื ื›ื“ื•ื’ืžืช [prettier](https://www.npmjs.com/package/prettier) ื˜ื•ื‘ื™ื ื™ื•ืชืจ ื‘ืขื™ืฆื•ื‘ ื•ืกื’ื ื•ืŸ ื”ืงื•ื“ ื•ืขื•ื‘ื“ื™ื ื‘ืฉื™ืœื•ื‘ ืขื ESLint. + +**ืื—ืจืช:** ืžืคืชื—ื™ื ื™ืฉืชืขืžืžื• ืชื•ืš ื›ื“ื™ ื”ืฉืงืขืช ื–ืžื ื ื‘ืžืฆื™ืืช ืจื•ื•ื—ื™ื ืกื•ืจืจื™ื ื•ื™ื“ืื’ื• ืœืื•ืจืš ื”ืฉื•ืจื” ื•ื”ื–ืžืŸ ื”ื™ืงืจ ืฉืœื”ื ื™ื‘ื•ื–ื‘ื– ืขืœ ืื™ืš ืœืฉืžื•ืจ ืขืœ ืกื’ื ื•ืŸ ื”ืงื•ื“ ืฉืœ ื”ืคืจื•ื™ื™ืงื˜. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื™ืžื•ืฉ ื‘-ESLint ื•-Prettier**](./sections/codestylepractices/eslint_prettier.md) + +

+ +## ![โœ”] 3.2 ื”ืฉืชืžืฉื• ื‘ืชื•ืกืคื™ื ืฉืœ Node.js ืฉืžืจื—ื™ื‘ื™ื ืืช ESLint + +**ืืž;ืœืง:** ืขืœ ื’ื‘ื™ ื”ืกื˜ื ื“ืจื˜ ืฉืœ ื—ื•ืงื™ ESLint ืฉืžื›ืกื™ื ืืช ืฉืคืช JavaScript, ื”ื•ืกื™ืคื• ืืช ื”ืชื•ืกืคื™ื ื”ื™ืขื•ื“ื™ื™ื ืฉืœ Node.js ื›ืžื• [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node), [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha), [eslint-plugin-node-security](https://www.npmjs.com/package/eslint-plugin-security), [eslint-plugin-require](https://www.npmjs.com/package/eslint-plugin-require), [eslint-plugin-jest](https://www.npmjs.com/package/eslint-plugin-jest) ื•ืขื•ื“ ืชื•ืกืคื™ื ืฉืžืžืžืฉื™ื ื—ื•ืงื™ื ื ื•ืกืคื™ื ื•ืžื•ืขื™ืœื™ื. + +**ืื—ืจืช:** ื”ืจื‘ื” ืชื‘ื ื™ื•ืช ืงื•ื“ ืœื ืชืงื™ื ื•ืช ืฉื‘ืฉื™ืžื•ืฉ ื‘-Node.js ื ืขืœืžื•ืช ืžืชื—ืช ืœืจื“ืื“. ืœื“ื•ื’ืžื”, ืžืคืชื—ื™ื ื™ื›ืชื‘ื• `require(variableAsPath)` ืขื ืžืฉืชื ื” ืฉืžืืคืฉืจ ื’ื™ืฉื” ืœืชื™ืงื™ื” ื‘ืงื•ื“, ื“ื‘ืจ ืฉืžืืคืฉืจ ืœืชื•ืงืคื™ื ืœื”ืจื™ืฅ ื›ืœ ืงื•ื“ JS. ืื ืชืฉืชืžืฉื• ื‘ื—ื•ืงื™ Node.js ืชื•ื›ืœื• ืœื–ื”ื•ืช ืืช ื”ื˜ืขื•ืช ื”ื–ืืช ื•ืœืงื‘ืœ ืขืœื™ื” ื”ืชืจืื” ืžื‘ืขื•ื“ ืžื•ืขื“. + +

+ +## ![โœ”] 3.3 ื”ืชื—ื™ืœื• ื‘ืœื•ืง ืฉืœ ืงื•ื“ ืขื ืกื•ื’ืจื™ื™ื ืžืกื•ืœืกืœื™ื ื‘ืื•ืชื” ื”ืฉื•ืจื” + +**ืืž;ืœืง:** ืžื•ืžืœืฅ ืฉื”ืกื•ื’ืจื™ื™ื ื”ืžืกื•ืœืกืœื™ื ื”ืคื•ืชื—ื™ื ืฉืœ ื‘ืœื•ืง ืฉืœ ืงื•ื“ ื™ื”ื™ื• ื‘ืื•ืชื” ื”ืฉื•ืจื” ื™ื—ื“ ืขื ื”ืงื•ื“. + +### ื“ื•ื’ืžื” + +```javascript +// Do +function someFunction() { + // code block +} + +// Avoid +function someFunction() +{ + // code block +} +``` + +**ืื—ืจืช:** ื”ืชืขืœืžื•ืช ืžืฉื™ื˜ืช ืขื‘ื•ื“ื” ื–ื• ืขืœื•ืœื” ืœื”ื•ื‘ื™ืœ ืœืชื•ืฆืื•ืช ืœื ืฆืคื•ื™ื•ืช, ื›ืžื• ืฉื ื™ืชืŸ ืœืจืื•ืช ื‘ืฉืจืฉื•ืจ ื‘ืงื™ืฉื•ืจ ืž StackOverflow: + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช:** "ืœืžื” ื”ืชื•ืฆืื•ืช ืžืฉืชื ื•ืช ื‘ื”ืชืื ืœืžื™ืงื•ื ื”ืกื•ื’ืจ ื”ืžืกื•ืœืกืœ?" (StackOverflow)](https://stackoverflow.com/questions/3641519/why-does-a-results-vary-based-on-curly-brace-placement) + +

+ +## ![โœ”] 3.4 ื”ืคืจื™ื“ื• ื‘ื™ืŸ ื”ื”ืฆื”ืจื•ืช ื”ืฉื•ื ื•ืช ื‘ืฆื•ืจื” ืชืงื ื™ืช + +ื‘ื™ืŸ ืื ืืชื ืžืฉืชืžืฉื™ื ื‘ื ืงื•ื“ื”-ืคืกื™ืง (;) ื‘ืฉื‘ื™ืœ ืœื”ืคืจื™ื“ ื‘ื™ืŸ ื”ื”ืฆื”ืจื•ืช ืขืœ ื”ืžืฉืชื ื™ื ื•ื‘ื™ืŸ ืื ืœื, ืขืฆื ื”ื™ื“ื™ืขื” ืขืœ ื”ื”ืฉืœื›ื•ืช ืฉืœ ื™ืจื™ื“ืช ืฉื•ืจื” ื‘ืžืงื•ื ื”ืœื ืžืชืื™ื ืื• ืฉืœ ื”ื•ืกืคื” ืื•ื˜ื•ืžื˜ื™ืช ืฉืœ ื ืงื•ื“ื”-ืคืกื™ืง, ื™ืขื–ืจื• ืœื›ื ืœื–ื”ื•ืช ืฉื’ื™ืื•ืช ืกื™ื ื˜ืงืก ืจื’ื™ืœื•ืช. + +**ืืž;ืœืง:** ืฉื™ืžื•ืฉ ื‘-ESLint ื›ื“ื™ ืœื”ืขืœื•ืช ืืช ื”ืžื•ื“ืขื•ืช ืœื’ื‘ื™ ื”ืกื™ื›ื•ืŸ ื”ื›ืจื•ืš ื‘ื–ื”. ื›ืœื™ื ื›ืžื• [Prettier](https://prettier.io/) ืื• [Standardjs](https://standardjs.com/) ื™ื›ื•ืœื™ื ื‘ืื•ืคืŸ ืื•ื˜ื•ืžื˜ื™ ืœืคืชื•ืจ ืืช ื”ื‘ืขื™ื•ืช ื”ืœืœื•. + +**ืื—ืจืช:** ื›ืžื• ืฉืจืื™ื ื• ื‘ืกืขื™ืฃ ื”ืงื•ื“ื, "ื”ืžืชื•ืจื’ืžืŸ" (interpreter) ืฉืœ JavaScript ืžื•ืกื™ืฃ ืื•ื˜ื•ืžื˜ื™ืช ื ืงื•ื“ื”-ืคืกื™ืง ื‘ืกื•ืฃ ื›ืœ ื”ืฆื”ืจื” ื‘ืžื™ื“ื” ื•ืื™ืŸ, ืื• ืฉื”ื•ื ืžื—ืœื™ื˜ ื›ื™ ื”ื”ืฆื”ืจื” ืžืกืชื™ื™ืžืช ื‘ืžืงื•ื ืื—ืจ ืžื”ืžืชื•ื›ื ืŸ ืขืœ ื™ื“ื™ื ื•, ื“ื‘ืจ ืฉืขืœื•ืœ ืœื”ื•ื‘ื™ืœ ืœืชื•ืฆืื•ืช ื‘ืœืชื™ ืฆืคื•ื™ื•ืช. ืืคืฉืจ ืœื”ืฉืชืžืฉ ื‘ื”ืฉืžื•ืช ื•ืœื”ื™ืžื ืข ืž [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) ื›ื“ื™ ืœื”ื™ืžื ืข ืžืจื•ื‘ ื”ื”ืชื ื”ื’ื•ื™ื•ืช ื”ื‘ืœืชื™ ืฆืคื•ื™ื•ืช. + +### ื“ื•ื’ืžื” + +```javascript +// Do +function doThing() { + // ... +} + +doThing() + +// Do + +const items = [1, 2, 3] +items.forEach(console.log) + +// Avoid โ€” throws exception +const m = new Map() +const a = [1,2,3] +[...m.values()].forEach(console.log) +> [...m.values()].forEach(console.log) +> ^^^ +> SyntaxError: Unexpected token ... + +// Avoid โ€” throws exception +const count = 2 // it tries to run 2(), but 2 is not a function +(function doSomething() { + // do something amazing +}()) +// put a semicolon before the immediate invoked function, after the const definition, save the return value of the anonymous function to a variable or avoid IIFEs altogether +``` + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช:** "Semi ESLint rule"](https://eslint.org/docs/rules/semi) +
+๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช:** "No unexpected multiline ESLint rule"](https://eslint.org/docs/rules/no-unexpected-multiline) + +

+ +## ![โœ”] 3.5 ืชื ื• ืœืคื•ื ืงืฆื™ื” ืฉื + +**ืืž;ืœืง:** ืชื ื• ืฉืžื•ืช ืœื›ืœ ื”ืคื•ื ืงืฆื™ื•ืช, ื›ื•ืœืœ closures ื•-callbacks. ื”ื™ืžื ืขื• ืžืคื•ื ืงืฆื™ื•ืช ืื ื•ื ื™ืžื™ื•ืช. ื–ื” ืžืื•ื“ ืฉื™ืžื•ืฉื™ ื›ืฉื‘ื•ื“ืงื™ื ืืคืœื™ืงืฆื™ื•ืช Node.js. ืžืชืŸ ืฉืžื•ืช ืœื›ืœ ื”ืคื•ื ืงืฆื™ื•ืช ื™ืืคืฉืจ ืœื›ื ืœื”ื‘ื™ืŸ ื‘ืงืœื•ืช ืขืœ ืžื” ืืชื ืžืกืชื›ืœื™ื ื›ืฉืืชื ืฆื•ืคื™ื ื‘ืชืžื•ื ืช ืžืฆื‘ ืฉืœ ื”ื–ื™ื›ืจื•ืŸ ืฉืœ ื”ืืคืœื™ืงืฆื™ื”. + +**ืื—ืจืช:** ืœื“ื‘ื’ ืืช ื’ืจืกืช ื”ื™ืฆื•ืจ (production) ืขืœ ื‘ืกื™ืก ืชืžื•ื ืช ืžืฆื‘ ืฉืœ ื”ื–ื™ื›ืจื•ืŸ (core dump) ืขืœื•ืœ ืœื”ื™ื•ืช ืžืืชื’ืจ ื›ืฉื”ื‘ืขื™ื•ืช ืฉืœ ื”ื–ื™ื›ืจื•ืŸ ืงื•ืจื•ืช ื‘ื›ืœ ืžื™ื ื™ ืคื•ื ืงืฆื™ื•ืช ืื ื•ื ื™ืžื™ื•ืช. + +

+ +## ![โœ”] 3.6 ื”ืฉืชืžืฉื• ื‘ืžื•ืกื›ืžื•ืช ืงื‘ื•ืขื•ืช ื‘ืžืชืŸ ืฉืžื•ืช ืœืžืฉืชื ื™ื, ืœืงื‘ื•ืขื™ื, ืœืคื•ื ืงืฆื™ื•ืช ื•ืœืžื—ืœืงื•ืช + +**ืืž;ืœืง:** ื”ืฉืชืžืฉื• ื‘-**_lowerCamelCase_** ื›ืืฉืจ ืืชื ื ื•ืชื ื™ื ืฉืžื•ืช ืœืงื‘ื•ืขื™ื, ืžืฉืชื ื™ื ื•ืคื•ื ืงืฆื™ื•ืช, **_UpperCamelCase_** (ื’ื ื”ืื•ืช ื”ืจืืฉื•ื ื” ื’ื“ื•ืœื”) ื›ืืฉืจ ืืชื ื ื•ืชื ื™ื ืฉืžื•ืช ืœืžื—ืœืงื•ืช ื•-**_UPPER_SNAKE_CASE_** ื›ืืฉืจ ืืชื ื ื•ืชื ื™ื ืฉืžื•ืช ืœืžืฉืชื ื™ื ื’ืœื•ื‘ืœื™ื™ื ืื• ืกื˜ื˜ื™ื™ื. ืกื“ืจ ื–ื” ื™ืืคืฉืจ ืœื›ื ืœื”ื‘ื—ื™ืŸ ื‘ืงืœื•ืช ื‘ื™ืŸ ืžืฉืชื ื™ื ืจื’ื™ืœื™ื ื•ืคื•ื ืงืฆื™ื•ืช ืœื‘ื™ืŸ ืžื—ืœืงื•ืช ืฉื“ื•ืจืฉื•ืช ืืชื—ื•ืœ ื•ืœื‘ื™ืŸ ืžืฉืชื ื™ื ื’ืœื•ื‘ืœื™ื™ื. ื”ืฉืชืžืฉื• ื‘ืฉืžื•ืช ืฉืžืชืืจื™ื ื”ื™ื˜ื‘ ืืช ืžืฉืžืขื•ืช ื”ืžืฉืชื ื”, ืืš ืฉื™ื”ื™ื” ืงืฆืจ. + +**ืื—ืจืช:** JavaScript ื”ื™ื ื”ืฉืคื” ื”ื™ื—ื™ื“ื” ื‘ืขื•ืœื ืฉืชืืคืฉืจ ืœื›ื ืœืงืจื•ื ืœ-constructor ("Class") ื™ืฉื™ืจื•ืช ืœืœื ืืชื—ื•ืœ. ืœื›ืŸ, ื—ืฉื•ื‘ ืžืื•ื“ ืœื”ื‘ื“ื™ืœ ื‘ื™ืŸ ืฉืžื•ืช ืžื—ืœืงื•ืช ื•ืฉืžื•ืช ืคื•ื ืงืฆื™ื•ืช ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘-UpperCamelCase. + +### ื“ื•ื’ืžืื•ืช + +```javascript +// for global variables names we use the const/let keyword and UPPER_SNAKE_CASE +let MUTABLE_GLOBAL = "mutable value"; +const GLOBAL_CONSTANT = "immutable value"; +const CONFIG = { + key: "value", +}; + +// examples of UPPER_SNAKE_CASE convention in nodejs/javascript ecosystem +// in javascript Math.PI module +const PI = 3.141592653589793; + +// https://github.com/nodejs/node/blob/b9f36062d7b5c5039498e98d2f2c180dca2a7065/lib/internal/http2/core.js#L303 +// in nodejs http2 module +const HTTP_STATUS_OK = 200; +const HTTP_STATUS_CREATED = 201; + +// for class name we use UpperCamelCase +class SomeClassExample { + // for static class properties we use UPPER_SNAKE_CASE + static STATIC_PROPERTY = "value"; +} + +// for functions names we use lowerCamelCase +function doSomething() { + // for scoped variable names we use the const/let keyword and lowerCamelCase + const someConstExample = "immutable value"; + let someMutableExample = "mutable value"; +} +``` + +

+ +## ![โœ”] 3.7 ื”ืขื“ื™ืคื• const ืขืœ ืคื ื™ let. ื ื™ื˜ืฉื• ืืช var + +**ืืž;ืœืง:** ืฉื™ืžื•ืฉ ื‘-`const` ืžืฉืžืขื•ืชื• ื”ื™ื ืฉืœืื—ืจ ืฉื”ืžืฉืชื ื” ืžืื•ืชื—ืœ ืœืจืืฉื•ื ื” ื”ื•ื ืœื ื™ื›ื•ืœ ืœื”ื™ื•ืช ืžืื•ืชื—ืœ ืฉื•ื‘. ื”ืขื“ืคืช ืฉื™ืžื•ืฉ ื‘-`const` ืชืขื–ื•ืจ ืœื›ื ืœื ืœื”ืชืคืชื•ืช ื•ืœื”ืฉืชืžืฉ ืฉื•ื‘ ื‘ืื•ืชื• ืžืฉืชื ื” ืœืฆืจื›ื™ื ืฉื•ื ื™ื ื•ืชื”ืคื•ืš ืืช ื”ืงื•ื“ ืฉืœื›ื ืœืงืจื™ื ื™ื•ืชืจ. ืื ืžืฉืชื ื” ืฆืจื™ืš ืœื”ื™ื•ืช ืžืื•ืชื—ืœ ืžื—ื“ืฉ, ืœืžืฉืœ ื‘ืชื•ืš ืœื•ืœืืช for, ืื– ื”ืฉืชืžืฉื• ื‘-`let` ืœืฆื•ืจืš ื›ืš. ื ืงื•ื“ื” ื ื•ืกืคืช ืฉื—ืฉื•ื‘ ืœืฆื™ื™ืŸ ื”ื™ื ืฉืฉื™ืžื•ืฉ ื‘-`let` ืืคืฉืจื™ืช ืจืง ื‘ืชื•ืš ืื•ืชื• ื”ื‘ืœื•ืง ืฉื”ื™ื ื”ื•ื’ื“ืจื” ื‘ื•. `var` ื ืฆืžื“ ืœscope ืฉืœ ื”ืคื•ื ืงืฆื™ื” ืฉื”ื•ื ืžื•ื’ื“ืจ ื‘ื• ื•ืœื ืœื‘ืœื•ืง ืกืคืฆื™ืคื™ ื•ืœื›ืŸ [ืฆืจื™ืš ืœื ืœื”ืฉืชืžืฉ ื‘ื• ื‘-ES6](https://hackernoon.com/why-you-shouldnt-use-var-anymore-f109a58b9b70) ื›ืฉืืคืฉืจ ืœื”ืฉืชืžืฉ ื‘-`const` ื•ื‘-`let`. + +**ืื—ืจืช:** ื“ื™ื‘ื•ื’ ื”ื•ืคืš ืœื”ื™ื•ืช ืžืื•ื“ ืžืกื•ืจื‘ืœ ื›ืืฉืจ ืžืฉืชื ื” ืžืฉืชื ื” ืœืขื™ืชื™ื ื“ื—ื•ืคื•ืช. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: JavaScript ES6+: var, let, or const?** ](https://medium.com/javascript-scene/javascript-es6-var-let-or-const-ba58b8dcde75) + +

+ +## ![โœ”] 3.8 ื˜ืขื ื• ืžื•ื“ื•ืœื™ื ื‘ืชื—ื™ืœื”, ื•ืœื ื‘ืงืจื™ืื” ืœืคื•ื ืงืฆื™ื•ืช + +**ืืž;ืœืง:** ื˜ืขื ื• ืืช ื”ืžื•ื“ื•ืœื™ื (require...) ื‘ืชื—ื™ืœืช ื›ืœ ืงื•ื‘ืฅ, ืœืคื ื™ ื›ืœ ื”ืคื•ื ืงืฆื™ื•ืช. ืฉื™ื˜ืช ืขื‘ื•ื“ื” ืคืฉื•ื˜ื” ื–ื• ืœื ืจืง ืฉืชืขื–ื•ืจ ืœื›ื ื‘ืงืœื•ืช ื•ื‘ืžื”ื™ืจื•ืช ืœื–ื”ื•ืช ืืช ื”ืชืœื•ื™ื•ืช ืฉืœ ืงื•ื‘ืฅ ืžืกื•ื™ื, ืืœื ื’ื ืชืžื ืข ืžืกืคืจ ื‘ืขื™ื•ืช ืืคืฉืจื™ื•ืช. + +**ืื—ืจืช:** ื˜ืขื™ื ืช ืžื•ื“ื•ืœื™ื ื”ื™ื ืชื”ืœื™ืš ืกื™ื ื›ืจื•ื ื™ ื‘-Node.js. ืื ื”ื˜ืขื™ื ื” ืชืชื‘ืฆืข ืžืชื•ืš ืคื•ื ืงืฆื™ื” ื”ื™ื ืขืœื•ืœื” ืœื—ืกื•ื ื˜ื™ืคื•ืœ ื‘ื‘ืงืฉื•ืช ืื—ืจื•ืช ื‘ื–ืžืŸ ืงืจื™ื˜ื™. ื‘ื ื•ืกืฃ ืœื›ืš, ืื ืžื•ื“ื•ืœ ื—ื™ื•ื ื™ ืื• ืžื™ืฉื”ื• ืฉื”ื•ื ืชืœื•ื™ ื‘ื• ื™ื–ืจืงื• ืฉื’ื™ืื” ื•ื™ืคื™ืœื• ืืช ื”ืฉืจืช, ืžื•ืžืœืฅ ืฉื–ื” ื™ื•ื•ื“ืข ื›ืžื” ืฉื™ื•ืชืจ ืžื•ืงื“ื, ืžื” ืฉืœื ื‘ื˜ื•ื— ื™ืงืจื” ื‘ืžืงืจื” ืฉื”ืžื•ื“ื•ืœ ื ื˜ืขืŸ ืžืชื•ืš ืคื•ื ืงืฆื™ื”. + +

+ +## ![โœ”] 3.9 ื”ื’ื“ื™ืจื• ื›ื ื™ืกื” ืžืกื•ื“ืจืช ืœืกืคืจื™ื” ืฉืœื›ื + +**ืืž;ืœืง:** ื‘ืขืช ืคื™ืชื•ื— ืžื•ื“ื•ืœ ืื• ืกืคืจื™ื”, ื”ื’ื“ื™ืจื• ืงื•ื‘ืฅ ื‘ืกื™ืก ืฉืžื™ื™ืฆื ืืช ื”ืงื•ื“ ื”ืžื™ื•ืขื“ ืœืฉื™ืžื•ืฉ ื—ื™ืฆื•ื ื™. ืžื ืขื• ืžื”ืžืฉืชืžืฉื™ื ืฉืœ ื”ืงื•ื“ ืฉืœื›ื ืืช ื”ืฆื•ืจืš ืœื™ื™ื‘ื ืงื‘ืฆื™ื ืฉื™ื•ืฉื‘ื™ื ืขืžื•ืง ืืฆืœื›ื ื•ืืช ื”ืฆื•ืจืš ืฉืœื”ื ืœื”ื‘ื™ืŸ ืืช ืžื‘ื ื” ื”ืงื‘ืฆื™ื ืฉืœื›ื. ื›ืืฉืจ ืขื•ื‘ื“ื™ื ื‘ืฉื™ื˜ืช commonjs (require), ื–ื” ื™ื›ื•ืœ ืœื”ื™ืขืฉื•ืช ืขืœ ื™ื“ื™ ืฉื™ืžื•ืฉ ื‘ืงื•ื‘ืฅ index.js ืฉื™ื•ืฉื‘ ื‘ืชื™ืงื™ื” ื”ืจืืฉื™ืช ืื• ื‘ื”ื’ื“ืจืช ื”ืฉื“ื” main ื‘ืงื•ื‘ืฅ package.json. ื›ืืฉืจ ืขื•ื‘ื“ื™ื ื‘ืฉื™ื˜ืช ESM (import), ืื ืงื•ื‘ืฅ package.json ืงื™ื™ื ื‘ืชื™ืงื™ื” ื”ืจืืฉื™ืช, ืื– ื”ืฉื“ื” "exports" ืžืืคืฉืจ ืืช ื”ื’ื“ืจืช ื”ืงื•ื‘ืฅ ื”ืจืืฉื™. ืืš ืื ืื™ืŸ ืงื•ื‘ืฅ package.json, ืื– ืฉื™ืžื•ืฉ ื‘ืงื•ื‘ืฅ index.js ื‘ืชื™ืงื™ื” ื”ืจืืฉื™ืช ื™ื™ืฆื ืืช ื›ืœ ื”ืคื•ื ืงืฆื™ื•ื ืœื™ื•ืช ืฉืžื™ื•ืขื“ืช ืœืฉื™ืžื•ืฉ ื—ื™ืฆื•ื ื™. + +**ืื—ืจืช:** ืงื™ื•ืžื• ืฉืœ ืงื•ื‘ืฅ ืจืืฉื™ ืจืฉืžื™ ืžืฉืžืฉ ื›ืžืžืฉืง ื—ื™ืฆื•ื ื™ ืฉืžืกืชื™ืจ ืืช ื”ื—ืœืงื™ื ื”ืคื ื™ืžื™ื™ื ืฉืœ ื”ืกืคืจื™ื”, ืžืงืฉืจ ืืช ื”ืžืฉืชืžืฉ ื™ืฉื™ืจื•ืช ืœืงื•ื“ ื”ื–ืžื™ืŸ ื•ืžืืคืฉืจ ืฉื™ื ื•ื™ื™ื ืขืชื™ื“ื™ื™ื ืœืœื ืฆื•ืจืš ืœืฉื‘ื•ืจืืช ื”ื—ื•ื–ื”. + +### ื“ื•ื’ืžื” + +```javascript +// Avoid: client has deep familiarity with the internals + +// Client code +const SMSWithMedia = require("./SMSProvider/providers/media/media-provider.js"); + +// Better: explicitly export the public functions + +//index.js, module code +module.exports.SMSWithMedia = require("./SMSProvider/providers/media/media-provider.js"); + +// Client code +const { SMSWithMedia } = require("./SMSProvider"); +``` + +

+ +## ![โœ”] 3.10 ื”ืฉืชืžืฉื• ื‘ืื•ืคืจื˜ื•ืจ `===` + +**ืืž;ืœืง:** ื”ืขื“ื™ืคื• ืืช ื”ื”ืฉื•ื•ืื” ื”ืงืคื“ื ื™ืช ื‘ืืžืฆืขื•ืช ื”ืื•ืคืจื˜ื•ืจ `===` ืขืœ ืคื ื™ ื”ื”ืฉื•ื•ืื” ื”ื—ืœืฉื” ื™ื•ืชืจ ื‘ืืžืฆืขื•ืช ื”ืื•ืคืจื˜ื•ืจ `==`. `==` ืžืฉื•ื•ื” ืฉื ื™ ืžืฉืชื ื™ื ืื—ืจื™ ื”ืžืจื” ืฉืœ ืฉื ื™ื”ื ืœืกื•ื’ ืžืฉืชื ื” ืื—ื“. ืื™ืŸ ื”ืžืจืช ืกื•ื’ื™ ืžืฉืชื ื™ื ื‘ืื•ืคืจื˜ื•ืจ `===`, ื•ืฉื ื™ ื”ืžืฉืชื ื™ื ื—ื™ื™ื‘ื™ื ืœื”ื™ื•ืช ืžืื•ืชื• ืกื•ื’ ื›ื“ื™ ืฉื™ื•ื›ืœื• ืœื”ื™ื•ืช ืฉื•ื•ื™ื. + +**ืื—ืจืช:** ืžืฉืชื ื™ื ื‘ืขืœื™ ืขืจื›ื™ื ืฉื•ื ื™ื ืขืœื•ืœื™ื ืœื”ื—ื–ื™ืจ `true` ื›ืืฉืจ ืžืฉื•ื•ื™ื ื‘ื™ื ื™ื”ื ื‘ืขื–ืจืช ื”ืื•ืคืจื˜ื•ืจ `==`. + +### ื“ื•ื’ืžืื•ืช + +```javascript +"" == "0"; // false +0 == ""; // true +0 == "0"; // true + +false == "false"; // false +false == "0"; // true + +false == undefined; // false +false == null; // false +null == undefined; // true + +" \t\r\n " == 0; // true +``` + +ื›ืœ ื”ื”ืฉื•ื•ืื•ืช ืœืขื™ืœ ื™ื—ื–ื™ืจื• `false` ื‘ืขืช ื”ืฉื•ื•ืื” ืขื `===`. + +

+ +## ![โœ”] 3.11 ื”ืฉืชืžืฉื• ื‘-Async Await, ื”ืžื ืขื• ืž-callbacks + +**ืืž;ืœืง:** async-await ื–ื• ื”ื“ืจืš ื”ืคืฉื•ื˜ื” ื‘ื™ื•ืชืจ ืœื›ืชื•ื‘ ืงื•ื“ ืืกื™ื ื›ืจื•ื ื™ ืฉื™ืจื’ื™ืฉ ื›ืžื• ืงื•ื“ ืกื™ื ื›ืจื•ื ื™. ื”ืงื•ื“ ืฉื™ื›ืชื‘ ื‘ืฉื™ื˜ืช async-await ื”ื•ื ื’ื ื”ืจื‘ื” ื™ื•ืชืจ ืคืฉื•ื˜ ื•ืชื•ืžืš ื‘ืžื ื’ื ื•ืŸ ื”-try-catch. ืฉื™ื˜ื” ื–ื• ืžื—ืœื™ืคื” ืืช ื”ืฆื•ืจืš ื‘-callbacks ื•-promises ื‘ืจื•ื‘ ื”ืžืงืจื™ื. ืฉื™ืžื•ืฉ ื‘ืฉื™ื˜ื” ื–ื• ื‘ืงื•ื“ ื”ื™ื ื›ื ืจืื” ืื—ืช ื”ืžืชื ื•ืช ื”ื˜ื•ื‘ื•ืช ื™ื•ืชืจ ืฉืืคืฉืจ ืœืชืช ืœืžื™ ืฉื™ืงืจื ืืช ื”ืงื•ื“. + +**ืื—ืจืช:** ื˜ื™ืคื•ืœ ื‘ืฉื’ื™ืื•ืช ืืกื™ื ื›ืจื•ื ื™ื•ืช ื‘ืฉื™ื˜ืช callback ื”ื™ื ื›ื ืจืื” ื”ื“ืจืš ื”ืžื”ื™ืจื” ืœื’ื”ื ื•ื - ืžื›ื™ื•ื•ืŸ ืฉืฉื™ื˜ื” ื–ื• ืžื—ื™ื™ื‘ืช ื‘ื“ื™ืงืช ืฉื’ื™ืื•ืช ื‘ื›ืœ ืฉืœื‘, ื™ื•ืฆืจืช ืงื™ื ื•ืŸ ืžื•ื–ืจ ื‘ืงื•ื“ ื•ืžืงืฉื” ืขืœ ื”ื‘ื ืช ืชื”ืœื™ืš ื”ื–ืจื™ืžื” ืฉืœ ื”ืงื•ื“. + +๐Ÿ”—[**ืœืงืจื™ืื” ื ื•ืกืคืช:** ืžื“ืจื™ืš ืœ-async-await](https://github.com/yortus/asyncawait) + +

+ +## ![โœ”] 3.12 ื”ืฉืชืžืฉื• ื‘ืคื•ื ืงืฆื™ื•ืช ื—ืฅ (=>) + +**ืืž;ืœืง:** ืืžื ื ืžื•ืžืœืฅ ืœื”ืฉืชืžืฉ ื‘ async-await ื•ืœื”ื™ืžื ืข ืžื”ื’ื“ืจืช ืคืจืžื˜ืจื™ื ื‘ืคื•ื ืงืฆื™ื•ืช ื›ืืฉืจ ืžืชืขืกืงื™ื ืขื API ื™ืฉืŸ ืฉืชื•ืžืš ื‘-callbacks ืื• ื”ื‘ื˜ื—ื•ืช - ืคื•ื ืงืฆื™ื•ืช ื—ืฅ ืžืืคืฉืจื•ืช ืœืืจื’ืŸ ืืช ื”ืงื•ื“ ืงื•ืžืคืงื˜ื™ ื™ื•ืชืจ ื•ื›ืžื•ื‘ืŸ ืฉืฉื•ืžืจื•ืช ืขืœ ื”ืงื•ื ื˜ืงืกื˜ ืฉืœ ืคื•ื ืงืฆื™ืช ื”ืžืขื˜ืคืช (`this`). + +**ืื—ืจืช:** ืงื•ื“ ืืจื•ืš ื™ื•ืชืจ (ืขืœ ื‘ืกื™ืก ืคื•ื ืงืฆื™ื•ืช ืฉืœ ES5) ื—ืฉื•ืฃ ืœื™ื•ืชืจ ื‘ืื’ื™ื ื•ืงืฉื” ื™ื•ืชืจ ืœืงืจื™ืื”. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื’ื™ืข ื”ื–ืžืŸ ืœืืžืฅ ืืช ืคื•ื ืงืฆื™ื•ืช ื”ื—ืฅ**](https://medium.com/javascript-scene/familiarity-bias-is-holding-you-back-its-time-to-embrace-arrow-functions-3d37e1a9bb75) + +

+ +## ![โœ”] 3.13 ื”ื™ืžื ืขื• ืžื”ืฉืคืขื•ืช ืฆื“ื“ื™ื•ืช ืžื—ื•ืฅ ืœืคื•ื ืงืฆื™ื•ืช + +**ืืž;ืœืง:** ื”ื™ืžื ืขื• ืžื›ืชื™ื‘ืช ืงื•ื“ ืขื ื”ืฉืคืขื•ืช ืฆื“ื“ื™ื•ืช ื›ืžื• ืคืขื•ืœืช ืจืฉืช ืื• ืคื ื™ื” ืœืžืกื“ ื ืชื•ื ื™ื ืžื—ื•ืฅ ืœืคื•ื ืงืฆื™ื”. ืื ื›ืŸ ืชื›ืชื‘ื• ืงื•ื“ ื›ื–ื” ื”ื•ื ื™ืจื•ืฅ ืžื™ื“ ื›ืืฉืจ ืงื•ื‘ืฅ ืื—ืจ ืคื•ื ื” ืœืงื•ื‘ืฅ ื”ื–ื”. ื”ืงื•ื“ 'ื”ืฆืฃ' ื”ื–ื” ืขืœื•ืœ ืœืจื•ืฅ ื›ืืฉืจ ื”ืชืฉืชื™ืช ืื•ืชื” ื”ื•ื ืžื‘ืงืฉ ืขื•ื“ ืœื ื–ืžื™ื ื” ืขื‘ื•ืจื•. ื–ื” ื’ื ืคื•ื’ืข ื‘ื‘ื™ืฆื•ืขื™ื ืืคื™ืœื• ืื ืื™ืŸ ืฆื•ืจืš ื‘ืคื•ื ืงืฆื™ื” ืฉืขื‘ื•ืจื” ืžืชื‘ืฆืขืช ื”ืคืขื•ืœื” ื‘ื–ืžืŸ ื”ืจื™ืฆื”. ื“ื‘ืจ ืื—ืจื•ืŸ, ื›ืชื™ื‘ืช ื›ื™ืกื•ื™ ืœืคืขื•ืœื” ื–ื• ื‘ืฉื‘ื™ืœ ื‘ื“ื™ืงื•ืช ื”ืจื‘ื” ื™ื•ืชืจ ืžื•ืจื›ื‘ืช ื›ืฉื”ื™ื ืœื ื ืขืฉื™ืช ื‘ืคื•ื ืงืฆื™ื”. ื‘ืžืงื•ื ื–ืืช, ืฉื™ืžื• ืืช ื”ืงื•ื“ ื”ื–ื” ื‘ืคื•ื ืงืฆื™ื” ืฉืฆืจื™ื›ื” ืœื”ื™ืงืจื ื‘ืžืคื•ืจืฉ. ืื ื”ืงื•ื“ ื”ื–ื” ืฆืจื™ืš ืœื”ื™ืงืจื ื™ืฉืจ ื‘ืขืช ืขืœื™ื™ืช ื”ืžืขืจื›ืช, ืฉื™ืงืœื• ืฉื™ืžื•ืฉ ื‘-factory ืื• ื‘ืชื‘ื ื™ืช ืื—ืจืช ืฉืžืชืื™ืžื” ืœื“ืจื™ืฉื” ื›ื–ืืช. + +**ืื—ืจืช:** ืชืฉืชื™ื•ืช ืกื˜ื ื“ืจื˜ื™ื•ืช ื‘ืขื•ืœื ื”ื•ื•ื‘ ืžื’ื“ื™ืจื•ืช ื ื™ื”ื•ืœ ืฉื’ื™ืื•ืช, ืžืฉืชื ื™ ืกื‘ื™ื‘ื” ื•ื ื™ื˜ื•ืจ ืชืงืœื•ืช. ืื ื”ืคืขื•ืœื” ืชืชื‘ืฆืข ืœืคื ื™ ืฉื”ืชืฉืชื™ืช ืžืื•ืชื—ืœืช ืื– ืœื ื™ื”ื™ื” ื ื™ื˜ื•ืจ ืฉืœ ื”ืžืงืจื” ืื• ืฉื”ืคืขื•ืœื” ืชื™ื›ืฉืœ ื‘ืฉืœ ื—ื•ืกืจ ื‘ื”ื’ื“ืจื•ืช ืฉื˜ืจื ื ื˜ืขื ื•. + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# `4. ื‘ื“ื™ืงื•ืช ื•ื‘ืงืจืช ืื™ื›ื•ืช` + +> ื™ืฉ ืœื ื• ืžื“ืจื™ื›ื™ื ื™ืขื•ื“ื™ื™ื ืœื›ืชื™ื‘ืช ื‘ื“ื™ืงื•ืช. ืจืฉื™ืžืช ืฉื™ื˜ื•ืช ื”ืขื‘ื•ื“ื” ื”ืžื•ืžืœืฆื•ืช ืคื” ื”ื™ื ืกื™ื›ื•ื ื›ืœืœื™ ืฉืœ ื”ืžื“ืจื™ื›ื™ื ื”ืœืœื•. +> +> ื. [ืฉื™ื˜ื•ืช ืขื‘ื•ื“ื” ืžื•ืžืœืฆื•ืช ื‘ื›ืชื™ื‘ืช ื‘ื“ื™ืงื•ืช ืœ-JavaScript](https://github.com/goldbergyoni/javascript-testing-best-practices)
+> ื‘. [ื‘ื“ื™ืงื•ืช ื‘-Node.js - ืžืขื‘ืจ ืœื™ืกื•ื“ื•ืช](https://github.com/testjavascript/nodejs-integration-tests-best-practices) + + +## ![โœ”] 4.1 ืœืคื—ื•ืช, ื›ื™ืชื‘ื• ื‘ื“ื™ืงื•ืช API ืœืจื›ื™ื‘ื™ื ื”ืฉื•ื ื™ื + +**ืืž;ืœืง:** ื‘ืจื•ื‘ ื”ืคืจื•ื™ืงื˜ื™ื ืื™ืŸ ื‘ื“ื™ืงื•ืช ืื•ื˜ื•ืžื˜ื™ื•ืช ื›ืœืœ ื‘ืฉืœ ืœื•ื— ื–ืžื ื™ื ืงืฆืจ, ืื• ืฉื”ืชื—ื™ืœื• ืœื ืกื•ืช ืœื”ื•ืกื™ืฃ ื‘ื“ื™ืงื•ืช ื‘ืคืจื•ื™ืงื˜ ื ื•ืกืฃ ืืš ื–ื” ื™ืฆื ืžืฉืœื™ื˜ื” ื•ื ื ื˜ืฉ ืขื ื”ื–ืžืŸ. ืœื›ืŸ, ืœืชืขื“ืฃ ื•ืœื”ืชื—ื™ืœ ื‘ื“ื™ืงื•ืช API ืฉื–ืืช ื”ื“ืจืš ื”ืงืœื” ืœื›ืชื•ื‘ ื‘ื“ื™ืงื•ืช ื•ืœืกืคืง ื›ื™ืกื•ื™ (ื‘ื“ื™ืงื•ืช) ืฉืœ ื”ืงื•ื“ ืžืืฉืจ ื‘ื‘ื“ื™ืงื•ืช ื™ื—ื™ื“ื” ืฉืœ ืคื•ื ืงืฆื™ื•ืช ื‘ื•ื“ื“ื•ืช (ืืคืฉืจ ืœื”ืฉืชืžืฉ ื‘ืฉื‘ื™ืœ ื–ื” ื’ื ื‘ื›ืœื™ื ื—ื™ืฆื•ื ื™ื™ื ืœืœื ื›ืชื™ื‘ืช ืงื•ื“, ืœืžืฉืœ ืฉื™ืžื•ืฉ ื‘-[Postman](https://www.getpostman.com/)). ืœืื—ืจ ืžื›ืŸ, ืื ื™ืฉ ืœื›ื ื™ื•ืชืจ ืžืฉืื‘ื™ื ื•ื–ืžืŸ ืชืžืฉื™ื›ื• ืขื ื‘ื“ื™ืงื•ืช ืžืชืงื“ืžื•ืช ื™ื•ืชืจ ื›ื’ื•ืŸ ื‘ื“ื™ืงื•ืช ื™ื—ื™ื“ื”, ื‘ื“ื™ืงื•ืช ืžื•ืœ ืžืกื“ื™ ื”ื ืชื•ื ื™ื ื‘ื“ื™ืงื•ืช ื‘ื™ืฆื•ืขื™ื ื•ืขื•ื“. + +**ืื—ืจืช:** ืืชื ืขืœื•ืœื™ื ืœื‘ื–ื‘ื– ื™ืžื™ื ืฉืœืžื™ื ืขืœ ื›ืชื™ื‘ืช ื‘ื“ื™ืงื•ืช ื™ื—ื™ื“ื” ื‘ืœื‘ื“ ื•ืœื’ืœื•ืช ื‘ืกื•ืคื• ืฉืœ ื“ื‘ืจ ืฉื›ื™ืกื™ืชื ืจืง 20% ืžื”ืžืขืจื›ืช. + +

+ +## ![โœ”] 4.2 ืกื•ื•ื’ื• 3 ื—ืœืงื™ื ื‘ืžืชืŸ ืฉื ืœื›ืœ ื‘ื“ื™ืงื” + +**ืืž;ืœืง:** ื’ื™ืจืžื• ืœื‘ื“ื™ืงื” ืœืชืืจ ืืช ืฉืœื‘ ื”ื“ืจื™ืฉื•ืช ื›ืš ืฉื”ื™ื ืชืกื‘ื™ืจ ืืช ืขืฆืžื” ื’ื ืœQA ืื• ืœืื—ืจื™ื (ื›ื•ืœืœ ืืชื›ื ื‘ืขืชื™ื“ ื”ืœื ืจื—ื•ืง) ืฉืœื ื‘ืงื™ืื™ื ื‘ื—ืœืงื™ื ื”ืคื ื™ืžื™ื™ื ืฉืœ ื”ืงื•ื“. ืฆื™ื™ื ื• ื‘ื‘ื“ื™ืงื” (1) ืื™ื–ื” ื—ืœืง ื ื‘ื“ืง, (2) ื‘ืื™ืœื• ืชื ืื™ื (3) ื•ืžื” ื”ืชื•ืฆืื” ืฉืžืฆืคื™ื ืฉืชื—ื•ืœ. + +**ืื—ืจืช:** ื”ื”ืชืงื ื” ื‘ื“ื™ื•ืง ื ื›ืฉืœื”, ื‘ื“ื™ืงื” ื‘ืฉื โ€œAdd productโ€ ื ื›ืฉืœื”. ื”ืื ื–ื” ืžืชืืจ ืžื” ื‘ื“ื™ื•ืง ืœื ืชื™ืคืงื“? + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืกื•ื•ื’ื• 3 ื—ืœืงื™ื ื‘ืžืชืŸ ืฉื ืœื›ืœ ื‘ื“ื™ืงื”**](./sections/testingandquality/3-parts-in-name.md) + +

+ +## ![โœ”] 4.3 ื—ืœืงื• ืืช ื”ื‘ื“ื™ืงื•ืช ืœืคื™ ืชื‘ื ื™ืช ื”-AAA + +**ืืž;ืœืง:** ื—ืœืงื• ืืช ื”ื‘ื“ื™ืงื•ืช ืœืฉืœื•ืฉื” ื—ืœืงื™ื ื ืคืจื“ื™ื: Arrange (ืืจื’ืŸ), Act (ืคืขืœ) & Assert (ื•ื“ื) (AAA). ื”ื—ืœืง ื”ืจืืฉื•ืŸ ื›ื•ืœืœ ืืช ื”ื”ื›ื ื” ืฉืœ ื”ืกื‘ื™ื‘ื” ืœื‘ื“ื™ืงื”, ื”ื—ืœืง ื”ืฉื ื™ ืืช ื”ื”ืจืฆื” ื‘ืžืฆื‘ ื‘ื“ื™ืงื•ืช, ื•ืœื‘ืกื•ืฃ ื”ื—ืœืง ืฉืžื•ื•ื“ื ืฉื”ืชืงื‘ืœื” ื”ืชื•ืฆืื” ื”ืจืฆื•ื™ื”. ืฉื™ืžื•ืฉ ื‘ืžื‘ื ื” ื–ื” ื‘ืขืงื‘ื™ื•ืช ืžื‘ื˜ื™ื— ืฉื”ืงื•ืจื ืœื ื™ื‘ื–ื‘ื– ื–ืžืŸ ืžื—ืฉื‘ื” ืฉืœ ื”ื‘ื ืช ื”ื‘ื“ื™ืงื”. + +**ืื—ืจืช:** ืœื ืžืกืคื™ืง ืฉื™ืชื‘ื–ื‘ื– ื–ืžืŸ ื ืจื—ื‘ ืžื”ื™ื•ื ืขืœ ื”ื‘ื ืช ื”ืงื•ื“, ืขื›ืฉื™ื• ื’ื ื”ื—ืœืง ื”ืงืœ ื‘ื™ื•ื (ื”ื‘ื ืช ื”ื‘ื“ื™ืงื•ืช) ื™ืฉืจื•ืฃ ืืช ื”ืžื•ื—. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื—ืœืงื• ืืช ื”ื‘ื“ื™ืงื•ืช ืœืคื™ ืชื‘ื ื™ืช ื”-AAA**](./sections/testingandquality/aaa.md) + +

+ +## ![โœ”] 4.4 ื•ื•ื“ืื• ื›ื™ ื’ืจืกืช ื”-Node ืื—ื™ื“ื” + +**ืืž;ืœืง:** ื”ืฉืชืžืฉื• ื‘ื›ืœื™ื ื”ืžืขื•ื“ื“ื™ื ืื• ืื•ื›ืคื™ื ืฉื™ืžื•ืฉ ื‘ืื•ืชื” ื’ืจืกืช Node.js ื‘ืกื‘ื™ื‘ื•ืช ื”ืฉื•ื ื•ืช ื•ืขืœ ื™ื“ื™ ืฉืืจ ื”ืžืคืชื—ื™ื. ื›ืœื™ื ื›ืžื• [nvm](https://github.com/nvm-sh/nvm), ื•-[Volta](https://volta.sh/) ืžืืคืฉืจื™ื ืœื”ื’ื“ื™ืจ ื‘ืžืคื•ืจืฉ ืืช ื”ื’ืจืกื” ื”ื ื“ืจืฉืช ื‘ืคืจื•ื™ืงื˜ ื‘ืงื•ื‘ืฅ ื›ืš ืฉื›ืœ ื—ื‘ืจื™ ื”ืฆื•ื•ืช ื™ื›ื•ืœื™ื ืขืœ ื™ื“ื™ ื”ืจืฆืช ืคืงื•ื“ื” ืื—ืช ืœื™ื™ืฉืจ ืงื• ืขื ื’ืจืกืช ื”ืคืจื•ื™ืงื˜. ื™ืฉื ื” ืืคืฉืจื•ืช ืฉื’ืจืกื” ื–ื• ื’ื ืชืฉืชืงืฃ ืœืชื”ืœื™ืš ื”-CI ื•ืกื‘ื™ื‘ืช ื”ื™ืฆื•ืจ/ืœืงื•ื—ื•ืช (ืœื“ื•ื’ืžื” ืขืœ ื™ื“ื™ ื”ืขืชืงืช ืžืกืคืจ ื”ื’ืจืกื” ื”ืžื‘ื•ืงืฉ ืœ-`.Dockerfile` ื•ืœืงื‘ืฆื™ ื”ื”ื’ื“ืจื•ืช ืฉืœ ืชื”ืœื™ืš ื”-CI). + +**ืื—ืจืช:** ืžืคืชื—ืช ืขืœื•ืœื” ืœื”ื™ืชืงืœ ืื• ืœืคืกืคืก ืฉื’ื™ืื” ืžื›ื™ื•ื•ืŸ ืฉื”ื™ื ืžืฉืชืžืฉืช ื‘ื’ืจืกืช Node.js ืฉื•ื ื” ืžืฉืืจ ื”ืฆื•ื•ืช. ืื• ื’ืจื•ืข ืžื›ืš, ืกื‘ื™ื‘ืช ื”ื™ืฆื•ืจ ืจืฆื” ื‘ืืžืฆืขื•ืช ื’ืจืกื” ืฉื•ื ื” ืžื–ื• ืฉื”ื•ืจืฆื• ืขืœื™ื” ื”ื‘ื“ื™ืงื•ืช. + +

+ +## ![โœ”] 4.5 ื”ื™ืžื ืขื• ืžืืชื—ื•ืœ ืžื™ื“ืข ื’ืจืขื™ื ื™ ืžืฉื•ืชืฃ, ื”ื’ื“ื™ืจื• ืœืคื™ ืฆื•ืจืš ืฉืœ ื‘ื“ื™ืงื” + +**ืืž;ืœืง:** ื›ื“ื™ ืœื”ื™ืžื ืข ืžืฆืžื™ื“ื•ืช ื•ืชืœื•ืช ื‘ื™ืŸ ื‘ื“ื™ืงื•ืช ืฉื•ื ื•ืช ื•ื›ื“ื™ ืฉื™ื”ื™ื” ื‘ืจื•ืจ ื™ื•ืชืจ ืื™ืš ืœื”ืกื‘ื™ืจ ืžื” ืงื•ืจื” ื‘ืฉืœื‘ื™ื ื”ืฉื•ื ื™ื ืฉืœ ื”ื‘ื“ื™ืงื”, ืจืื•ื™ ืฉื›ืœ ื‘ื“ื™ืงื” ืชื•ืกื™ืฃ ื•ืชื ื”ืœ ืืช ื”ืžื™ื“ืข ื”ืขื•ื˜ืฃ ืฉืœื” (ืœืžืฉืœ ืฉื•ืจื•ืช ื‘ื˜ื‘ืœื”). ื‘ืžืงืจื” ื•ื‘ื“ื™ืงื” ืฆืจื™ื›ื” ืœืฆืจื•ืš ืžื™ื“ืข ืžื˜ื‘ืœื” ืื• ืœื”ื ื™ื— ืฉื”ื•ื ืงื™ื™ื ืฉื - ื”ื™ื ืฆืจื™ื›ื” ืงื•ื“ื ืœื›ืŸ ืœื”ื•ืกื™ืฃ ืืช ื”ืžื™ื“ืข ื‘ืžืคื•ืจืฉ ื•ืœื”ื™ืžื ืข ืžืฉื™ื ื•ื™ ืžื™ื“ืข ืฉืœ ื‘ื“ื™ืงื” ืื—ืจืช. + +**ืื—ืจืช:** ืชืืจื• ืœื›ื ืžืงืจื” ื‘ื• ื”ืคืฆืช ื’ืจืกื” ื ื›ืฉืœื” ื‘ืฉืœ ืฉื’ื™ืื” ื‘ื‘ื“ื™ืงื•ืช, ื”ืฆื•ื•ืช ืžืฉื ืก ืžื•ืชื ื™ื™ื ืœื—ืงื•ืจ ืืช ื”ืกื™ื‘ื” ื•ืžื’ื™ืข ืื ื”ืชื•ื‘ื ื” ื”ืขืฆื•ื‘ื” ืฉื”ืžืขืจื›ืช ืขื•ื‘ื“ืช ืชืงื™ืŸ ืื‘ืœ ื”ื‘ื“ื™ืงื•ืช ื“ื•ืจืกื•ืช ืžื™ื“ืข ืื—ืช ืœืฉื ื™ื” ื•ืœื›ืŸ ื ื›ืฉืœื• ื•ืขืฆืจื• ืืช ืชื”ืœื™ืš ื”ื”ืคืฆื”. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื™ืžื ืขื• ืžืืชื—ื•ืœ ืžื™ื“ืข ื’ืจืขื™ื ื™ ืžืฉื•ืชืฃ**](./sections/testingandquality/avoid-global-test-fixture.md) + +

+ +## ![โœ”] 4.6 ืชื™ื™ื’ื• ืืช ื”ื‘ื“ื™ืงื•ืช + +**ืืž;ืœืง:** ื‘ื“ื™ืงื•ืช ืฉื•ื ื•ืช ืฆืจื™ื›ื•ืช ืœืจื•ืฅ ื‘ืชืจื—ื™ืฉื™ื ืฉื•ื ื™ื: ื‘ื“ื™ืงื•ืช ืฉืคื™ื•ืช (quick smoke/sanity), IO-less, ื‘ื“ื™ืงื•ืช ื‘ืขืช ืฉืžื™ืจืช ืงื•ื‘ืฅ ืื• commit, ื‘ื“ื™ืงื•ืช ืžืœืื•ืช ืžืงืฆื” ืœืงืฆื” (e2e) ื›ืืฉืจ ื ืคืชื— PR ื•ื›ื•ืœื™... ื”ืชืจื—ื™ืฉื™ื ื”ืฉื•ื ื™ื ื™ื›ื•ืœื™ื ืœื”ื™ื•ืช ืžื•ื’ื“ืจื™ื ื‘ืขื–ืจืช ืชื™ื•ื’ ื‘ื“ื™ืงื•ืช ืฉื•ื ื•ืช ืขื ืžื™ืœื•ืช ืžืคืชื— ื›ืžื• #cold #api #sanity ื“ื‘ืจ ื”ืžืืคืฉืจ ืœื”ื’ื“ื™ืจ ืงื‘ื•ืฆืช ื‘ื“ื™ืงื•ืช ื‘ื”ืชืื ืœืฆื•ืจืš ื•ืœื”ืจื™ืฅ ืจืง ืื•ืชื”. ืœืžืฉืœ, ื–ืืช ื”ืฉื™ื˜ื” ืœื”ืจื™ืฅ ืจืง ืืช ืงื‘ื•ืฆืช ื‘ื“ื™ืงื•ืช ื”ืฉืคื™ื•ืช ื‘ืืžืฆืขื•ืช [Mocha](https://mochajs.org/): `mocha --grep 'sanity'`. + +**ืื—ืจืช:** ื”ืจืฆื” ืฉืœ ื›ืœ ื”ื‘ื“ื™ืงื•ืช ื›ื•ืœืœ ื›ืืœื• ืฉืžื‘ืฆืขื•ืช ืขืฉืจื•ืช ืคื ื™ื•ืช ืœืžืกื“ ื ืชื•ื ื™ื ื‘ื›ืœ ืคืขื ืฉืžืคืชื— ืขื•ืฉื” ืฉื™ื ื•ื™ ืงื˜ืŸ ื™ืื˜ ืืช ืงืฆื‘ ื”ืคื™ืชื•ื— ื‘ืฆื•ืจื” ื ื™ื›ืจืช ื•ืชืžื ืข ืžืฆื•ื•ืช ื”ืคื™ืชื•ื— ืœื”ืจื™ืฅ ื‘ื“ื™ืงื•ืช. + +

+ +## ![โœ”] 4.7 ื‘ื™ื“ืงื• ืืช ืจืžืช ื›ื™ืกื•ื™ ื”ื‘ื“ื™ืงื•ืช ืฉืœื›ื, ื–ื” ื™ืขื–ื•ืจ ืœื–ื”ื•ืช ื“ืคื•ืกื™ ื‘ื“ื™ืงื•ืช ืฉื’ื•ื™ื™ื + +**ืืž;ืœืง:** ื›ืœื™ื ืœื‘ื“ื™ืงืช ื›ื™ืกื•ื™ ื”ืงื•ื“ ืขืœ ื™ื“ื™ ื‘ื“ื™ืงื•ืช ื›ืžื• [Istanbul](https://github.com/istanbuljs/istanbuljs)/[NYC](https://github.com/istanbuljs/nyc) ืžืฆื•ื™ื ื™ื ื‘ืฉืœ ืฉืœื•ืฉ ืกื™ื‘ื•ืช: ื”ื ื‘ื—ื™ื ื (ืื™ืŸ ืขืœื•ืช ืœื“ื•"ื—ื•ืช ืฉื”ื ืžืกืคืงื™ื), ื”ื ืขื•ื–ืจื™ื ืœื–ื”ื•ืช ื™ืจื™ื“ื” ื‘ืื—ื•ื–ื™ ื”ื›ื™ืกื•ื™, ื•ืื—ืจื•ืŸ ื—ื‘ื™ื‘ ื”ื ืžื“ื’ื™ืฉื™ื ืžืงืจื™ื ืฉืœ ืื™ ื”ืชืืžื” ื‘ื‘ื“ื™ืงื•ืช: ืขืœ ื™ื“ื™ ืฆืคื™ื™ื” ื‘ืฆื‘ืขื™ื ืฉื”ื“ื•ื—ื•ืช ื”ืœืœื• ืžืกืคืงื™ื ืืคืฉืจ ืœื–ื”ื•ืช ืœืžืฉืœ ืฉื™ืฉ ืงื˜ืขื™ ืงื•ื“ ืฉืœื ื ื‘ื“ืงื™ื ืœืขื•ืœื ื›ืžื• ื”ืกืชืขืคื•ื™ื•ืช ืฉืœ `catch` (ืžื” ืฉืื•ืžืจ ืฉื™ืฉ ื‘ื“ื™ืงื•ืช ืจืง ืœืžืกืœื•ืœ ื”ืžืฆืœื™ื— ื•ืœื ืœืžืงืจื™ื ืฉืœ ื”ืฉื’ื™ืื•ืช). ืจืฆื•ื™ ืœื”ื’ื“ื™ืจ ืืช ื–ื” ื›ืš ืฉื–ื” ื™ืคื™ืœ ืืช ืชื”ืœื™ื›ื™ ื™ืฆื™ืจืช ื”ื’ืจืกืื•ืช ื‘ืžื™ื“ื” ื•ื”ื›ื™ืกื•ื™ ืœื ืขื•ื‘ืจ ืกืฃ ืžืกื•ื™ื. + +**ืื—ืจืช:** ืœื ื™ื”ื™ื” ืฉื•ื ืืžืฆืขื™ ืžื“ื™ื“ื” ืฉื™ื“ื•ื•ื— ืฉืงื˜ืขื™ื ื ืจื—ื‘ื™ื ืžื”ืงื•ื“ ืœื ื ื‘ื“ืงื™ื ื›ืœืœ. + +

+ +## ![โœ”] 4.8 Use production-like environment for e2e testing + +**ืืž;ืœืง:** End to end (e2e) testing which includes live data used to be the weakest link of the CI process as it depends on multiple heavy services like DB. Use an environment which is as close to your real production environment as possible like a-continue (Missed -continue here, needs content. Judging by the **Otherwise** clause, this should mention docker-compose) + +**ืื—ืจืช:** Without docker-compose, teams must maintain a testing DB for each testing environment including developers' machines, keep all those DBs in sync so test results won't vary across environments + +

+ +## ![โœ”] 4.9 ืฉื›ืชื‘ื• ืืช ื”ืงื•ื“ ื‘ืื•ืคืŸ ืงื‘ื•ืข ื‘ืขื–ืจืช ื›ืœื™ ื ื™ืชื•ื— ืกื˜ื˜ื™ + +**ืืž;ืœืง:** ืฉื™ืžื•ืฉ ื‘ื›ืœื™ ื ื™ืชื•ื— ืกื˜ื˜ื™ (static analysis tools) ืขื•ื–ืจ ื‘ื›ืš ืฉื”ื•ื ื ื•ืชืŸ ื“ืจื›ื™ื ืžืชืื™ืžื•ืช ืœืฉืคืจ ืืช ืื™ื›ื•ืช ื”ืงื•ื“ ื•ืœืฉืžื•ืจ ืขืœ ื”ืงื•ื“ ืžืชื•ื—ื–ืง. ืืคืฉืจ ืœื”ื•ืกื™ืฃ ื›ืœื™ื ื›ืืœื• ืœืฉืœื‘ื™ ื”ื‘ื ื™ื™ื” ื‘-CI ื›ืš ืฉื™ืคื™ืœื• ืืช ื”ืชื”ืœื™ืš ื‘ืžื™ื“ื” ื•ื”ื ืžื–ื”ื™ื ื ื™ื—ื•ื—ื•ืช ื‘ืงื•ื“. ืื—ื“ ื”ื™ืชืจื•ื ื•ืช ื”ืขื™ืงืจื™ื™ื ืฉืœื”ื ืขืœ ืคื ื™ ื›ืœื™ื ืคืฉื•ื˜ื™ื ื™ื•ืชืจ ื”ื•ื ื”ื™ื›ื•ืœืช ืœื–ื”ื•ืช ืคื’ืžื™ื ื‘ืื™ื›ื•ืช ื”ืงื•ื“ ืขืœ ืคื ื™ ืžืกืคืจ ืงื‘ืฆื™ื (ื›ืžื• ื›ืคืœ ืงื•ื“), ืžื•ืจื›ื‘ื•ืช ื’ื‘ื•ื”ื” ืฉืœ ืงื•ื“ ื•ืžืขืงื‘ ืื—ืจื™ ื”ื”ื™ืกื˜ื•ืจื™ื” ื•ื”ื”ืชืงื“ืžื•ืช ืฉืœ ื”ืงื•ื“. ืฉื ื™ ื›ืœื™ื ืžื•ืžืœืฆื™ื ืœืฉื™ืžื•ืฉ ื”ื [Sonarqube](https://www.sonarqube.org/) (7,900+ [stars](https://github.com/SonarSource/sonarqube)) ื• [Code Climate](https://codeclimate.com/) (2,400+ [stars](https://github.com/codeclimate/codeclimate)). + +**ืื—ืจืช:** ืื ื”ืงื•ื“ ื‘ืื™ื›ื•ืช ื ืžื•ื›ื”, ืชืงืœื•ืช ื•ื‘ืขื™ื•ืช ื‘ื™ืฆื•ืขื™ื ืชืžื™ื“ ื™ื”ื•ื• ืืชื’ืจ ืฉืืฃ ืกืคืจื™ื” ื—ื“ืฉื” ื•ื ื•ืฆืฆืช ืื• ืคืชืจื•ืŸ ื˜ื›ื ื•ืœื•ื’ื™ ื—ื“ื™ืฉ ื™ื•ื›ืœื• ืœืคืชื•ืจ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืฉื›ืชื•ื‘!**](./sections/testingandquality/refactoring.md) + +

+ +## ![โœ”] 4.10 ื”ื“ืžื™ื™ืช ืชืฉื•ื‘ื•ืช ืฉืœ ืฉืจืชื™ HTTP ื—ื™ืฆื•ื ื™ื™ื + +**ืืž;ืœืง:** ื”ืฉืชืžืฉื• ื‘ื›ืœื™ ื”ื“ืžื™ื” ืฉืœ ื”ืžื™ื“ืข ืฉืžื’ื™ืข ืžื”ืจืฉืช ืขื‘ื•ืจ ืชืฉื•ื‘ื•ืช ืฉืžื’ื™ืขื•ืช ืžืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื (ื›ืžื• ื‘ืงืฉื•ืช REST ื• GraphQL). ื–ื” ื”ื›ืจื—ื™ ืœื ืจืง ื›ื“ื™ ืœื‘ื•ื“ื“ ืืช ื”ืจื›ื™ื‘ ืฉื ื‘ื“ืง ืืœื ื‘ืขื™ืงืจ ื›ื“ื™ ืœื‘ื“ื•ืง ืžืฆื‘ื™ื ืœื ืฆืคื•ื™ื™ื. ื›ืœื™ื ื›ืžื• [nock](https://github.com/nock/nock) ืื• [Mock-Server](https://www.mock-server.com/) ืžืืคืฉืจื™ื ืœื”ื’ื“ื™ืจ ืชืฉื•ื‘ื” ืžืกื•ื™ืžืช ืœื‘ืงืฉื” ืœืฉื™ืจื•ืช ื—ื™ืฆื•ื ื™ ื‘ืฉื•ืจืช ืงื•ื“ ื‘ื•ื“ื“ื”. ื—ืฉื•ื‘ ืœื ืœืฉื›ื•ื— ืœื“ืžื•ืช ื’ื ืฉื’ื™ืื•ืช, ืขื™ื›ื•ื‘ื™ื, timeouts, ื•ื›ืœ ืื™ืจื•ืข ืื—ืจ ืฉื›ื ืจืื” ื™ืงืจื” ื‘ืกื‘ื™ื‘ืช ื”ื™ื™ืฆื•ืจ. + +**ืื—ืจืช:** ืœืืคืฉืจ ืœืจื›ื™ื‘ ืœื’ืฉืช ืœืžื™ื“ืข ืืžื™ืชื™ ืžืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื ื‘ื“ืจืš ื›ืœืœ ื™ืกืชื™ื™ื ื‘ื‘ื“ื™ืงื•ืช ืคืฉื•ื˜ื•ืช ืฉืžื›ืกื•ืช ื‘ืขื™ืงืจ ืืช ื”ืžืงืจื™ื ืฉื”ื›ืœ ื˜ื•ื‘. ื‘ื ื•ืกืฃ ืœื›ืš ื”ื‘ื“ื™ืงื•ืช ืœืคืขืžื™ื ื™ื›ืฉืœื• ื•ื™ื”ื™ื• ืื™ื˜ื™ื•ืช ื™ื•ืชืจ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื“ืžื™ื™ืช ืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื**](./sections/testingandquality/mock-external-services.md) + +

+ +## ![โœ”] 4.11 ื‘ื™ื“ืงื• ืืช ืคื•ื ืงืฆื™ื•ืช ื”ื‘ื™ื ื™ื™ื ื‘ื ืคืจื“ + +**ืืž;ืœืง:** ื›ืืฉืจ ืคื•ื ืงืฆื™ื™ืช ื‘ื™ื ื™ื™ื (middleware) ืื•ื—ื–ืช ื ืชื— ืžืฉืžืขื•ืชื™ ืฉืœ ืœื•ื’ื™ืงื” ืฉืžืฉืชืจืขืช ืขืœ ืคื ื™ ืžืกืคืจ ืขืฆื•ื ืฉืœ ื‘ืงืฉื•ืช, ื›ื“ืื™ ืœื‘ื“ื•ืง ืื•ืชื” ื‘ืฆื•ืจื” ืžื‘ื•ื“ื“ืช ืœืœื ืฆื•ืจืš ืœื˜ืขื•ืŸ ืืช ื›ืœ ืชืฉืชื™ืช ื”ืคืจื™ื™ืžื•ื•ืจืง. ืืคืฉืจ ืœื”ืฉื™ื’ ืืช ื”ืคืขื•ืœื” ื”ื–ืืช ื‘ืงืœื•ืช ืขืœ ื™ื“ื™ ืขื˜ื™ืคื” ืื• ื”ื“ืžื™ื” ืฉืœ `{req, res, next}`. + +**ืื—ืจืช:** ื‘ืื’ ื‘ืคื•ื ืงืฆื™ื•ืช ื‘ื™ื ื™ื™ื ื‘-`express` === ื‘ืื’ ื‘ืจื•ื‘ ื”ืงืจื™ื˜ื™ ืฉืœ ื”ื‘ืงืฉื•ืช. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืœื‘ื“ื•ืง ืคื•ื ืงืฆื™ื•ืช ื‘ื™ื ื™ื™ื ื‘ื ืคืจื“**](./sections/testingandquality/test-middlewares.md) + +

+ +## ![โœ”] 4.12 ืงื‘ืขื• ืืช ื”ืคื•ืจื˜ ื‘ื™ื™ืฆื•ืจ, ื”ื’ื“ื™ืจื• ืืงืจืื™ ืœื‘ื“ื™ืงื•ืช + +**ืืž;ืœืง:** ื›ืืฉืจ ืžื‘ืฆืขื™ื ื‘ื“ื™ืงื•ืช ืžื•ืœ API, ื–ื” ืจืฆื•ื™ ื•ืืฃ ื ื”ื•ื’ ืœืืชื—ืœ ืืช ื”ืฉืจืช ื‘ืชื•ืš ื”ื‘ื“ื™ืงื•ืช. ืชื ื• ืœืฉืจืช ืœื‘ื—ื•ืจ ืคื•ืจื˜ ื‘ืื•ืคืŸ ืืงืจืื™ ื›ืืฉืจ ืžืจื™ืฆื™ื ื‘ื“ื™ืงื•ืช ื›ื“ื™ ืœืžื ื•ืข ื”ืชื ื’ืฉื•ื™ื•ืช. ืื ืืชื ืžืฉืชืžืฉื™ื ื‘ืฉืจืช HTTP ืฉืœ Node.js (ื‘ืฉื™ืžื•ืฉ ืขืœ ื™ื“ื™ ืจื•ื‘ ืกืคืจื™ื•ืช ื”ืชืฉืชื™ืช), ื›ื“ื™ ืœื”ืฉื™ื’ ืืช ื”ื™ื›ื•ืœืช ื”ื–ืืช ืื™ืŸ ืฆื•ืจืš ืœืขืฉื•ืช ื›ืœื•ื ืžืœื‘ื“ ืœื”ืขื‘ื™ืจ port=0 - ื–ื” ื›ื‘ืจ ื™ื’ืจื•ื ืœื”ืงืฆืื” ื“ื™ื ืืžื™ืช ืฉืœ ืคื•ืจื˜. + +**ืื—ืจืช:** ื”ื’ื“ืจื” ืฉืœ ืคื•ืจื˜ ืกืคืฆื™ืคื™ ื™ืžื ืข ืืช ื”ืืคืฉืจื•ืช ืœื”ืจื™ืฅ ืฉื ื™ ื˜ืกื˜ื™ื ื‘ืžืงื‘ื™ืœ. ืจื•ื‘ ื”ื›ืœื™ื ืฉืžืจื™ืฆื™ื ื›ื™ื•ื ื˜ืกื˜ื™ื - ืžืจื™ืฆื™ื ื‘ืžืงื‘ื™ืœ ื›ื‘ืจื™ืจืช ืžื—ื“ืœ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื’ื“ื™ืจื• ืคื•ืจื˜ ืืงืจืื™ ืœื‘ื“ื™ืงื•ืช**](./sections/testingandquality/randomize-port.md) + +

+ +## ![โœ”] 4.13 ื‘ื™ื“ืงื• ืืช ื—ืžืฉืช ื”ืชื•ืฆืื•ืช ื”ืืคืฉืจื™ื•ืช + +**ืืž;ืœืง:** ื‘ืขืช ื‘ื“ื™ืงืช ืžืงืจื”, ื•ื“ืื• ืฉืืชื ืžื›ืกื™ื ืืช ื—ืžืฉืช ื”ืงื˜ื’ื•ืจื™ื•ืช ื”ืืคืฉืจื™ื•ืช. ื‘ื›ืœ ืคืขื ืฉืคืขื•ืœื” ื—ืœื” (ืœืžืฉืœ ืงืจื™ืืช API), ืžืชื—ื™ืœื” ืชื’ื•ื‘ื”, **ืชื•ืฆืื”** ืžืฉืžืขื•ืชื™ืช ื ื•ืฆืจืช ื•ืžืชื‘ืฆืขืช ืงืจื™ืื” ืœื‘ื“ื™ืงื”. ื™ืฉื ืŸ ื—ืžืฉ ืกื•ื’ื™ ืชื•ืฆืื•ืช ืœื›ืœ ืžืงืจื”: ืชื’ื•ื‘ื”, ืฉื™ื ื•ื™ ื ืจืื” ืœืขื™ืŸ (ื›ืžื• ืขื“ื›ื•ืŸ ื‘ืžืกื“ ื”ื ืชื•ื ื™ื), ืฉืœื™ื—ืช ืงืจื™ืื” ืœ- +API, ื”ื•ื“ืขื” ื—ื“ืฉื” ื ืจืฉืžืช ืœืชื•ืจ, ื•ืงืจื™ืื” ืœื›ืœื™ ืฆืคื™ื” ื‘ืžื™ื“ืข (ื›ืžื• ืœื•ื’ืจ ื•ืื ืœื™ื˜ื™ืงื•ืช). [ืจืฉื™ืžืช ื‘ื“ื™ืงื•ืช ื‘ืกื™ืกื™ื•ืช](https://testjavascript.com/wp-content/uploads/2021/10/the-backend-checklist.pdf). ื›ืœ ืกื•ื’ ืฉืœ ืชื•ืฆืื” ืžื’ื™ืข ืื ืืชื’ืจื™ื ื™ื—ื•ื“ื™ื™ื ื•ืฉื™ื˜ื•ืช ืœื”ืžืชื™ืง ืืช ื”ืืชื’ืจื™ื ื”ืœืœื• - ื›ืชื‘ื ื• ืžื“ืจื™ืš ื™ืขื•ื“ื™ ืขืœ ื ื•ืฉื ื–ื” [ื‘ื“ื™ืงื•ืช ื‘-Node.js - ืžืขื‘ืจ ืœื™ืกื•ื“ื•ืช](https://github.com/testjavascript/nodejs-integration-tests-best-practices) + +**ืื—ืจืช:** ืชืืจื• ืœืขืฆืžื›ื ืžืงืจื” ืฉืœ ื‘ื“ื™ืงืช ื”ื•ืกืคื” ืฉืœ ืžื•ืฆืจ ื—ื“ืฉ ืœืžืขืจื›ืช. ื ืคื•ืฅ ืœืจืื•ืช ื‘ื“ื™ืงื•ืช ืฉืžื›ืกื•ืช ืืš ื•ืจืง ืืช ื”ืžืงืจื™ื ืฉืœ ืชืฉื•ื‘ื” ืชืงื™ื ื”. ืžื” ื™ืงืจื” ืื ื”ืžื•ืฆืจ ืœื ื™ืชื•ื•ืกืฃ ืขืœ ืืฃ ื”ืชืฉื•ื‘ื” ื”ื—ื™ื•ื‘ื™ืช? ืžื” ืฆืจื™ืš ืœื”ื™ืขืฉื•ืช ื‘ืžื™ื“ื” ื•ื‘ืขืช ื”ื•ืกืคืช ืžื•ืฆืจ ื™ืฉ ื’ื ืงืจื™ืื” ืœืฉื™ืจื•ืช ื—ื™ืฆื•ื ื™ ืื• ื”ื•ืกืคืช ื”ื•ื“ืขื” ืœืชื•ืจ - ื”ืื ื”ื‘ื“ื™ืงื” ืœื ืฆืจื™ื›ื” ืœื”ืชื™ื™ื—ืก ื’ื ืœื–ื”? ืงืœ ืœื”ืชืขืœื ืžืžื’ื•ื•ืŸ ืžืงืจื™ื, ื•ื‘ื ืงื•ื“ื” ื–ืืช [ืจืฉื™ืžืช ื”ื‘ื“ื™ืงื•ืช](https://testjavascript.com/wp-content/uploads/2021/10/the-backend-checklist.pdf) ืขื•ื–ืจืช. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื‘ื“ื™ืงืช ื—ืžืฉืช ื”ืชื•ืฆืื•ืช**](./sections/testingandquality/test-five-outcomes.md) + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# `5. ืขืœื™ื™ื” ืœืื•ื•ื™ืจ` + +## ![โœ”] 5.1. ื ื™ื˜ื•ืจ + +**ืืž;ืœืง:** ื ื™ื˜ื•ืจ ื”ื•ื ืžืฉื—ืง ืฉืœ ืžืฆื™ืืช ื‘ืขื™ื•ืช ืœืคื ื™ ืฉื”ืžืฉืชืžืฉื™ื ืžื•ืฆืื™ื ืื•ืชืŸ - ืžื•ื‘ืŸ ืžืืœื™ื• ืฉื–ื” ืฆืจื™ืš ืœื”ื™ื•ืช ื‘ืจืืฉ ืกื“ืจ ื”ืขื“ื™ืคื•ื™ื•ืช. ื”ืฉื•ืง ืžื•ืฆืฃ ื‘ื”ืฆืขื•ืช ืœื”ื’ื“ืจื•ืช ืžื” ื”ื ื”ืžื“ื“ื™ื ื”ื‘ืกื™ืกื™ื™ื ืฉื—ื™ื™ื‘ื™ื ืœืขืงื•ื‘ ืื—ืจื™ื”ื (ื”ื”ืžืœืฆื•ืช ืฉืœื ื• ื‘ื”ืžืฉืš), ืœืื—ืจ ืžื›ืŸ ืœืขื‘ื•ืจ ืขืœ ื›ืœ ื”ื™ื›ื•ืœื•ืช ื”ืžืขื ื™ื™ื ื•ืช ืฉื›ืœ ืžื•ืฆืจ ืžืฆื™ืข ื•ืœื‘ื—ื•ืจ ืืช ื”ืคืชืจื•ืŸ ื”ืžื™ื˜ื‘ื™ ืขื‘ื•ืจ ื”ื“ืจื™ืฉื•ืช ืฉืœื›ื. ื‘ื›ืœ ืžืงืจื”, ืืจื‘ืขืช ื”ืฉื›ื‘ื•ืช ื”ื ื™ืชื ื•ืช ืœืฆืคื™ื™ื” ื—ื™ื™ื‘ื•ืช ืœื”ื™ืžื“ื“: (1) Uptime - ืžืฆื™ื™ื ืช ื”ืื ื”ืžืขืจื›ืช ื–ืžื™ื ื”, (2) Metrics - ืžืฆื™ื™ื ืช ืžื”ื™ ื”ื”ืชื ื”ื’ื•ืช ื”ืžืฆื˜ื‘ืจืช ืฉืœ ื”ืžืขืจื›ืช (ื”ืื 99% ืžื”ื‘ืงืฉื•ืช ื ืขื ื•ืช), (3) Logging - ื‘ื•ื“ืงืช ืื ื‘ืงืฉื” ืžืกื•ื™ื™ืžืช ืžืกืชื™ื™ืžืช ื‘ื”ืฆืœื—ื”, (4) Distributed tracing - ื‘ื•ื“ืงืช ื”ืื ื”ืžืขืจื›ืช ื™ืฆื™ื‘ื” ื‘ื™ืŸ ื”ืจื›ื™ื‘ื™ื ื”ืžื‘ื•ื–ืจื™ื ืฉืœื”. + +**ืื—ืจืช:** ื›ืฉืœื•ืŸ === ืœืงื•ื—ื•ืช ืžืื•ื›ื–ื‘ื™ื. ืคืฉื•ื˜ ืžืื•ื“. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื ื™ื˜ื•ืจ!**](./sections/production/monitoring.md) + +

+ +## ![โœ”] 5.2. ื”ื’ื“ื™ืœื• ืืช ื™ื›ื•ืœืช ื”ืฆืคื™ื™ื” ื‘ืขื–ืจืช ืœื•ื’ื™ื ืื™ื›ื•ืชื™ื™ื + +**ืืž;ืœืง:** ืœื•ื’ื™ื ื™ื›ื•ืœื™ื ืœื”ื™ื•ืช ืคื— ื”ื–ื‘ืœ ืฉืœ ืฉืœืœ ืžืฆื‘ื™ื ืฉื”ืžืคืชื—ื™ื ืจืฆื• ืœื“ื‘ื’ ืื• ืœื—ืœื•ืคื™ืŸ ืžืกืš ืžื”ืžื ืฉืžืชืืจ ืืช ื”ืžืฆื‘ ืฉืœ ื”ืžื•ืฆืจ. ืชื›ื ื ื• ืืช ื”ืœื•ื’ื™ื ืฉืœื›ื ืžื”ื™ื•ื ื”ืจืืฉื•ืŸ: ืื™ืš ื”ื ื ืืกืคื™ื, ืื™ืคื” ื”ื ื ืฉืžืจื™ื ื•ืื™ืš ื”ื ืžื ื•ืชื—ื™ื ื›ื“ื™ ืœื”ื‘ื˜ื™ื— ืฉื”ืžื™ื“ืข ื”ื”ื›ืจื—ื™ (ืื—ื•ื– ืฉื’ื™ืื•ืช, ืžืขืงื‘ ืื—ืจ ืคืขื•ืœื” ื‘ื™ืŸ ืžืกืคืจ ืฉื™ืจื•ืชื™ื ื•ื›ื•') ื‘ืืžืช ื ื’ื™ืฉ ื•ื‘ืจ ืฉื™ืžื•ืฉ. + +**ืื—ืจืช:** ื™ืฉ ืœื›ื ืงื•ืคืกื” ืฉื—ื•ืจื” ืฉืงืฉื” ืœื”ื‘ื™ืŸ ืœืžื” ื”ื™ื ืžื’ื™ืขื” ืœืžืฆื‘ ื”ื ื•ื›ื—ื™, ื•ืจืง ืขื›ืฉื™ื• ืืชื ืžืชื—ื™ืœื™ื ืœืฉื›ืชื‘ ืืช ื›ืœ ื”ืœื•ื’ื™ื ืฉืœื›ื ื›ื“ื™ ืฉื™ื”ื™ื” ืžื™ื“ืข ืจืœื•ื•ื ื˜ื™. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื’ื“ืœืช ื”ืฉืงื™ืคื•ืช ืขืœ ื™ื“ื™ ืœื•ื’ื™ื ืื™ื›ื•ืชื™ื™ื**](./sections/production/smartlogging.md) + +

+ +## ![โœ”] 5.3. ื”ืืฆื™ืœื• ื›ืœ ืžื” ืฉืืคืฉืจ (ืœื“ื•ื’ืžื” gzip, SSL) ืœืฉื™ืจื•ืช ื ืคืจื“ + +**ืืž;ืœืง:** Node.js ื’ืจื•ืข ื‘ืœื‘ืฆืข ืคืขื•ืœื•ืช ืฉื“ื•ืจืฉื•ืช ืขื•ืฆืžืช ื—ื™ืฉื•ื‘ ื’ื‘ื•ื”ื” ืžื”-CPU, ื›ืžื• ืœืžืฉืœ ื“ื—ื™ืกื”, ืกื™ื•ื ืชื”ืœื™ืš SSL, ื•ื›ื•'... ื›ื“ืื™ ืฉืชืฉืชืžืฉื• ื‘ืชืฉืชื™ื•ืช ื›ืžื• nginx, HAproxy ืื• ืฉื™ืจื•ืชื™ ืขื ืŸ ืื—ืจื™ื ืœืฉื ื›ืš. + +**ืื—ืจืช:** ื”ืช'ืจื“ ื”ื‘ื•ื“ื“ ื•ื”ืžืกื›ืŸ ืฉืœื›ื ื™ื™ืฉืืจ ืขืกื•ืง ื‘ืžืฉื™ืžื•ืช ืชืฉืชื™ืชื™ื•ืช ื‘ืžืงื•ื ืœื”ืชืขืกืง ื‘ืœื‘ ื”ืžืขืจื›ืช ืฉืœื›ื ื•ื”ื‘ื™ืฆื•ืขื™ื ื™ื™ืฉื—ืงื• ื‘ื”ืชืื. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ืืฆืœืช ื›ืœ ืžื” ืฉืืคืฉืจ (ืœื“ื•ื’ืžื” gzip, SSL) ืœืฉื™ืจื•ืช ื ืคืจื“**](./sections/production/delegatetoproxy.md) + +

+ +## ![โœ”] 5.4. ืงื™ื‘ื•ืข ืชืœื•ื™ื•ืช + +**ืืž;ืœืง:** ื”ืงื•ื“ ืฉืœื›ื ืฆืจื™ืš ืœื”ื™ื•ืช ื–ื”ื” ื‘ื›ืœ ื”ืกื‘ื™ื‘ื•ืช, ืืš ืœืœื ืงื•ื‘ืฅ ื™ืขื•ื“ื™ npm ื™ืืคืฉืจ ืฉื™ืžื•ืฉ ื‘ืชืœื•ื™ื•ืช ืฉื•ื ื•ืช ื‘ื›ืœ ืกื‘ื™ื‘ื”. ื•ื“ืื• ื›ื™ ื™ืฉ ืœื›ื `package-lock.json` ื›ืš ืฉื›ืœ ื”ืกื‘ื™ื‘ื•ืช ื™ื”ื™ื• ื–ื”ื•ืช. + +**ืื—ืจืช:** ืื ืฉื™ ื”ื‘ื“ื™ืงื•ืช ื™ืืฉืจื• ื’ืจืกื” ืฉืชืชื ื”ื’ ืื—ืจืช ื‘ืกื‘ื™ื‘ืช ื™ื™ืฆื•ืจ. ื’ืจื•ืข ืžื›ืš, ืฉืจืชื™ื ืฉื•ื ื™ื ื‘ืื•ืชื” ืกื‘ื™ื‘ื” ื™ืจื™ืฆื• ืงื•ื“ ืฉื•ื ื”. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืงื™ื‘ื•ืข ืชืœื•ื™ื•ืช**](./sections/production/lockdependencies.md) + +

+ +## ![โœ”] 5.5. ื”ื‘ื˜ื™ื—ื• ืืช ื–ืžื™ื ื•ืช ื”ืžืขืจื›ืช ื‘ืขื–ืจืช ื”ื›ืœื™ ื”ืžืชืื™ื + +**ืืž;ืœืง:** ื”ืžืขืจื›ืช ืฆืจื™ื›ื” ืœื”ืžืฉื™ืš ืœืขื‘ื•ื“ ื•ืœื”ืชืืชื—ืœ ื‘ืžื™ื“ื” ื•ืงืจืชื” ืฉื’ื™ืื” ืงืจื™ื˜ื™ืช. ืกื‘ื™ื‘ื•ืช ืจื™ืฆื” ื—ื“ืฉื•ืช ื›ืžื• ืœืžืฉืœ ื›ืืœื• ื”ืžื‘ื•ืกืกื•ืช ื“ื•ืงืจ (ื›ืžื• ืงื•ื‘ืจื ื˜ื™ืก), ืื• Serverless ืžื˜ืคืœื•ืช ื‘ื–ื” ื‘ืฆื•ืจื” ืื•ื˜ื•ืžื˜ื™ืช. ื›ืืฉืจ ื”ืžื•ืฆืจ ืžื•ืชืงืŸ ืขืœ ืฉืจืช ืืžื™ืชื™ ืคื™ื–ื™, ื™ืฉ ืฆื•ืจืš ืœื ื”ืœ ืืช ืžืฉืื‘ื™ ื”ืžืขืจื›ืช ื‘ืขื–ืจืช ื›ืœื™ ื›ืžื• [systemd](https://systemd.io/). ืืš ื™ืฉ ืœื”ื™ืžื ืข ืžืœืขืฉื•ืช ื–ืืช ื›ืืฉืจ ืžืฉืชืžืฉื™ื ื‘ืชืฉืชื™ื•ืช ืฉื›ื‘ืจ ืžื‘ืฆืขื•ืช ืืช ื”ื ื™ื˜ื•ืจ ืžื›ื™ื•ื•ืŸ ืฉื–ื” ื™ื’ืจื•ื ืœื‘ืœื™ืขืช ืฉื’ื™ืื•ืช. ื›ืืฉืจ ืœืชืฉืชื™ืช ืื™ืŸ ืžื•ื“ืขื•ืช ืœืฉื’ื™ืื•ืช, ืื™ืŸ ืœื” ื™ื›ื•ืœืช ืฉืœ ื‘ื™ืฆื•ืข ืฉืœื‘ื™ ืคื™ื—ื•ืช ืžืฉืื‘ื™ื ื›ืžื• ื”ืขื‘ืจืช ื”ืื™ื ืกื˜ื ืก ืฉืœ ื”ืžืขืจื›ืช ืœืžืงื•ื ืื—ืจ ื‘ืจืฉืช. + +**ืื—ืจืช:** ื”ืจืฆื” ืฉืœ ืขืฉืจื•ืช ืื™ื ืกื˜ื ืกื™ื ืœืœื ืกื™ื‘ื” ื‘ืจื•ืจื” ื•ื™ื•ืชืจ ืžื™ื“ื™ ื›ืœื™ ืชืฉืชื™ืช ื™ื—ื“ (cluster management, docker, PM2) ืขืœื•ืœ ืœื’ืจื•ื ืœื›ืื•ืก ืขื‘ื•ืจ ื”-DevOps. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ื‘ื˜ื™ื—ื• ืืช ื–ืžื™ื ื•ืช ื”ืžืขืจื›ืช ื‘ืขื–ืจืช ื”ื›ืœื™ ื”ืžืชืื™ื**](./sections/production/guardprocess.md) + +

+ +## ![โœ”] 5.6. ื”ืฉืชืžืฉื• ื‘ื›ืœ ืžืขื‘ื“ื™ ื”-CPU + +**ืืž;ืœืง:** ื‘ืชืฆื•ืจื” ื”ื‘ืกื™ืกื™ืช ืฉืœื”, ืžืขืจื›ืช ืžื‘ื•ืกืกืช Node.js ืชืจื•ืฅ ืขืœ ืžืขื‘ื“ CPU ืื—ื“ ื•ืฉืืจ ื”ืžืขื‘ื“ื™ื ื™ื ื•ื—ื•. ืžื—ื•ื‘ืชื›ื ืœืฉื›ืคืœ ืืช ื”ืชื”ืœื™ืš ื•ืœื ื”ืœ ืืช ื”ืžืขืจื›ืช ื›ื›ื” ืฉืชืจื•ืฅ ืขืœ ื›ืœ ื”ืžืขื‘ื“ื™ื. ืจื•ื‘ ืชืฉืชื™ื•ืช ื”ืจื™ืฆื” ื”ื—ื“ืฉื•ืช (ื›ืžื• ืงื•ื‘ืจื ื˜ื™ืก) ืžืืคืฉืจื•ืช ืœืฉื›ืคืœ ืืช ื”ืชื”ืœื™ื›ื™ื ืœืžืกืคืจ ืžืขื‘ื“ื™ื, ืืš ื”ืŸ ืœื ืžื‘ื˜ื™ื—ื•ืช ืœื”ืฉืชืžืฉ ื‘ื›ืœ ื”ืžืขื‘ื“ื™ื - ื–ืืช ื”ืื—ืจื™ื•ืช ืฉืœื›ื! ืื ื”ืžื•ืฆืจ ืžื•ืชืงืŸ ืขืœ ืฉืจืช ืคื™ื–ื™, ืื– ื›ื—ืœืง ืžืื—ืจื™ื•ืชื›ื ืืชื ืฆืจื™ื›ื™ื ื’ื ืœื”ืฉืชืžืฉ ื‘ืคืชืจื•ื ื•ืช ืฉื™ื‘ืฆืขื• ืืช ื”ืฉื›ืคื•ืœ ืฉืœ ื”ืชื”ืœื™ืš (ื›ืžื• systemd). + +**ืื—ืจืช:** ื”ืžื•ืฆืจ ืฉืœื›ื ื™ื ืฆืœ ืœื›ืœ ื”ื™ื•ืชืจ 25% ืžื”ืžืฉืื‘ื™ื ื”ื–ืžื™ื ื™ื(!). ื–ื›ืจื• ืฉืœืฉืจืช ืจื’ื™ืœ ื™ืฉ 4 ืžืขื‘ื“ื™ CPU ืื• ื™ื•ืชืจ, ื•ื”ืชืงื ื” ืกื˜ื ื“ืจื˜ื™ืช ืฉืœ ืชื”ืœื™ืš Node.js ืžืฉืชืžืฉืช ืจืง ื‘ืžืขื‘ื“ ืื—ื“ (ื’ื ืฉื™ืจื•ืชื™ื ื‘ืฉื™ื˜ืช PaaS ื›ืžื• AWS beanstalk!). + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื”ืฉืชืžืฉื• ื‘ื›ืœ ืžืขื‘ื“ื™ ื”-CPU**](./sections/production/utilizecpu.md) + +

+ +## ![โœ”] 5.7. ืชื™ืฆืจื• โ€˜maintenance endpointโ€™ + +**ืืž;ืœืง:** ื—ื™ืฉืคื• ืžื™ื“ืข ืจืœื•ื•ื ื˜ื™ ืขืœ ื”ืžืขืจื›ืช, ืœืžืฉืœ ืžืฆื‘ ื”ื–ื™ื›ืจื•ืŸ ื• -[REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), ื‘ืืžืฆืขื•ืช API ืžืื•ื‘ื˜ื—. ืขืœ ืืฃ ืฉืžื•ืžืœืฅ ืœื”ื™ืฉืขืŸ ืขืœ ื›ืœื™ื ื™ืขื•ื“ื™ื™ื ืœืฉื ื›ืš, ืืช ื—ืœืง ืžื”ืžื™ื“ืข ื•ื”ืคืขื•ืœื•ืช ื™ื•ืชืจ ืคืฉื•ื˜ ืœื‘ื“ื•ืง ื‘ืืžืฆืขื•ืช ื›ืชื™ื‘ืช ืงื•ื“. + +**ืื—ืจืช:** ืชื’ืœื• ืฉืืชื ืžื‘ืฆืขื™ื ื”ืจื‘ื” โ€œdiagnostic deploysโ€ โ€“ ื”ืขืœืืช ืงื•ื“ ืœืกื‘ื™ื‘ืช ื”ื™ื™ืฆื•ืจ ืจืง ื›ื“ื™ ืœื”ืฉื™ื’ ืขื•ื“ ืงืฆืช ืžื™ื“ืข ืื‘ื—ื ืชื™ ืขืœ ื”ืžืขืจื›ืช. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื™ืฆื™ืจืช โ€˜maintenance endpointโ€™**](./sections/production/createmaintenanceendpoint.md) + +

+ +## ![โœ”] 5.8. ื’ืœื• ืืช ื”ืœื ื™ื“ื•ืข ื‘ืขื–ืจืช ืžื•ืฆืจื™ APM + +**ืืž;ืœืง:** ืฉื™ืงืœื• ื”ื•ืกืคืช ืฉื›ื‘ื” ื ื•ืกืคืช ืฉืœ ื‘ื˜ื™ื—ื•ืช ืœืžื•ืฆืจ ืฉืœื›ื - [APM](https://en.wikipedia.org/wiki/Application_performance_management) (Application monitoring and performance products). ืืžื ื ืจื•ื‘ ื”ืกืžืžื ื™ื ื•ื”ื’ื•ืจืžื™ื ื™ื›ื•ืœื™ื ืœื”ื™ืžืฆื ืขืœ ื™ื“ื™ ื˜ื›ื ื™ืงื•ืช ื ื™ื˜ื•ืจ ืกื˜ื ื“ืจื˜ื™ื•ืช, ืืš ื‘ืžืขืจื›ื•ืช ืžื‘ื•ื–ืจื•ืช ื™ืฉ ืขื•ื“ ืจื‘ื“ื™ื ืกืžื•ื™ื™ื ืžืŸ ื”ืขื™ืŸ. ื ื™ื˜ื•ืจ ืžืขืจื›ื•ืช ื•ื‘ื“ื™ืงืช ื‘ื™ืฆื•ืขื™ื (ืื• ื‘ืงื™ืฆื•ืจ APM) ื™ื›ื•ืœื™ื ื‘ืื•ืคืŸ ืงืกื•ื ืœื”ื•ืกื™ืฃ ืฉื›ื‘ื” ื ื•ืกืคืช ืฉืœ ื—ื•ื•ื™ื™ืช ืคื™ืชื•ื— ืžืขื‘ืจ ืœืžื” ืฉืžืกืคืงื™ื ื”ื›ืœื™ื ื”ืกื˜ื ื“ืจื˜ื™ื™ื. ืœื“ื•ื’ืžื”, ื™ืฉื ื ื›ืœื™ APM ืฉื™ื›ื•ืœื™ื ืœื”ื“ื’ื™ืฉ ื˜ืจื ื–ืงืฆื™ื” ืฉื˜ื•ืขื ืช ืœืื˜ ืžื™ื“ื™ ืืช **ืฆื“ ื”ืœืงื•ื—** ื•ืœื”ืฆื™ืข ืžื” ื”ืกื™ื‘ื” ืœื›ืš. ื›ืœื™ื ืืœื• ื’ื ืžืกืคืงื™ื ื™ื•ืชืจ ื”ืงืฉืจ ืœืฆื•ื•ืช ื”ืคื™ืชื•ื— ืฉืžื ืกื™ื ืœื—ืงื•ืจ ืฉื’ื™ืื” ื•ื–ืืช ืขืœ ื™ื“ื™ ื”ืฆื’ื” ืฉืœ ื”ืขื•ืžืกื™ื ืฉื”ื™ื• ื‘ืฉืจืช ื‘ื–ืžืŸ ืฉื—ืœื” ื”ืฉื’ื™ืื”. + +**ืื—ืจืช:** ืืชื ืžืฉืงื™ืขื™ื ื–ืžืŸ ื ื™ื›ืจ ื‘ืžื“ื™ื“ืช ื‘ื™ืฆื•ืขื™ API ื•ืื™ ื–ืžื™ื ื•ืช ืฉืœ ื”ืžืขืจื›ืช, ื›ื ืจืื” ืฉืœืขื•ืœื ืœื ืชื”ื™ื• ืžื•ื“ืขื™ื ืœืื™ืœื• ื—ืœืงื™ื ื‘ืงื•ื“ ื”ื ื”ืื™ื˜ื™ื™ื ื‘ื™ื•ืชืจ ื‘ื–ืžืŸ ืืžืช ื•ืื™ืš ื–ื” ืžืฉืคื™ืข ืขืœ ื—ื•ื•ื™ืช ื”ืžืฉืชืžืฉ. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื’ื™ืœื•ื™ ืฉื’ื™ืื•ืช ื•ื–ืžื ื™ ื”ืฉื‘ืชื” ื‘ืขื–ืจืช ืžื•ืฆืจื™ APM**](./sections/production/apmproducts.md) + +

+ +## ![โœ”] 5.9. ื›ืชื‘ื• ืืช ื”ืงื•ื“ ืžื•ืชืื ืœื”ืชืงื ื” + +**ืืž;ืœืง:** ืงื•ื“ื“ื• ื›ืืฉืจ ื”ืชื•ืฆืื” ื”ืกื•ืคื™ืช ื‘ืžื—ืฉื‘ื•ืชื™ื›ื, ื”ืชื›ื•ื ื ื• ืœื”ืชืงื ื” ื‘ืกื‘ื™ื‘ืช ื™ืฆื•ืจ ื›ื‘ืจ ืžื”ื™ื•ื ื”ืจืืฉื•ืŸ. ื–ื” ืืžื ื ื ืฉืžืข ืงืฆืช ืžืขื•ืจืคืœ ื•ืœื›ืŸ ื‘ืงื™ืฉื•ืจ ื™ืฉื ืŸ ืžืกืคืจ ื”ืžืœืฆื•ืช ื”ืงืฉื•ืจื•ืช ืœืชืžื™ื›ื” ื‘ืžื•ืฆืจ ืฉื›ื‘ืจ ื”ื•ืชืงืŸ. + +**ืื—ืจืช:** ืืœื•ืคื™ ื”ืขื•ืœื ืฉืœ IT/DevOps ืœื ื™ื ืกื• ืœื”ืฆื™ืœ ืžืขืจื›ืช ืฉื›ืชื•ื‘ื” ื’ืจื•ืข. + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ื›ืชื‘ื• ืืช ื”ืงื•ื“ ืžื•ืชืื ืœื”ืชืงื ื”**](./sections/production/productioncode.md) + +

+ +## ![โœ”] 5.10. ืžื“ื“ื• ื•ืฉื™ืžืจื• ืืช ื ื™ืฆื•ืœ ื”ื–ื™ื›ืจื•ืŸ + +**ืืž;ืœืง:** ืœ-Node.js ื™ืฉื ื” ืžืขืจื›ืช ื™ื—ืกื™ื ืžื•ืจื›ื‘ืช ืขื ื ื™ื”ื•ืœ ื”ื–ื™ื›ืจื•ืŸ: ืœืžื ื•ืข ื”-v8 ื™ืฉื ื ื’ื‘ื•ืœื•ืช ืขื“ื™ื ื™ื ืฉืœ ืฆืจื™ื›ืช ื–ื™ื›ืจื•ืŸ (1.4GB) ื•ื™ืฉื ืŸ ื“ืจื›ื™ื ื™ื“ื•ืขื•ืช ืื™ืš ืœื’ืจื•ื ืœื–ืœื™ื’ืช ื–ื™ื›ืจื•ืŸ ื‘ืงื•ื“ ืฉืœ Node.js - ื•ืœื›ืŸ ืžืขืงื‘ ืื—ืจ ืฆืจื™ื›ืช ื”ื–ื™ื›ืจื•ืŸ ืฉืœ ืชื”ืœื™ืš Node.js ื”ื•ื ื—ื•ื‘ื”. ื‘ืžื•ืฆืจื™ื ืงื˜ื ื™ื, ืืคืฉืจ ืœืืžื•ื“ ืืช ืฆืจื™ื›ืช ื”ื–ื™ื›ืจื•ืŸ ื›ืœ ื›ืžื” ื–ืžืŸ ื‘ืขื–ืจืช ืคืงื•ื“ื•ืช shell, ืื‘ืœ ื‘ืžื•ืฆืจื™ื ื‘ื™ื ื•ื ื™ื™ื-ื’ื“ื•ืœื™ื ืฆืจื™ืš ืœืชืขื“ืฃ ืฉื™ืžื•ืฉ ื‘ื›ืœื™ื ื—ื–ืงื™ื ืœื ื™ื˜ื•ืจ ืžืฆื‘ ื”ื–ื™ื›ืจื•ืŸ. + +**ืื—ืจืช:** ื–ื•ืœื’ื™ื ืœื›ื ืžืื•ืช MB ื›ืœ ื™ื•ื ืžื”ืชื”ืœื™ืš ื›ืžื• ืฉืงืจื” ื‘[ื•ื•ืœืžืืจื˜](https://www.joyent.com/blog/walmart-node-js-memory-leak) + +๐Ÿ”— [**ืœืงืจื™ืื” ื ื•ืกืคืช: ืžื“ื™ื“ื” ื•ืฉืžื™ืจื” ืขืœ ื ื™ืฆื•ืœ ื”ื–ื™ื›ืจื•ืŸ**](./sections/production/measurememory.md) + +

+ +## ![โœ”] 5.11. Get your frontend assets out of Node + +**ืืž;ืœืง:** Serve frontend content using a specialized infrastructure (nginx, S3, CDN) because Node performance gets hurt when dealing with many static files due to its single-threaded model. One exception to this guideline is when doing server-side rendering + +**ืื—ืจืช:** Your single Node thread will be busy streaming hundreds of html/images/angular/react files instead of allocating all its resources for the task it was born for โ€“ serving dynamic content + +๐Ÿ”— [**Read More: Get your frontend assets out of Node**](./sections/production/frontendout.md) + +

+ +## ![โœ”] 5.12. Strive to be stateless + +**ืืž;ืœืง:** Store any type of _data_ (e.g. user sessions, cache, uploaded files) within external data stores. When the app holds data in-process this adds additional layer of maintenance complexity like routing users to the same instance and higher cost of restarting a process. To enforce and encourage a stateless approach, most modern runtime platforms allows 'reapp-ing' instances periodically + +**ืื—ืจืช:** Failure at a given server will result in application downtime instead of just killing a faulty machine. Moreover, scaling-out elasticity will get more challenging due to the reliance on a specific server + +๐Ÿ”— [**Read More: Be stateless, kill your Servers almost every day**](./sections/production/bestateless.md) + +

+ +## ![โœ”] 5.13. Use tools that automatically detect vulnerabilities + +**ืืž;ืœืง:** Even the most reputable dependencies such as Express have known vulnerabilities (from time to time) that can put a system at risk. This can be easily be tamed using community and commercial tools that constantly check for vulnerabilities and warn (locally or at GitHub), some can even patch them immediately + +**ืื—ืจืช:** Keeping your code clean from vulnerabilities without dedicated tools will require you to constantly follow online publications about new threats. Quite tedious + +๐Ÿ”— [**Read More: Use tools that automatically detect vulnerabilities**](./sections/production/detectvulnerabilities.md) + +

+ +## ![โœ”] 5.14. Assign a transaction id to each log statement + +**ืืž;ืœืง:** Assign the same identifier, transaction-id: uuid(), to each log entry within a single request (also known as correlation-id/tracing-id/request-context). Then when inspecting errors in logs, easily conclude what happened before and after. Node has a built-in mechanism, [AsyncLocalStorage](https://nodejs.org/api/async_context.html), for keeping the same context across asynchronous calls. see code examples inside + +**ืื—ืจืช:** Looking at a production error log without the context โ€“ what happened before โ€“ makes it much harder and slower to reason about the issue + +๐Ÿ”— [**Read More: Assign โ€˜TransactionIdโ€™ to each log statement**](./sections/production/assigntransactionid.md) + +

+ +## ![โœ”] 5.15. Set `NODE_ENV=production` + +**ืืž;ืœืง:** Set the environment variable `NODE_ENV` to โ€˜productionโ€™ or โ€˜developmentโ€™ to flag whether production optimizations should get activated โ€“ some npm packages determine the current environment and optimize their code for production + +**ืื—ืจืช:** Omitting this simple property might greatly degrade performance when dealing with some specific libraries like Express server-side rendering + +๐Ÿ”— [**Read More: Set NODE_ENV=production**](./sections/production/setnodeenv.md) + +

+ +## ![โœ”] 5.16. Design automated, atomic and zero-downtime deployments + +**ืืž;ืœืง:** Research shows that teams who perform many deployments lower the probability of severe production issues. Fast and automated deployments that donโ€™t require risky manual steps and service downtime significantly improve the deployment process. You should probably achieve this using Docker combined with CI tools as they became the industry standard for streamlined deployment + +**ืื—ืจืช:** Long deployments -> production downtime & human-related error -> team unconfident in making deployment -> fewer deployments and features + +

+ +## ![โœ”] 5.17. Use an LTS release of Node.js + +**ืืž;ืœืง:** Ensure you are using an LTS version of Node.js to receive critical bug fixes, security updates and performance improvements + +**ืื—ืจืช:** Newly discovered bugs or vulnerabilities could be used to exploit an application running in production, and your application may become unsupported by various modules and harder to maintain + +๐Ÿ”— [**Read More: Use an LTS release of Node.js**](./sections/production/LTSrelease.md) + +

+ +## ![โœ”] 5.18. Log to stdout, avoid specifying log destination within the app + +**ืืž;ืœืง:** Log destinations should not be hard-coded by developers within the application code, but instead should be defined by the execution environment the application runs in. Developers should write logs to `stdout` using a logger utility and then let the execution environment (container, server, etc.) pipe the `stdout` stream to the appropriate destination (i.e. Splunk, Graylog, ElasticSearch, etc.). + +**ืื—ืจืช:** If developers set the log routing, less flexibility is left for the ops professional who wishes to customize it. Beyond this, if the app tries to log directly to a remote location (e.g., Elastic Search), in case of panic or crash - further logs that might explain the problem won't arrive + +๐Ÿ”— [**Read More: Log Routing**](./sections/production/logrouting.md) + +

+ +## ![โœ”] 5.19. Install your packages with `npm ci` + +**ืืž;ืœืง:** Run `npm ci` to strictly do a clean install of your dependencies matching package.json and package-lock.json. Obviously production code must use the exact version of the packages that were used for testing. While package-lock.json file sets strict version for dependencies, in case of mismatch with the file package.json, the command 'npm install' will treat package.json as the source of truth. On the other hands, the command 'npm ci' will exit with error in case of mismatch between these files + +**ืื—ืจืช:** QA will thoroughly test the code and approve a version that will behave differently in production. Even worse, different servers in the same production cluster might run different code. + +๐Ÿ”— [**Read More: Use npm ci**](./sections/production/installpackageswithnpmci.md) + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# `6. ืื‘ื˜ื—ื”` + +
+54 items +
+ +## ![โœ”] 6.1. Embrace linter security rules + + + +**ืืž;ืœืง:** Make use of security-related linter plugins such as [eslint-plugin-security](https://github.com/nodesecurity/eslint-plugin-security) to catch security vulnerabilities and issues as early as possible, preferably while they're being coded. This can help catching security weaknesses like using eval, invoking a child process or importing a module with a string literal (e.g. user input). Click 'Read more' below to see code examples that will get caught by a security linter + +**ืื—ืจืช:** What could have been a straightforward security weakness during development becomes a major issue in production. Also, the project may not follow consistent code security practices, leading to vulnerabilities being introduced, or sensitive secrets committed into remote repositories + +๐Ÿ”— [**Read More: Lint rules**](./sections/security/lintrules.md) + +

+ +## ![โœ”] 6.2. Limit concurrent requests using a middleware + + + +**ืืž;ืœืง:** DOS attacks are very popular and relatively easy to conduct. Implement rate limiting using an external service such as cloud load balancers, cloud firewalls, nginx, [rate-limiter-flexible](https://www.npmjs.com/package/rate-limiter-flexible) package, or (for smaller and less critical apps) a rate-limiting middleware (e.g. [express-rate-limit](https://www.npmjs.com/package/express-rate-limit)) + +**ืื—ืจืช:** An application could be subject to an attack resulting in a denial of service where real users receive a degraded or unavailable service. + +๐Ÿ”— [**Read More: Implement rate limiting**](./sections/security/limitrequests.md) + +

+ +## ![โœ”] 6.3 Extract secrets from config files or use packages to encrypt them + + + +**ืืž;ืœืง:** Never store plain-text secrets in configuration files or source code. Instead, make use of secret-management systems like Vault products, Kubernetes/Docker Secrets, or using environment variables. As a last resort, secrets stored in source control must be encrypted and managed (rolling keys, expiring, auditing, etc). Make use of pre-commit/push hooks to prevent committing secrets accidentally + +**ืื—ืจืช:** Source control, even for private repositories, can mistakenly be made public, at which point all secrets are exposed. Access to source control for an external party will inadvertently provide access to related systems (databases, apis, services, etc). + +๐Ÿ”— [**Read More: Secret management**](./sections/security/secretmanagement.md) + +

+ +## ![โœ”] 6.4. Prevent query injection vulnerabilities with ORM/ODM libraries + + + +**ืืž;ืœืง:** To prevent SQL/NoSQL injection and other malicious attacks, always make use of an ORM/ODM or a database library that escapes data or supports named or indexed parameterized queries, and takes care of validating user input for expected types. Never just use JavaScript template strings or string concatenation to inject values into queries as this opens your application to a wide spectrum of vulnerabilities. All the reputable Node.js data access libraries (e.g. [Sequelize](https://github.com/sequelize/sequelize), [Knex](https://github.com/tgriesser/knex), [mongoose](https://github.com/Automattic/mongoose)) have built-in protection against injection attacks. + +**ืื—ืจืช:** Unvalidated or unsanitized user input could lead to operator injection when working with MongoDB for NoSQL, and not using a proper sanitization system or ORM will easily allow SQL injection attacks, creating a giant vulnerability. + +๐Ÿ”— [**Read More: Query injection prevention using ORM/ODM libraries**](./sections/security/ormodmusage.md) + +

+ +## ![โœ”] 6.5. Collection of generic security best practices + +**ืืž;ืœืง:** This is a collection of security advice that is not related directly to Node.js - the Node implementation is not much different than any other language. Click read more to skim through. + +๐Ÿ”— [**Read More: Common security best practices**](./sections/security/commonsecuritybestpractices.md) + +

+ +## ![โœ”] 6.6. Adjust the HTTP response headers for enhanced security + + + +**ืืž;ืœืง:** Your application should be using secure headers to prevent attackers from using common attacks like cross-site scripting (XSS), clickjacking and other malicious attacks. These can be configured easily using modules like [helmet](https://www.npmjs.com/package/helmet). + +**ืื—ืจืช:** Attackers could perform direct attacks on your application's users, leading to huge security vulnerabilities + +๐Ÿ”— [**Read More: Using secure headers in your application**](./sections/security/secureheaders.md) + +

+ +## ![โœ”] 6.7. Constantly and automatically inspect for vulnerable dependencies + + + +**ืืž;ืœืง:** With the npm ecosystem it is common to have many dependencies for a project. Dependencies should always be kept in check as new vulnerabilities are found. Use tools like [npm audit](https://docs.npmjs.com/cli/audit) or [snyk](https://snyk.io/) to track, monitor and patch vulnerable dependencies. Integrate these tools with your CI setup so you catch a vulnerable dependency before it makes it to production. + +**ืื—ืจืช:** An attacker could detect your web framework and attack all its known vulnerabilities. + +๐Ÿ”— [**Read More: Dependency security**](./sections/security/dependencysecurity.md) + +

+ +## ![โœ”] 6.8. Protect Users' Passwords/Secrets using bcrypt or scrypt + + + +**ืืž;ืœืง:** Passwords or secrets (e.g. API keys) should be stored using a secure hash + salt function like `bcrypt`,`scrypt`, or worst case `pbkdf2`. + +**ืื—ืจืช:** Passwords and secrets that are stored without using a secure function are vulnerable to brute forcing and dictionary attacks that will lead to their disclosure eventually. + +๐Ÿ”— [**Read More: User Passwords**](./sections/security/userpasswords.md) + +

+ +## ![โœ”] 6.9. Escape HTML, JS and CSS output + + + +**ืืž;ืœืง:** Untrusted data that is sent down to the browser might get executed instead of just being displayed, this is commonly referred as a cross-site-scripting (XSS) attack. Mitigate this by using dedicated libraries that explicitly mark the data as pure content that should never get executed (i.e. encoding, escaping) + +**ืื—ืจืช:** An attacker might store malicious JavaScript code in your DB which will then be sent as-is to the poor clients + +๐Ÿ”— [**Read More: Escape output**](./sections/security/escape-output.md) + +

+ +## ![โœ”] 6.10. Validate incoming JSON schemas + + + +**ืืž;ืœืง:** Validate the incoming requests' body payload and ensure it meets expectations, fail fast if it doesn't. To avoid tedious validation coding within each route you may use lightweight JSON-based validation schemas such as [jsonschema](https://www.npmjs.com/package/jsonschema) or [joi](https://www.npmjs.com/package/joi) + +**ืื—ืจืช:** Your generosity and permissive approach greatly increases the attack surface and encourages the attacker to try out many inputs until they find some combination to crash the application + +๐Ÿ”— [**Read More: Validate incoming JSON schemas**](./sections/security/validation.md) + +

+ +## ![โœ”] 6.11. Support blocklisting JWTs + + + +**ืืž;ืœืง:** When using JSON Web Tokens (for example, with [Passport.js](https://github.com/jaredhanson/passport)), by default there's no mechanism to revoke access from issued tokens. Once you discover some malicious user activity, there's no way to stop them from accessing the system as long as they hold a valid token. Mitigate this by implementing a blocklist of untrusted tokens that are validated on each request. + +**ืื—ืจืช:** Expired, or misplaced tokens could be used maliciously by a third party to access an application and impersonate the owner of the token. + +๐Ÿ”— [**Read More: Blocklist JSON Web Tokens**](./sections/security/expirejwt.md) + +

+ +## ![โœ”] 6.12. Prevent brute-force attacks against authorization + + + +**ืืž;ืœืง:** A simple and powerful technique is to limit authorization attempts using two metrics: + +1. The first is number of consecutive failed attempts by the same user unique ID/name and IP address. +2. The second is number of failed attempts from an IP address over some long period of time. For example, block an IP address if it makes 100 failed attempts in one day. + +**ืื—ืจืช:** An attacker can issue unlimited automated password attempts to gain access to privileged accounts on an application + +๐Ÿ”— [**Read More: Login rate limiting**](./sections/security/login-rate-limit.md) + +

+ +## ![โœ”] 6.13. Run Node.js as non-root user + + + +**ืืž;ืœืง:** There is a common scenario where Node.js runs as a root user with unlimited permissions. For example, this is the default behaviour in Docker containers. It's recommended to create a non-root user and either bake it into the Docker image (examples given below) or run the process on this user's behalf by invoking the container with the flag "-u username" + +**ืื—ืจืช:** An attacker who manages to run a script on the server gets unlimited power over the local machine (e.g. change iptable and re-route traffic to their server) + +๐Ÿ”— [**Read More: Run Node.js as non-root user**](./sections/security/non-root-user.md) + +

+ +## ![โœ”] 6.14. Limit payload size using a reverse-proxy or a middleware + + + +**ืืž;ืœืง:** The bigger the body payload is, the harder your single thread works in processing it. This is an opportunity for attackers to bring servers to their knees without tremendous amount of requests (DOS/DDOS attacks). Mitigate this limiting the body size of incoming requests on the edge (e.g. firewall, ELB) or by configuring [express body parser](https://github.com/expressjs/body-parser) to accept only small-size payloads + +**ืื—ืจืช:** Your application will have to deal with large requests, unable to process the other important work it has to accomplish, leading to performance implications and vulnerability towards DOS attacks + +๐Ÿ”— [**Read More: Limit payload size**](./sections/security/requestpayloadsizelimit.md) + +

+ +## ![โœ”] 6.15. Avoid JavaScript eval statements + + + +**ืืž;ืœืง:** `eval` is evil as it allows executing custom JavaScript code during run time. This is not just a performance concern but also an important security concern due to malicious JavaScript code that may be sourced from user input. Another language feature that should be avoided is `new Function` constructor. `setTimeout` and `setInterval` should never be passed dynamic JavaScript code either. + +**ืื—ืจืช:** Malicious JavaScript code finds a way into text passed into `eval` or other real-time evaluating JavaScript language functions, and will gain complete access to JavaScript permissions on the page. This vulnerability is often manifested as an XSS attack. + +๐Ÿ”— [**Read More: Avoid JavaScript eval statements**](./sections/security/avoideval.md) + +

+ +## ![โœ”] 6.16. Prevent evil RegEx from overloading your single thread execution + + + +**ืืž;ืœืง:** Regular Expressions, while being handy, pose a real threat to JavaScript applications at large, and the Node.js platform in particular. A user input for text to match might require an outstanding amount of CPU cycles to process. RegEx processing might be inefficient to an extent that a single request that validates 10 words can block the entire event loop for 6 seconds and set the CPU on ๐Ÿ”ฅ. For that reason, prefer third-party validation packages like [validator.js](https://github.com/chriso/validator.js) instead of writing your own Regex patterns, or make use of [safe-regex](https://github.com/substack/safe-regex) to detect vulnerable regex patterns + +**ืื—ืจืช:** Poorly written regexes could be susceptible to Regular Expression DoS attacks that will block the event loop completely. For example, the popular `moment` package was found vulnerable with malicious RegEx usage in November of 2017 + +๐Ÿ”— [**Read More: Prevent malicious RegEx**](./sections/security/regex.md) + +

+ +## ![โœ”] 6.17. Avoid module loading using a variable + + + +**ืืž;ืœืง:** Avoid requiring/importing another file with a path that was given as parameter due to the concern that it could have originated from user input. This rule can be extended for accessing files in general (i.e. `fs.readFile()`) or other sensitive resource access with dynamic variables originating from user input. [Eslint-plugin-security](https://www.npmjs.com/package/eslint-plugin-security) linter can catch such patterns and warn early enough + +**ืื—ืจืช:** Malicious user input could find its way to a parameter that is used to require tampered files, for example, a previously uploaded file on the file system, or access already existing system files. + +๐Ÿ”— [**Read More: Safe module loading**](./sections/security/safemoduleloading.md) + +

+ +## ![โœ”] 6.18. Run unsafe code in a sandbox + + + +**ืืž;ืœืง:** When tasked to run external code that is given at run-time (e.g. plugin), use any sort of 'sandbox' execution environment that isolates and guards the main code against the plugin. This can be achieved using a dedicated process (e.g. `cluster.fork()`), serverless environment or dedicated npm packages that act as a sandbox + +**ืื—ืจืช:** A plugin can attack through an endless variety of options like infinite loops, memory overloading, and access to sensitive process environment variables + +๐Ÿ”— [**Read More: Run unsafe code in a sandbox**](./sections/security/sandbox.md) + +

+ +## ![โœ”] 6.19. Take extra care when working with child processes + + + +**ืืž;ืœืง:** Avoid using child processes when possible and validate and sanitize input to mitigate shell injection attacks if you still have to. Prefer using `child_process.execFile` which by definition will only execute a single command with a set of attributes and will not allow shell parameter expansion. + +**ืื—ืจืช:** Naive use of child processes could result in remote command execution or shell injection attacks due to malicious user input passed to an unsanitized system command. + +๐Ÿ”— [**Read More: Be cautious when working with child processes**](./sections/security/childprocesses.md) + +

+ +## ![โœ”] 6.20. Hide error details from clients + + + +**ืืž;ืœืง:** An integrated express error handler hides the error details by default. However, great are the chances that you implement your own error handling logic with custom Error objects (considered by many as a best practice). If you do so, ensure not to return the entire Error object to the client, which might contain some sensitive application details + +**ืื—ืจืช:** Sensitive application details such as server file paths, third party modules in use, and other internal workflows of the application which could be exploited by an attacker, could be leaked from information found in a stack trace + +๐Ÿ”— [**Read More: Hide error details from client**](./sections/security/hideerrors.md) + +

+ +## ![โœ”] 6.21. Configure 2FA for npm or Yarn + + + +**ืืž;ืœืง:** Any step in the development chain should be protected with MFA (multi-factor authentication), npm/Yarn are a sweet opportunity for attackers who can get their hands on some developer's password. Using developer credentials, attackers can inject malicious code into libraries that are widely installed across projects and services. Maybe even across the web if published in public. Enabling 2-factor-authentication in npm leaves almost zero chances for attackers to alter your package code. + +**ืื—ืจืช:** [Have you heard about the eslint developer whose password was hijacked?](https://medium.com/@oprearocks/eslint-backdoor-what-it-is-and-how-to-fix-the-issue-221f58f1a8c8) + +

+ +## ![โœ”] 6.22. Modify session middleware settings + + + +**ืืž;ืœืง:** Each web framework and technology has its known weaknessesโ€Š-โ€Štelling an attacker which web framework we use is a great help for them. Using the default settings for session middlewares can expose your app to module- and framework-specific hijacking attacks in a similar way to the `X-Powered-By` header. Try hiding anything that identifies and reveals your tech stack (E.g. Node.js, express) + +**ืื—ืจืช:** Cookies could be sent over insecure connections, and an attacker might use session identification to identify the underlying framework of the web application, as well as module-specific vulnerabilities + +๐Ÿ”— [**Read More: Cookie and session security**](./sections/security/sessions.md) + +

+ +## ![โœ”] 6.23. Avoid DOS attacks by explicitly setting when a process should crash + + + +**ืืž;ืœืง:** The Node process will crash when errors are not handled. Many best practices even recommend to exit even though an error was caught and got handled. Express, for example, will crash on any asynchronous errorโ€Š-โ€Šunless you wrap routes with a catch clause. This opens a very sweet attack spot for attackers who recognize what input makes the process crash and repeatedly send the same request. There's no instant remedy for this but a few techniques can mitigate the pain: Alert with critical severity anytime a process crashes due to an unhandled error, validate the input and avoid crashing the process due to invalid user input, wrap all routes with a catch and consider not to crash when an error originated within a request (as opposed to what happens globally) + +**ืื—ืจืช:** This is just an educated guess: given many Node.js applications, if we try passing an empty JSON body to all POST requestsโ€Š-โ€Ša handful of applications will crash. At that point, we can just repeat sending the same request to take down the applications with ease + +

+ +## ![โœ”] 6.24. Prevent unsafe redirects + + + +**ืืž;ืœืง:** Redirects that do not validate user input can enable attackers to launch phishing scams, steal user credentials, and perform other malicious actions. + +**ืื—ืจืช:** If an attacker discovers that you are not validating external, user-supplied input, they may exploit this vulnerability by posting specially-crafted links on forums, social media, and other public places to get users to click it. + +๐Ÿ”— [**Read More: Prevent unsafe redirects**](./sections/security/saferedirects.md) + +

+ +## ![โœ”] 6.25. Avoid publishing secrets to the npm registry + + + +**ืืž;ืœืง:** Precautions should be taken to avoid the risk of accidentally publishing secrets to public npm registries. An `.npmignore` file can be used to ignore specific files or folders, or the `files` array in `package.json` can act as an allow list. + +**ืื—ืจืช:** Your project's API keys, passwords or other secrets are open to be abused by anyone who comes across them, which may result in financial loss, impersonation, and other risks. + +๐Ÿ”— [**Read More: Avoid publishing secrets**](./sections/security/avoid_publishing_secrets.md) + +

+ +## ![โœ”] 6.26 Inspect for outdated packages + +**ืืž;ืœืง:** Use your preferred tool (e.g. `npm outdated` or [npm-check-updates](https://www.npmjs.com/package/npm-check-updates)) to detect installed outdated packages, inject this check into your CI pipeline and even make a build fail in a severe scenario. For example, a severe scenario might be when an installed package is 5 patch commits behind (e.g. local version is 1.3.1 and repository version is 1.3.8) or it is tagged as deprecated by its author - kill the build and prevent deploying this version + +**ืื—ืจืช:** Your production will run packages that have been explicitly tagged by their author as risky + +

+ +## ![โœ”] 6.27. Import built-in modules using the 'node:' protocol + + + +**ืืž;ืœืง:** Import or require built-in Node.js modules using the 'node protocol' syntax: + +```javascript +import { functionName } from "node:module"; // note that 'node:' prefix +``` + +For example: + +```javascript +import { createServer } from "node:http"; +``` + +This style ensures that there is no ambiguity with global npm packages and makes it clear for the reader that the code refers to a well-trusted official module. This style can be enforced with the eslint rule ['prefer-node-protocol'](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-node-protocol.md) + +**ืื—ืจืช:** Using the import syntax without 'node:' prefix opens the door for [typosquatting attacks](https://en.wikipedia.org/wiki/Typosquatting) where one could mistakenly mistype a module name (e.g., 'event' instead of 'events) and get a malicious package that was built only to trick users into installing them + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# `7. ื˜ื™ื•ื˜ื”: ื‘ื™ืฆื•ืขื™ื` + +## Our contributors are working on this section. [Would you like to join?](https://github.com/goldbergyoni/nodebestpractices/issues/256) + +

+ +## ![โœ”] 7.1. Don't block the event loop + +**ืืž;ืœืง:** Avoid CPU intensive tasks as they will block the mostly single-threaded Event Loop and offload those to a dedicated thread, process or even a different technology based on the context. + +**ืื—ืจืช:** As the Event Loop is blocked, Node.js will be unable to handle other request thus causing delays for concurrent users. **3000 users are waiting for a response, the content is ready to be served, but one single request blocks the server from dispatching the results back** + +๐Ÿ”— [**Read More: Do not block the event loop**](./sections/performance/block-loop.md) + +


+ +## ![โœ”] 7.2. Prefer native JS methods over user-land utils like Lodash + +**ืืž;ืœืง:** It's often more penalising to use utility libraries like `lodash` and `underscore` over native methods as it leads to unneeded dependencies and slower performance. +Bear in mind that with the introduction of the new V8 engine alongside the new ES standards, native methods were improved in such a way that it's now about 50% more performant than utility libraries. + +**ืื—ืจืช:** You'll have to maintain less performant projects where you could have simply used what was **already** available or dealt with a few more lines in exchange of a few more files. + +๐Ÿ”— [**Read More: Native over user land utils**](./sections/performance/nativeoverutil.md) + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# `8. ื“ื•ืงืจ` + +๐Ÿ… Many thanks to [Bret Fisher](https://github.com/BretFisher) from whom we learned many of the following practices + +

+ +## ![โœ”] 8.1 Use multi-stage builds for leaner and more secure Docker images + +**ืืž;ืœืง:** Use multi-stage build to copy only necessary production artifacts. A lot of build-time dependencies and files are not needed for running your application. With multi-stage builds these resources can be used during build while the runtime environment contains only what's necessary. Multi-stage builds are an easy way to get rid of overweight and security threats. + +**ืื—ืจืช:** Larger images will take longer to build and ship, build-only tools might contain vulnerabilities and secrets only meant for the build phase might be leaked. + +### Example Dockerfile for multi-stage builds + +```dockerfile +FROM node:14.4.0 AS build + +COPY . . +RUN npm ci && npm run build + + +FROM node:slim-14.4.0 + +USER node +EXPOSE 8080 + +COPY --from=build /home/node/app/dist /home/node/app/package.json /home/node/app/package-lock.json ./ +RUN npm ci --production + +CMD [ "node", "dist/app.js" ] +``` + +๐Ÿ”— [**Read More: Use multi-stage builds**](./sections/docker/multi_stage_builds.md) + +


+ +## ![โœ”] 8.2. Bootstrap using `node` command, avoid `npm start` + +**ืืž;ืœืง:** Use `CMD ['node','server.js']` to start your app, avoid using npm scripts which don't pass OS signals to the code. This prevents problems with child-processes, signal handling, graceful shutdown and having zombie processes + +Update: [Starting from npm 7, npm claim](https://docs.npmjs.com/cli/v7/using-npm/changelog#706-2020-10-27) to pass signals. We follow and will update accordingly + +**ืื—ืจืช:** When no signals are passed, your code will never be notified about shutdowns. Without this, it will lose its chance to close properly possibly losing current requests and/or data + +[**Read More: Bootstrap container using node command, avoid npm start**](./sections/docker/bootstrap-using-node.md) + +


+ +## ![โœ”] 8.3. Let the Docker runtime handle replication and uptime + +**ืืž;ืœืง:** When using a Docker run time orchestrator (e.g., Kubernetes), invoke the Node.js process directly without intermediate process managers or custom code that replicate the process (e.g. PM2, Cluster module). The runtime platform has the highest amount of data and visibility for making placement decision - It knows best how many processes are needed, how to spread them and what to do in case of crashes + +**ืื—ืจืช:** Container keeps crashing due to lack of resources will get restarted indefinitely by the process manager. Should Kubernetes be aware of that, it could relocate it to a different roomy instance + +๐Ÿ”— [**Read More: Let the Docker orchestrator restart and replicate processes**](./sections/docker/restart-and-replicate-processes.md) + +


+ +## ![โœ”] 8.4. Use .dockerignore to prevent leaking secrets + +**TL;DR**: Include a `.dockerignore` file that filters out common secret files and development artifacts. By doing so, you might prevent secrets from leaking into the image. As a bonus the build time will significantly decrease. Also, ensure not to copy all files recursively rather explicitly choose what should be copied to Docker + +**Otherwise**: Common personal secret files like `.env`, `.aws` and `.npmrc` will be shared with anybody with access to the image (e.g. Docker repository) + +๐Ÿ”— [**Read More: Use .dockerignore**](./sections/docker/docker-ignore.md) + +


+ +## ![โœ”] 8.5. Clean-up dependencies before production + +**ืืž;ืœืง:** Although Dev-Dependencies are sometimes needed during the build and test life-cycle, eventually the image that is shipped to production should be minimal and clean from development dependencies. Doing so guarantees that only necessary code is shipped and the amount of potential attacks (i.e. attack surface) is minimized. When using multi-stage build (see dedicated bullet) this can be achieved by installing all dependencies first and finally running `npm ci --production` + +**ืื—ืจืช:** Many of the infamous npm security breaches were found within development packages (e.g. [eslint-scope](https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes)) + +๐Ÿ”— Read More: [Remove development dependencies](./sections/docker/install-for-production.md) + +


+ +## ![โœ”] 8.6. Shutdown smartly and gracefully + +**ืืž;ืœืง:** Handle the process SIGTERM event and clean-up all existing connection and resources. This should be done while responding to ongoing requests. In Dockerized runtimes, shutting down containers is not a rare event, rather a frequent occurrence that happen as part of routine work. Achieving this demands some thoughtful code to orchestrate several moving parts: The load balancer, keep-alive connections, the HTTP server and other resources + +**ืื—ืจืช:** Dying immediately means not responding to thousands of disappointed users + +๐Ÿ”— [**Read More: Graceful shutdown**](./sections/docker/graceful-shutdown.md) + +


+ +## ![โœ”] 8.7. Set memory limits using both Docker and v8 + +**ืืž;ืœืง:** Always configure a memory limit using both Docker and the JavaScript runtime flags. The Docker limit is needed to make thoughtful container placement decision, the --v8's flag max-old-space is needed to kick off the GC on time and prevent under utilization of memory. Practically, set the v8's old space memory to be a just bit less than the container limit + +**ืื—ืจืช:** The docker definition is needed to perform thoughtful scaling decision and prevent starving other citizens. Without also defining the v8's limits, it will under utilize the container resources - Without explicit instructions it crashes when utilizing ~50-60% of its host resources + +๐Ÿ”— [**Read More: Set memory limits using Docker only**](./sections/docker/memory-limit.md) + +


+ +## ![โœ”] 8.8. Plan for efficient caching + +**ืืž;ืœืง:** Rebuilding a whole docker image from cache can be nearly instantaneous if done correctly. The less updated instructions should be at the top of your Dockerfile and the ones constantly changing (like app code) should be at the bottom. + +**ืื—ืจืช:** Docker build will be very long and consume lot of resources even when making tiny changes + +๐Ÿ”— [**Read More: Leverage caching to reduce build times**](./sections/docker/use-cache-for-shorter-build-time.md) + +


+ +## ![โœ”] 8.9. Use explicit image reference, avoid `latest` tag + +**ืืž;ืœืง:** Specify an explicit image digest or versioned label, never refer to `latest`. Developers are often led to believe that specifying the `latest` tag will provide them with the most recent image in the repository however this is not the case. Using a digest guarantees that every instance of the service is running exactly the same code. + +In addition, referring to an image tag means that the base image is subject to change, as image tags cannot be relied upon for a deterministic install. Instead, if a deterministic install is expected, a SHA256 digest can be used to reference an exact image. + +**ืื—ืจืช:** A new version of a base image could be deployed into production with breaking changes, causing unintended application behaviour. + +๐Ÿ”— [**Read More: Understand image tags and use the "latest" tag with caution**](./sections/docker/image-tags.md) + +


+ +## ![โœ”] 8.10. Prefer smaller Docker base images + +**ืืž;ืœืง:** Large images lead to higher exposure to vulnerabilities and increased resource consumption. Using leaner Docker images, such as Slim and Alpine Linux variants, mitigates this issue. + +**ืื—ืจืช:** Building, pushing, and pulling images will take longer, unknown attack vectors can be used by malicious actors and more resources are consumed. + +๐Ÿ”— [**Read More: Prefer smaller images**](./sections/docker/smaller_base_images.md) + +


+ +## ![โœ”] 8.11. Clean-out build-time secrets, avoid secrets in args + +**ืืž;ืœืง:** Avoid secrets leaking from the Docker build environment. A Docker image is typically shared in multiple environment like CI and a registry that are not as sanitized as production. A typical example is an npm token which is usually passed to a dockerfile as argument. This token stays within the image long after it is needed and allows the attacker indefinite access to a private npm registry. This can be avoided by coping a secret file like `.npmrc` and then removing it using multi-stage build (beware, build history should be deleted as well) or by using Docker build-kit secret feature which leaves zero traces + +**ืื—ืจืช:** Everyone with access to the CI and docker registry will also get access to some precious organization secrets as a bonus + +๐Ÿ”— [**Read More: Clean-out build-time secrets**](./sections/docker/avoid-build-time-secrets.md) + +


+ +## ![โœ”] 8.12. Scan images for multi layers of vulnerabilities + +**ืืž;ืœืง:** Besides checking code dependencies vulnerabilities also scan the final image that is shipped to production. Docker image scanners check the code dependencies but also the OS binaries. This E2E security scan covers more ground and verifies that no bad guy injected bad things during the build. Consequently, it is recommended running this as the last step before deployment. There are a handful of free and commercial scanners that also provide CI/CD plugins + +**ืื—ืจืช:** Your code might be entirely free from vulnerabilities. However it might still get hacked due to vulnerable version of OS-level binaries (e.g. OpenSSL, TarBall) that are commonly being used by applications + +๐Ÿ”— [**Read More: Scan the entire image before production**](./sections/docker/scan-images.md) + +


+ +## ![โœ”] 8.13 Clean NODE_MODULE cache + +**ืืž;ืœืง:** After installing dependencies in a container remove the local cache. It doesn't make any sense to duplicate the dependencies for faster future installs since there won't be any further installs - A Docker image is immutable. Using a single line of code tens of MB (typically 10-50% of the image size) are shaved off + +**ืื—ืจืช:** The image that will get shipped to production will weigh 30% more due to files that will never get used + +๐Ÿ”— [**Read More: Clean NODE_MODULE cache**](./sections/docker/clean-cache.md) + +


+ +## ![โœ”] 8.14. Generic Docker practices + +**ืืž;ืœืง:** This is a collection of Docker advice that is not related directly to Node.js - the Node implementation is not much different than any other language. Click read more to skim through. + +๐Ÿ”— [**Read More: Generic Docker practices**](./sections/docker/generic-tips.md) + +


+ +## ![โœ”] 8.15. Lint your Dockerfile + +**ืืž;ืœืง:** Linting your Dockerfile is an important step to identify issues in your Dockerfile which differ from best practices. By checking for potential flaws using a specialised Docker linter, performance and security improvements can be easily identified, saving countless hours of wasted time or security issues in production code. + +**ืื—ืจืช:** Mistakenly the Dockerfile creator left Root as the production user, and also used an image from unknown source repository. This could be avoided with with just a simple linter. + +๐Ÿ”— [**Read More: Lint your Dockerfile**](./sections/docker/lint-dockerfile.md) + +


+ +

โฌ† ื—ื–ืจื” ืœืžืขืœื”

+ +# Milestones + +To maintain this guide and keep it up to date, we are constantly updating and improving the guidelines and best practices with the help of the community. You can follow our [milestones](https://github.com/goldbergyoni/nodebestpractices/milestones) and join the working groups if you want to contribute to this project + +
+ +## Translations + +All translations are contributed by the community. We will be happy to get any help with either completed, ongoing or new translations! + +### Completed translations + +- ![BR](./assets/flags/BR.png) [Brazilian Portuguese](./README.brazilian-portuguese.md) - Courtesy of [Marcelo Melo](https://github.com/marcelosdm) +- ![CN](./assets/flags/CN.png) [Chinese](./README.chinese.md) - Courtesy of [Matt Jin](https://github.com/mattjin) +- ![RU](./assets/flags/RU.png) [Russian](./README.russian.md) - Courtesy of [Alex Ivanov](https://github.com/contributorpw) +- ![PL](./assets/flags/PL.png) [Polish](./README.polish.md) - Courtesy of [Michal Biesiada](https://github.com/mbiesiad) +- ![JA](./assets/flags/JA.png) [Japanese](./README.japanese.md) - Courtesy of [Yuki Ota](https://github.com/YukiOta), [Yuta Azumi](https://github.com/YA21) +- ![EU](./assets/flags/EU.png) [Basque](README.basque.md) - Courtesy of [Ane Diaz de Tuesta](https://github.com/anediaz) & Joxefe Diaz de Tuesta + +### Translations in progress + +- ![FR](./assets/flags/FR.png) [French](./README.french.md) ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/129)) +- ![HE](./assets/flags/HE.png) Hebrew ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/156)) +- ![KR](./assets/flags/KR.png) [Korean](README.korean.md) - Courtesy of [Sangbeom Han](https://github.com/uronly14me) ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/94)) +- ![ES](./assets/flags/ES.png) [Spanish](https://github.com/goldbergyoni/nodebestpractices/blob/spanish-translation/README.spanish.md) ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/95)) +- ![TR](./assets/flags/TR.png) Turkish ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/139)) + +

+ +## Steering Committee + +Meet the steering committee members - the people who work together to provide guidance and future direction to the project. In addition, each member of the committee leads a project tracked under our [GitHub projects](https://github.com/goldbergyoni/nodebestpractices/projects). + + + +[Yoni Goldberg](https://github.com/goldbergyoni) + + + +Independent Node.js consultant who works with customers in the USA, Europe, and Israel on building large-scale Node.js applications. Many of the best practices above were first published at [goldbergyoni.com](https://goldbergyoni.com). Reach Yoni at [@goldbergyoni](https://github.com/goldbergyoni) or [me@goldbergyoni.com](mailto:me@goldbergyoni.com) + +
+ +Josh Hemphill + +[Josh Hemphill](https://github.com/josh-hemphill) + + + + +Full Stack Software Engineer / Developer specializing in Security, DevOps/DevSecOps, and ERP Integrations. + +
+ +Raz Luvaton + +[Raz Luvaton](https://github.com/rluvaton) + + + +Full Stack Developer who knows how to exit from Vim and loves Architecture, Virtualization and Security. + +
+ +## Contributing + +If you've ever wanted to contribute to open source, now is your chance! See the [contributing docs](.operations/CONTRIBUTING.md) for more information. + +## Contributors โœจ + +Thanks goes to these wonderful people who have contributed to this repository! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Kevin Rambaud
Kevin Rambaud

๐Ÿ–‹
Michael Fine
Michael Fine

๐Ÿ–‹
Shreya Dahal
Shreya Dahal

๐Ÿ–‹
Matheus Cruz Rocha
Matheus Cruz Rocha

๐Ÿ–‹
Yog Mehta
Yog Mehta

๐Ÿ–‹
Kudakwashe Paradzayi
Kudakwashe Paradzayi

๐Ÿ–‹
t1st3
t1st3

๐Ÿ–‹
mulijordan1976
mulijordan1976

๐Ÿ–‹
Matan Kushner
Matan Kushner

๐Ÿ–‹
Fabio Hiroki
Fabio Hiroki

๐Ÿ–‹
James Sumners
James Sumners

๐Ÿ–‹
Dan Gamble
Dan Gamble

๐Ÿ–‹
PJ Trainor
PJ Trainor

๐Ÿ–‹
Remek Ambroziak
Remek Ambroziak

๐Ÿ–‹
Yoni Jah
Yoni Jah

๐Ÿ–‹
Misha Khokhlov
Misha Khokhlov

๐Ÿ–‹
Evgeny Orekhov
Evgeny Orekhov

๐Ÿ–‹
-
-

๐Ÿ–‹
Isaac Halvorson
Isaac Halvorson

๐Ÿ–‹
Vedran Karaฤiฤ‡
Vedran Karaฤiฤ‡

๐Ÿ–‹
lallenlowe
lallenlowe

๐Ÿ–‹
Nathan Wells
Nathan Wells

๐Ÿ–‹
Paulo Reis
Paulo Reis

๐Ÿ–‹
syzer
syzer

๐Ÿ–‹
David Sancho
David Sancho

๐Ÿ–‹
Robert Manolea
Robert Manolea

๐Ÿ–‹
Xavier Ho
Xavier Ho

๐Ÿ–‹
Aaron
Aaron

๐Ÿ–‹
Jan Charles Maghirang Adona
Jan Charles Maghirang Adona

๐Ÿ–‹
Allen
Allen

๐Ÿ–‹
Leonardo Villela
Leonardo Villela

๐Ÿ–‹
Michaล‚ Zaล‚ฤ™cki
Michaล‚ Zaล‚ฤ™cki

๐Ÿ–‹
Chris Nicola
Chris Nicola

๐Ÿ–‹
Alejandro Corredor
Alejandro Corredor

๐Ÿ–‹
cwar
cwar

๐Ÿ–‹
Yuwei
Yuwei

๐Ÿ–‹
Utkarsh Bhatt
Utkarsh Bhatt

๐Ÿ–‹
Duarte Mendes
Duarte Mendes

๐Ÿ–‹
Jason Kim
Jason Kim

๐Ÿ–‹
Mitja O.
Mitja O.

๐Ÿ–‹
Sandro Miguel Marques
Sandro Miguel Marques

๐Ÿ–‹
Gabe
Gabe

๐Ÿ–‹
Ron Gross
Ron Gross

๐Ÿ–‹
Valeri Karpov
Valeri Karpov

๐Ÿ–‹
Sergio Bernal
Sergio Bernal

๐Ÿ–‹
Nikola Telkedzhiev
Nikola Telkedzhiev

๐Ÿ–‹
Vitor Godoy
Vitor Godoy

๐Ÿ–‹
Manish Saraan
Manish Saraan

๐Ÿ–‹
Sangbeom Han
Sangbeom Han

๐Ÿ–‹
blackmatch
blackmatch

๐Ÿ–‹
Joe Reeve
Joe Reeve

๐Ÿ–‹
Ryan Busby
Ryan Busby

๐Ÿ–‹
Iman Mohamadi
Iman Mohamadi

๐Ÿ–‹
Sergii Paryzhskyi
Sergii Paryzhskyi

๐Ÿ–‹
Kapil Patel
Kapil Patel

๐Ÿ–‹
่ฟทๆธก
่ฟทๆธก

๐Ÿ–‹
Hozefa
Hozefa

๐Ÿ–‹
Ethan
Ethan

๐Ÿ–‹
Sam
Sam

๐Ÿ–‹
Arlind
Arlind

๐Ÿ–‹
Teddy Toussaint
Teddy Toussaint

๐Ÿ–‹
Lewis
Lewis

๐Ÿ–‹
Gabriel Lidenor
Gabriel Lidenor

๐Ÿ–‹
Roman
Roman

๐Ÿ–‹
Francozeira
Francozeira

๐Ÿ–‹
Invvard
Invvard

๐Ÿ–‹
Rรดmulo Garofalo
Rรดmulo Garofalo

๐Ÿ–‹
Tho Q Luong
Tho Q Luong

๐Ÿ–‹
Burak Shen
Burak Shen

๐Ÿ–‹
Martin Muzatko
Martin Muzatko

๐Ÿ–‹
Jared Collier
Jared Collier

๐Ÿ–‹
Hilton Meyer
Hilton Meyer

๐Ÿ–‹
ChangJoo Park(๋ฐ•์ฐฝ์ฃผ)
ChangJoo Park(๋ฐ•์ฐฝ์ฃผ)

๐Ÿ–‹
Masahiro Sakaguchi
Masahiro Sakaguchi

๐Ÿ–‹
Keith Holliday
Keith Holliday

๐Ÿ–‹
coreyc
coreyc

๐Ÿ–‹
Maximilian Berkmann
Maximilian Berkmann

๐Ÿ–‹
Douglas Mariano Valero
Douglas Mariano Valero

๐Ÿ–‹
Marcelo Melo
Marcelo Melo

๐Ÿ–‹
Mehmet Perk
Mehmet Perk

๐Ÿ–‹
ryan ouyang
ryan ouyang

๐Ÿ–‹
Shabeer
Shabeer

๐Ÿ–‹
Eduard Kyvenko
Eduard Kyvenko

๐Ÿ–‹
Deyvison Rocha
Deyvison Rocha

๐Ÿ–‹
George Mamer
George Mamer

๐Ÿ–‹
Konstantinos Leimonis
Konstantinos Leimonis

๐Ÿ–‹
Oliver Lluberes
Oliver Lluberes

๐ŸŒ
Tien Do
Tien Do

๐Ÿ–‹
Ranvir Singh
Ranvir Singh

๐Ÿ–‹
Vadim Nicolaev
Vadim Nicolaev

๐Ÿ–‹ ๐ŸŒ
German Gamboa Gonzalez
German Gamboa Gonzalez

๐Ÿ–‹
Hafez
Hafez

๐Ÿ–‹
Chandiran
Chandiran

๐Ÿ–‹
VinayaSathyanarayana
VinayaSathyanarayana

๐Ÿ–‹
Kim Kern
Kim Kern

๐Ÿ–‹
Kenneth Freitas
Kenneth Freitas

๐Ÿ–‹
songe
songe

๐Ÿ–‹
Kirill Shekhovtsov
Kirill Shekhovtsov

๐Ÿ–‹
Serge
Serge

๐Ÿ–‹
keyrwinz
keyrwinz

๐Ÿ–‹
Dmitry Nikitenko
Dmitry Nikitenko

๐Ÿ–‹
bushuai
bushuai

๐Ÿ‘€ ๐Ÿ–‹
Benjamin Gruenbaum
Benjamin Gruenbaum

๐Ÿ–‹
Ezequiel
Ezequiel

๐ŸŒ
Juan Josรฉ Rodrรญguez
Juan Josรฉ Rodrรญguez

๐ŸŒ
Or Bin
Or Bin

๐Ÿ–‹
Andreo Vieira
Andreo Vieira

๐Ÿ–‹
Michael Solomon
Michael Solomon

๐Ÿ–‹
Jimmy Callin
Jimmy Callin

๐Ÿ–‹
Siddharth
Siddharth

๐Ÿ–‹
Ryan Smith
Ryan Smith

๐Ÿ–‹
Tom Boettger
Tom Boettger

๐Ÿ–‹
Joaquรญn Ormaechea
Joaquรญn Ormaechea

๐ŸŒ
dfrzuz
dfrzuz

๐ŸŒ
Victor Homyakov
Victor Homyakov

๐Ÿ–‹
Josh
Josh

๐Ÿ–‹ ๐Ÿ›ก๏ธ
Alec Francis
Alec Francis

๐Ÿ–‹
arjun6610
arjun6610

๐Ÿ–‹
Jan Osch
Jan Osch

๐Ÿ–‹
Thiago Rotondo Sampaio
Thiago Rotondo Sampaio

๐ŸŒ
Alexsey
Alexsey

๐Ÿ–‹
Luis A. Acurero
Luis A. Acurero

๐ŸŒ
Lucas Romano
Lucas Romano

๐ŸŒ
Denise Case
Denise Case

๐Ÿ–‹
Nick Ribal
Nick Ribal

๐Ÿ–‹ ๐Ÿ‘€
0xflotus
0xflotus

๐Ÿ–‹
Jonathan Chen
Jonathan Chen

๐Ÿ–‹
Dilan Srilal
Dilan Srilal

๐Ÿ–‹
vladthelittleone
vladthelittleone

๐ŸŒ
Nik Osvalds
Nik Osvalds

๐Ÿ–‹
Daniel Kiss
Daniel Kiss

๐Ÿ“–
Forresst
Forresst

๐Ÿ–‹
Jonathan Svenheden
Jonathan Svenheden

๐Ÿ–‹
AustrisC
AustrisC

๐Ÿ–‹
kyeongtae kim
kyeongtae kim

๐ŸŒ
007
007

๐Ÿ–‹
Ane Diaz de Tuesta
Ane Diaz de Tuesta

๐ŸŒ ๐Ÿ–‹
YukiOta
YukiOta

๐ŸŒ
Frazer Smith
Frazer Smith

๐Ÿ–‹
Raz Luvaton
Raz Luvaton

๐Ÿ–‹
Yuta Azumi
Yuta Azumi

๐Ÿ–‹
andrewjbarbour
andrewjbarbour

๐Ÿ–‹
mr
mr

๐Ÿ–‹
Aleksandar
Aleksandar

๐Ÿ–‹
Owl
Owl

๐Ÿ–‹
Yedidya Schwartz
Yedidya Schwartz

๐Ÿ–‹ ๐Ÿ’ก
ari
ari

๐Ÿ–‹
Thomas Kรถnig
Thomas Kรถnig

๐Ÿ–‹
Kalle Lรคmsรค
Kalle Lรคmsรค

๐Ÿ–‹
Wyatt
Wyatt

๐Ÿ–‹
KHADIR Tayeb
KHADIR Tayeb

๐Ÿ–‹
Shankar Regmi
Shankar Regmi

๐Ÿ–‹
Shubham
Shubham

๐Ÿ–‹
Lucas Alves
Lucas Alves

๐Ÿ–‹
Benjamin
Benjamin

๐Ÿ–‹
Yeoh Joer
Yeoh Joer

๐Ÿ–‹
Miigon
Miigon

๐Ÿ–‹
Rostislav Bogorad
Rostislav Bogorad

๐Ÿ–‹
Flouse
Flouse

๐Ÿ–‹
Tarantini Pereira
Tarantini Pereira

๐Ÿ–‹
Kazuki Matsuo
Kazuki Matsuo

๐Ÿ–‹
Adam Smith
Adam Smith

๐Ÿ–‹
Dohyeon Ko
Dohyeon Ko

๐Ÿ–‹
Vladislav Legkov
Vladislav Legkov

๐Ÿ–‹
Kerollos Magdy
Kerollos Magdy

๐Ÿ–‹
Erez Lieberman
Erez Lieberman

๐Ÿ–‹
Breno Macedo
Breno Macedo

๐Ÿ–‹
Fernando Flores
Fernando Flores

๐ŸŒ
Rafael Brito
Rafael Brito

๐ŸŒ
Emiliano Peralta
Emiliano Peralta

๐ŸŒ
Shin, SJ
Shin, SJ

๐Ÿ–‹
Benjamin Forster
Benjamin Forster

๐Ÿ–‹
Daniele Fedeli
Daniele Fedeli

๐Ÿ–‹
djob195
djob195

๐Ÿ–‹
antspk
antspk

๐Ÿ–‹
์ •์ง„์˜
์ •์ง„์˜

๐Ÿ–‹
kkk-cashwalk
kkk-cashwalk

๐Ÿ–‹
apainintheneck
apainintheneck

๐Ÿ–‹
Fajar Budhi Iswanda
Fajar Budhi Iswanda

๐Ÿ–‹
์ด์ฃผํ˜ธ
์ด์ฃผํ˜ธ

๐Ÿ–‹
Singh
Singh

๐Ÿ–‹
Alex Dumitru
Alex Dumitru

๐Ÿ–‹
Anton Lykhatskyi
Anton Lykhatskyi

๐Ÿ–‹
sangwonlee
sangwonlee

๐Ÿ–‹
Eugenio Berretta
Eugenio Berretta

๐Ÿ–‹
soranakk
soranakk

๐Ÿ–‹
๊ณ ์ค€์˜
๊ณ ์ค€์˜

๐Ÿ–‹ ๐Ÿ’ป
Guilherme Portella
Guilherme Portella

๐Ÿ–‹
Andrรฉ Esser
Andrรฉ Esser

๐Ÿ–‹
Scc
Scc

๐ŸŒ
Mauro Accornero
Mauro Accornero

๐Ÿ–‹
no-yan
no-yan

๐Ÿ–‹
+ + + + + + +### Steering Committee Emeriti + +[Bruno Scheufler](https://github.com/BrunoScheufler) + + +๐Ÿ’ป full-stack web engineer, Node.js & GraphQL enthusiast + +
+ + + +[Kyle Martin](https://github.com/js-kyle) + + + +Full Stack Developer & Site Reliability Engineer based in New Zealand, interested in web application security, and architecting and building Node.js applications to perform at global scale. + +
+ + + +[Kevyn Bruyere](https://github.com/kevynb) + + +Independent full-stack developer with a taste for Ops and automation. + +
+ + + +[Sagir Khan](https://github.com/sagirk) + + + + +Deep specialist in JavaScript and its ecosystem โ€” React, Node.js, TypeScript, GraphQL, MongoDB, pretty much anything that involves JS/JSON in any layer of the system โ€” building products using the web platform for the worldโ€™s most recognized brands. Individual Member of the Node.js Foundation. diff --git a/README.md b/README.md index 5fcaf4dec..065d37585 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
- 102 items Last update: March 21, 2023 Updated for Node 14.0.0 + 102 items Last update: January 3rd, 2024 Updated for Node 22.0.0

@@ -22,19 +22,13 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin
-## ๐Ÿš€ We have an [official Node.js starter - Practica.js](https://github.com/practicajs/practica). Use it to generate a new solution skeleton with all the practices baked inside. Or just it to learn by code examples +# ๐ŸŽŠ 2024 edition is here! -
- -# Latest Best Practices and News - -- **โœจ 80,000 stars**: Blushing, surprised and proud! - -- **๐Ÿ”– New menu and tags**: Our menu is collapsible now and includes `#tags`. New visitors can read `#strategic` items first. Returning visitors can focus on `#new` content. Seniors can filter for `#advanced` items. Courtesy of the one and only [Rubek Joshi](https://github.com/rubek-joshi) +- **๐Ÿ›ฐ Modernized to 2024**: Tons of text edits, new recommended libraries, and some new best practices -- **๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ New family member!**: A new repository joins our family - [Node.js Integration Tests Best Practices โœจ](https://github.com/testjavascript/nodejs-integration-tests-best-practices). It includes 40+ best practices for writing awesome and performant Node.js component tests +- **โœจ Easily focus on new content**: Already visited before? Search for `#new` or `#updated` tags for new content only -- **![FR](./assets/flags/FR.png) French translation!1! :** The latest translation that joins our international guide is French. Bienvenue +- **๐Ÿ”– Curious to see examples? We have a starter**: Visit [Practica.js](https://github.com/practicajs/practica), our application example and boilerplate (beta) to see some practices in action

@@ -48,18 +42,25 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin

+# By Yoni Goldberg + +### Learn with me: As a consultant, I engage with worldwide teams on various activities like workshops and code reviews. ๐ŸŽ‰AND... Hold on, I've just launched my [beyond-the-basics testing course, which is on a ๐ŸŽ limited-time sale](https://testjavascript.com/) until August 7th + +

+ ## Table of Contents
- 1. Project Structure Practices (5) + 1. Project Architecture Practices (6) -  [1.1 Structure your solution by components `#strategic`](#-11-structure-your-solution-by-components)
-  [1.2 Layer your components, keep the web layer within its boundaries `#strategic`](#-12-layer-your-components-keep-the-web-layer-within-its-boundaries)
-  [1.3 Wrap common utilities as npm packages](#-13-wrap-common-utilities-as-npm-packages)
-  [1.4 Separate Express 'app' and 'server'](#-14-separate-express-app-and-server)
-  [1.5 Use environment aware, secure and hierarchical config `#modified-recently`](#-15-use-environment-aware-secure-and-hierarchical-config)
+  [1.1 Structure your solution by components `#strategic` `#updated`](#-11-structure-your-solution-by-business-components)
+  [1.2 Layer your components, keep the web layer within its boundaries `#strategic` `#updated`](#-12-layer-your-components-with-3-tiers-keep-the-web-layer-within-its-boundaries)
+  [1.3 Wrap common utilities as packages, consider publishing](#-13-wrap-common-utilities-as-packages-consider-publishing)
+  [1.4 Use environment aware, secure and hierarchical config `#updated`](#-14-use-environment-aware-secure-and-hierarchical-config)
+  [1.5 Consider all the consequences when choosing the main framework `#new`](#-15-consider-all-the-consequences-when-choosing-the-main-framework)
+  [1.6 Use TypeScript sparingly and thoughtfully `#new`](#-16-use-typescript-sparingly-and-thoughtfully)
@@ -69,37 +70,39 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin   [2.1 Use Async-Await or promises for async error handling](#-21-use-async-await-or-promises-for-async-error-handling)
-  [2.2 Use only the built-in Error object `#strategic`](#-22-use-only-the-built-in-error-object)
-  [2.3 Distinguish operational vs programmer errors `#strategic`](#-23-distinguish-operational-vs-programmer-errors)
+  [2.2 Extend the built-in Error object `#strategic` `#updated`](#-22-extend-the-built-in-error-object)
+  [2.3 Distinguish operational vs programmer errors `#strategic` `#updated`](#-23-distinguish-catastrophic-errors-from-operational-errors)
  [2.4 Handle errors centrally, not within a middleware `#strategic`](#-24-handle-errors-centrally-not-within-a-middleware)
-  [2.5 Document API errors using Swagger or GraphQL `#modified-recently`](#-25-document-api-errors-using-swagger-or-graphql)
+  [2.5 Document API errors using OpenAPI or GraphQL](#-25-document-api-errors-using-openapi-or-graphql)
  [2.6 Exit the process gracefully when a stranger comes to town `#strategic`](#-26-exit-the-process-gracefully-when-a-stranger-comes-to-town)
-  [2.7 Use a mature logger to increase error visibility](#-27-use-a-mature-logger-to-increase-error-visibility)
-  [2.8 Test error flows using your favorite test framework](#-28-test-error-flows-using-your-favorite-test-framework)
+  [2.7 Use a mature logger to increase errors visibility `#updated`](#-27-use-a-mature-logger-to-increase-errors-visibility)
+  [2.8 Test error flows using your favorite test framework `#updated`](#-28-test-error-flows-using-your-favorite-test-framework)
  [2.9 Discover errors and downtime using APM products](#-29-discover-errors-and-downtime-using-apm-products)
-  [2.10 Catch unhandled promise rejections `#modified-recently`](#-210-catch-unhandled-promise-rejections)
+  [2.10 Catch unhandled promise rejections `#updated`](#-210-catch-unhandled-promise-rejections)
  [2.11 Fail fast, validate arguments using a dedicated library](#-211-fail-fast-validate-arguments-using-a-dedicated-library)
  [2.12 Always await promises before returning to avoid a partial stacktrace `#new`](#-212-always-await-promises-before-returning-to-avoid-a-partial-stacktrace)
+  [2.13 Subscribe to event emitters 'error' event `#new`](#-213-subscribe-to-event-emitters-and-streams-error-event)
- 3. Code Style Practices (12) + 3. Code Style Practices (12)   [3.1 Use ESLint `#strategic`](#-31-use-eslint)
-  [3.2 Node.js specific plugins](#-32-nodejs-specific-plugins)
+  [3.2 Use Node.js eslint extension plugins `#updated`](#-32-use-nodejs-eslint-extension-plugins)
  [3.3 Start a Codeblock's Curly Braces on the Same Line](#-33-start-a-codeblocks-curly-braces-on-the-same-line)
  [3.4 Separate your statements properly](#-34-separate-your-statements-properly)
  [3.5 Name your functions](#-35-name-your-functions)
  [3.6 Use naming conventions for variables, constants, functions and classes](#-36-use-naming-conventions-for-variables-constants-functions-and-classes)
  [3.7 Prefer const over let. Ditch the var](#-37-prefer-const-over-let-ditch-the-var)
  [3.8 Require modules first, not inside functions](#-38-require-modules-first-not-inside-functions)
-  [3.9 Require modules by folders, as opposed to the files directly](#-39-require-modules-by-folders-as-opposed-to-the-files-directly)
+  [3.9 Set an explicit entry point to a module/folder `#updated`](#-39-set-an-explicit-entry-point-to-a-modulefolder)
  [3.10 Use the === operator](#-310-use-the--operator)
  [3.11 Use Async Await, avoid callbacks `#strategic`](#-311-use-async-await-avoid-callbacks)
  [3.12 Use arrow function expressions (=>)](#-312-use-arrow-function-expressions-)
+  [3.13 Avoid effects outside of functions `#new`](#-313-avoid-effects-outside-of-functions)
@@ -111,16 +114,16 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin   [4.1 At the very least, write API (component) testing `#strategic`](#-41-at-the-very-least-write-api-component-testing)
  [4.2 Include 3 parts in each test name `#new`](#-42-include-3-parts-in-each-test-name)
  [4.3 Structure tests by the AAA pattern `#strategic`](#-43-structure-tests-by-the-aaa-pattern)
-  [4.4 Detect code issues with a linter](#-44-detect-code-issues-with-a-linter)
+  [4.4 Ensure Node version is unified `#new`](#-44-ensure-node-version-is-unified)
  [4.5 Avoid global test fixtures and seeds, add data per-test `#strategic`](#-45-avoid-global-test-fixtures-and-seeds-add-data-per-test)
-  [4.6 Constantly inspect for vulnerable dependencies](#-46-constantly-inspect-for-vulnerable-dependencies)
-  [4.7 Tag your tests `#advanced`](#-47-tag-your-tests)
-  [4.8 Check your test coverage, it helps to identify wrong test patterns](#-48-check-your-test-coverage-it-helps-to-identify-wrong-test-patterns)
-  [4.9 Inspect for outdated packages](#-49-inspect-for-outdated-packages)
-  [4.10 Use production-like environment for e2e testing](#-410-use-production-like-environment-for-e2e-testing)
-  [4.11 Refactor regularly using static analysis tools](#-411-refactor-regularly-using-static-analysis-tools)
-  [4.12 Carefully choose your CI platform (Jenkins vs CircleCI vs Travis vs Rest of the world)](#-412-carefully-choose-your-ci-platform-jenkins-vs-circleci-vs-travis-vs-rest-of-the-world)
-  [4.13 Test your middlewares in isolation](#-413-test-your-middlewares-in-isolation)
+  [4.6 Tag your tests `#advanced`](#-46-tag-your-tests)
+  [4.7 Check your test coverage, it helps to identify wrong test patterns](#-47-check-your-test-coverage-it-helps-to-identify-wrong-test-patterns)
+  [4.8 Use production-like environment for e2e testing](#-48-use-production-like-environment-for-e2e-testing)
+  [4.9 Refactor regularly using static analysis tools](#-49-refactor-regularly-using-static-analysis-tools)
+  [4.10 Mock responses of external HTTP services #advanced `#new` `#advanced`](#-410-mock-responses-of-external-http-services)
+  [4.11 Test your middlewares in isolation](#-411-test-your-middlewares-in-isolation)
+  [4.12 Specify a port in production, randomize in testing `#new`](#-412-specify-a-port-in-production-randomize-in-testing)
+  [4.13 Test the five possible outcomes #strategic `#new`](#-413-test-the-five-possible-outcomes)
@@ -130,23 +133,23 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin   [5.1. Monitoring `#strategic`](#-51-monitoring)
-  [5.2. Increase transparency using smart logging `#strategic`](#-52-increase-transparency-using-smart-logging)
+  [5.2. Increase the observability using smart logging `#strategic`](#-52-increase-the-observability-using-smart-logging)
  [5.3. Delegate anything possible (e.g. gzip, SSL) to a reverse proxy `#strategic`](#-53-delegate-anything-possible-eg-gzip-ssl-to-a-reverse-proxy)
  [5.4. Lock dependencies](#-54-lock-dependencies)
  [5.5. Guard process uptime using the right tool](#-55-guard-process-uptime-using-the-right-tool)
  [5.6. Utilize all CPU cores](#-56-utilize-all-cpu-cores)
  [5.7. Create a โ€˜maintenance endpointโ€™](#-57-create-a-maintenance-endpoint)
-  [5.8. Discover errors and downtime using APM products `#advanced`](#-58-discover-errors-and-downtime-using-apm-products)
+  [5.8. Discover the unknowns using APM products `#advanced` `#updated`](#-58-discover-the-unknowns-using-apm-products)
  [5.9. Make your code production-ready](#-59-make-your-code-production-ready)
  [5.10. Measure and guard the memory usage `#advanced`](#-510-measure-and-guard-the-memory-usage)
  [5.11. Get your frontend assets out of Node](#-511-get-your-frontend-assets-out-of-node)
-  [5.12. Be stateless, kill your servers almost every day](#-512-be-stateless-kill-your-servers-almost-every-day)
+  [5.12. Strive to be stateless `#strategic`](#-512-strive-to-be-stateless)
  [5.13. Use tools that automatically detect vulnerabilities](#-513-use-tools-that-automatically-detect-vulnerabilities)
  [5.14. Assign a transaction id to each log statement `#advanced`](#-514-assign-a-transaction-id-to-each-log-statement)
  [5.15. Set NODE_ENV=production](#-515-set-node_envproduction)
-  [5.16. Design automated, atomic and zero-downtime deployments `#advanced`](#-516-design-automated-atomic-and-zero-downtime-deployments)
+  [5.16. Design automated, atomic and zero-downtime deployments `#advanced`](#-516-design-automated-atomic-and-zero-downtime-deployments)
  [5.17. Use an LTS release of Node.js](#-517-use-an-lts-release-of-nodejs)
-  [5.18. Don't route logs within the app](#-518-dont-route-logs-within-the-app)
+  [5.18. Log to stdout, avoid specifying log destination within the app `#updated`](#-518-log-to-stdout-avoid-specifying-log-destination-within-the-app)
  [5.19. Install your packages with npm ci `#new`](#-519-install-your-packages-with-npm-ci)
@@ -158,7 +161,7 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin   [6.1. Embrace linter security rules](#-61-embrace-linter-security-rules)
  [6.2. Limit concurrent requests using a middleware](#-62-limit-concurrent-requests-using-a-middleware)
-  [6.3 Extract secrets from config files or use packages to encrypt them `#strategic`](#-63-extract-secrets-from-config-files-or-use-packages-to-encrypt-them)
+  [6.3 Extract secrets from config files or use packages to encrypt them `#strategic`](#-63-extract-secrets-from-config-files-or-use-packages-to-encrypt-them)
  [6.4. Prevent query injection vulnerabilities with ORM/ODM libraries `#strategic`](#-64-prevent-query-injection-vulnerabilities-with-ormodm-libraries)
  [6.5. Collection of generic security best practices](#-65-collection-of-generic-security-best-practices)
  [6.6. Adjust the HTTP response headers for enhanced security](#-66-adjust-the-http-response-headers-for-enhanced-security)
@@ -174,13 +177,15 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin   [6.16. Prevent evil RegEx from overloading your single thread execution](#-616-prevent-evil-regex-from-overloading-your-single-thread-execution)
  [6.17. Avoid module loading using a variable](#-617-avoid-module-loading-using-a-variable)
  [6.18. Run unsafe code in a sandbox](#-618-run-unsafe-code-in-a-sandbox)
-  [6.19. Take extra care when working with child processes `#advanced`](#-619-take-extra-care-when-working-with-child-processes)
+  [6.19. Take extra care when working with child processes `#advanced`](#-619-take-extra-care-when-working-with-child-processes)
  [6.20. Hide error details from clients](#-620-hide-error-details-from-clients)
  [6.21. Configure 2FA for npm or Yarn `#strategic`](#-621-configure-2fa-for-npm-or-yarn)
  [6.22. Modify session middleware settings](#-622-modify-session-middleware-settings)
-  [6.23. Avoid DOS attacks by explicitly setting when a process should crash `#advanced`](#-623-avoid-dos-attacks-by-explicitly-setting-when-a-process-should-crash)
+  [6.23. Avoid DOS attacks by explicitly setting when a process should crash `#advanced`](#-623-avoid-dos-attacks-by-explicitly-setting-when-a-process-should-crash)
  [6.24. Prevent unsafe redirects](#-624-prevent-unsafe-redirects)
  [6.25. Avoid publishing secrets to the npm registry](#-625-avoid-publishing-secrets-to-the-npm-registry)
+  [6.26. 6.26 Inspect for outdated packages](#-626-inspect-for-outdated-packages)
+  [6.27. Import built-in modules using the 'node:' protocol `#new`](#-627-import-built-in-modules-using-the-node-protocol)
@@ -204,70 +209,122 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin   [8.3. Let the Docker runtime handle replication and uptime `#strategic`](#-83-let-the-docker-runtime-handle-replication-and-uptime)
  [8.4. Use .dockerignore to prevent leaking secrets](#-84-use-dockerignore-to-prevent-leaking-secrets)
  [8.5. Clean-up dependencies before production](#-85-clean-up-dependencies-before-production)
-  [8.6. Shutdown smartly and gracefully `#advanced`](#-86-shutdown-smartly-and-gracefully)
-  [8.7. Set memory limits using both Docker and v8 `#advanced #strategic`](#-87-set-memory-limits-using-both-docker-and-v8)
+  [8.6. Shutdown smartly and gracefully `#advanced`](#-86-shutdown-smartly-and-gracefully)
+  [8.7. Set memory limits using both Docker and v8 `#advanced` `#strategic`](#-87-set-memory-limits-using-both-docker-and-v8)
  [8.8. Plan for efficient caching](#-88-plan-for-efficient-caching)
  [8.9. Use explicit image reference, avoid latest tag](#-89-use-explicit-image-reference-avoid-latest-tag)
  [8.10. Prefer smaller Docker base images](#-810-prefer-smaller-docker-base-images)
-  [8.11. Clean-out build-time secrets, avoid secrets in args `#strategic #new`](#-811-clean-out-build-time-secrets-avoid-secrets-in-args)
-  [8.12. Scan images for multi layers of vulnerabilities `#advanced`](#-812-scan-images-for-multi-layers-of-vulnerabilities)
+  [8.11. Clean-out build-time secrets, avoid secrets in args `#strategic #new`](#-811-clean-out-build-time-secrets-avoid-secrets-in-args)
+  [8.12. Scan images for multi layers of vulnerabilities `#advanced`](#-812-scan-images-for-multi-layers-of-vulnerabilities)
  [8.13 Clean NODE_MODULE cache](#-813-clean-node_module-cache)
  [8.14. Generic Docker practices](#-814-generic-docker-practices)
-  [8.15. Lint your Dockerfile `#new`](#-815-lint-your-dockerfile)
+  [8.15. Lint your Dockerfile `#new`](#-815-lint-your-dockerfile)


-# `1. Project Structure Practices` +# `1. Project Architecture Practices` + +## ![โœ”] 1.1 Structure your solution by business components -## ![โœ”] 1.1 Structure your solution by components +### `๐Ÿ“ #updated` -**TL;DR:** The worst large applications pitfall is maintaining a huge code base with hundreds of dependencies - such a monolith slows down developers as they try to incorporate new features. Instead, partition your code into components, each gets its folder or a dedicated codebase, and ensure that each unit is kept small and simple. Visit 'Read More' below to see examples of correct project structure +**TL;DR:** The root of a system should contain folders or repositories that represent reasonably sized business modules. Each component represents a product domain (i.e., bounded context), like 'user-component', 'order-component', etc. Each component has its own API, logic, and logical database. What is the significant merit? With an autonomous component, every change is performed over a granular and smaller scope - the mental overload, development friction, and deployment fear are much smaller and better. As a result, developers can move much faster. This does not necessarily demand physical separation and can be achieved using a Monorepo or with a multi-repo + +```bash +my-system +โ”œโ”€ apps (components) +โ”‚ โ”œโ”€ orders +โ”‚ โ”œโ”€ users +โ”‚ โ”œโ”€ payments +โ”œโ”€ libraries (generic cross-component functionality) +โ”‚ โ”œโ”€ logger +โ”‚ โ”œโ”€ authenticator +``` -**Otherwise:** When developers who code new features struggle to realize the impact of their change and fear to break other dependent components - deployments become slower and riskier. It's also considered harder to scale-out when all the business units are not separated +**Otherwise:** when artifacts from various modules/topics are mixed together, there are great chances of a tightly-coupled 'spaghetti' system. For example, in an architecture where 'module-a controller' might call 'module-b service', there are no clear modularity borders - every code change might affect anything else. With this approach, developers who code new features struggle to realize the scope and impact of their change. Consequently, they fear breaking other modules, and each deployment becomes slower and riskier ๐Ÿ”— [**Read More: structure by components**](./sections/projectstructre/breakintcomponents.md)

-## ![โœ”] 1.2 Layer your components, keep the web layer within its boundaries +## ![โœ”] 1.2 Layer your components with 3-tiers, keep the web layer within its boundaries -**TL;DR:** Each component should contain 'layers' - a dedicated object for the web, logic, and data access code. This not only draws a clean separation of concerns but also significantly eases mocking and testing the system. Though this is a very common pattern, API developers tend to mix layers by passing the web layer objects (e.g. Express req, res) to business logic and data layers - this makes your application dependent on and accessible only by specific web frameworks +### `๐Ÿ“ #updated` + +**TL;DR:** Each component should contain 'layers' - a dedicated folder for common concerns: 'entry-point' where controller lives, 'domain' where the logic lives, and 'data-access'. The primary principle of the most popular architectures is to separate the technical concerns (e.g., HTTP, DB, etc) from the pure logic of the app so a developer can code more features without worrying about infrastructural concerns. Putting each concern in a dedicated folder, also known as the [3-Tier pattern](https://en.wikipedia.org/wiki/Multitier_architecture), is the _simplest_ way to meet this goal + +```bash +my-system +โ”œโ”€ apps (components) +โ”‚ โ”œโ”€ component-a + โ”‚ โ”œโ”€ entry-points + โ”‚ โ”‚ โ”œโ”€ api # controller comes here + โ”‚ โ”‚ โ”œโ”€ message-queue # message consumer comes here + โ”‚ โ”œโ”€ domain # features and flows: DTO, services, logic + โ”‚ โ”œโ”€ data-access # DB calls w/o ORM +``` -**Otherwise:** App that mixes web objects with other layers cannot be accessed by testing code, CRON jobs, triggers from message queues, etc +**Otherwise:** It's often seen that developer pass web objects like request/response to functions in the domain/logic layer - this violates the separation principle and makes it harder to access later the logic code by other clients like testing code, scheduled jobs, message queues, etc ๐Ÿ”— [**Read More: layer your app**](./sections/projectstructre/createlayers.md)

-## ![โœ”] 1.3 Wrap common utilities as npm packages +## ![โœ”] 1.3 Wrap common utilities as packages, consider publishing -**TL;DR:** In a large app that constitutes a large codebase, cross-cutting-concern utilities like a logger, encryption and alike, should be wrapped by your code and exposed as private npm packages. This allows sharing them among multiple codebases and projects +**TL;DR:** Place all reusable modules in a dedicated folder, e.g., "libraries", and underneath each module in its own folder, e.g., "/libraries/logger". Make the module an independent package with its own package.json file to increase the module encapsulation, and allows future publishing to a repository. In a Monorepo setup, modules can be consumed by 'npm linking' to their physical paths, using ts-paths or by publishing and installing from a package manager repository like the npm registry -**Otherwise:** You'll have to invent your deployment and the dependency wheel +```bash +my-system +โ”œโ”€ apps (components) + โ”‚ โ”œโ”€ component-a +โ”œโ”€ libraries (generic cross-component functionality) +โ”‚ โ”œโ”€ logger +โ”‚ โ”‚ โ”œโ”€ package.json +โ”‚ โ”‚ โ”œโ”€ src +โ”‚ โ”‚ โ”‚ โ”œโ”€ index.js + +``` + +**Otherwise:** Clients of a module might import and get coupled to internal functionality of a module. With a package.json at the root, one can set a package.json.main or package.json.exports to explicitly tell which files and functions are part of the public interface ๐Ÿ”— [**Read More: Structure by feature**](./sections/projectstructre/wraputilities.md)

-## ![โœ”] 1.4 Separate Express 'app' and 'server' +## ![โœ”] 1.4 Use environment aware, secure and hierarchical config -**TL;DR:** Avoid the nasty habit of defining the entire [Express](https://expressjs.com/) app in a single huge file - separate your 'Express' definition to at least two files: the API declaration (app.js) and the networking concerns (WWW). For even better structure, locate your API declaration within components +### `๐Ÿ“ #updated` -**Otherwise:** Your API will be accessible for testing via HTTP calls only (slower and much harder to generate coverage reports). It probably won't be a big pleasure to maintain hundreds of lines of code in a single file +**TL;DR:** A flawless configuration setup should ensure (a) keys can be read from file AND from environment variable (b) secrets are kept outside committed code (c) config is hierarchical for easier findability (d) typing support (e) validation for failing fast (f) Specify default for each key. There are a few packages that can help tick most of those boxes like [convict](https://www.npmjs.com/package/convict), [env-var](https://github.com/evanshortiss/env-var), [zod](https://github.com/colinhacks/zod), and others -๐Ÿ”— [**Read More: separate Express 'app' and 'server'**](./sections/projectstructre/separateexpress.md) +**Otherwise:** Consider a mandatory environment variable that wasn't provided. The app starts successfully and serve requests, some information is already persisted to DB. Then, it's realized that without this mandatory key the request can't complete, leaving the app in a dirty state + +๐Ÿ”— [**Read More: configuration best practices**](./sections/projectstructre/configguide.md)

-## ![โœ”] 1.5 Use environment aware, secure and hierarchical config +## ![โœ”] 1.5 Consider all the consequences when choosing the main framework -**TL;DR:** A perfect and flawless configuration setup should ensure (a) keys can be read from file AND from environment variable (b) secrets are kept outside committed code (c) config is hierarchical for easier findability. There are a few packages that can help tick most of those boxes like [rc](https://www.npmjs.com/package/rc), [nconf](https://www.npmjs.com/package/nconf), [config](https://www.npmjs.com/package/config), and [convict](https://www.npmjs.com/package/convict). +### `๐ŸŒŸ #new` -**Otherwise:** Failing to satisfy any of the config requirements will simply bog down the development or DevOps team. Probably both +**TL;DR:** When building apps and APIs, using a framework is mandatory. It's easy to overlook alternative frameworks or important considerations and then finally land on a sub optimal option. As of 2023/2024, we believe that these four frameworks are worth considering: [Nest.js](https://nestjs.com/), [Fastify](https://www.fastify.io/), [express](https://expressjs.com/), and [Koa](https://koajs.com/). Click read more below for a detailed pros/cons of each framework. Simplistically, we believe that Nest.js is the best match for teams who wish to go OOP and/or build large-scale apps that can't get partitioned into smaller _autonomous_ components. Fastify is our recommendation for apps with reasonably-sized components (e.g., Microservices) that are built around simple Node.js mechanics. Read our [full considerations guide here](./sections/projectstructre/choose-framework.md) -๐Ÿ”— [**Read More: configuration best practices**](./sections/projectstructre/configguide.md) +**Otherwise:** Due to the overwhelming amount of considerations, it's easy to make decisions based on partial information and compare apples with oranges. For example, it's believed that Fastify is a minimal web-server that should get compared with express only. In reality, it's a rich framework with many official plugins that cover many concerns + +๐Ÿ”— [**Read More: Choosing the right framework**](./sections/projectstructre/choose-framework.md) + +## ![โœ”] 1.6 Use TypeScript sparingly and thoughtfully + +### `๐ŸŒŸ #new` + +**TL;DR:** Coding without type safety is no longer an option, TypeScript is the most popular option for this mission. Use it to define variables and functions return types. With that, it is also a double edge sword that can greatly _encourage_ complexity with its additional ~ 50 keywords and sophisticated features. Consider using it sparingly, mostly with simple types, and utilize advanced features only when a real need arises + +**Otherwise:** [Researches](https://earlbarr.com/publications/typestudy.pdf) show that using TypeScript can help in detecting ~20% bugs earlier. Without it, also the developer experience in the IDE is intolerable. On the flip side, 80% of other bugs were not discovered using types. Consequently, typed syntax is valuable but limited. Only efficient tests can discover the whole spectrum of bugs, including type-related bugs. It might also defeat its purpose: sophisticated code features are likely to increase the code complexity, which by itself increases both the amount of bugs and the average bug fix time + +๐Ÿ”— [**Read More: TypeScript considerations**](./sections/projectstructre/typescript-considerations.md)


@@ -277,7 +334,7 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin ## ![โœ”] 2.1 Use Async-Await or promises for async error handling -**TL;DR:** Handling async errors in callback style is probably the fastest way to hell (a.k.a the pyramid of doom). The best gift you can give to your code is using a reputable promise library or async-await instead which enables a much more compact and familiar code syntax like try-catch +**TL;DR:** Handling async errors in callback style is probably the fastest way to hell (a.k.a the pyramid of doom). The best gift you can give to your code is using Promises with async-await which enables a much more compact and familiar code syntax like try-catch **Otherwise:** Node.js callback style, function(err, response), is a promising way to un-maintainable code due to the mix of error handling with casual code, excessive nesting, and awkward coding patterns @@ -285,9 +342,11 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin

-## ![โœ”] 2.2 Use only the built-in Error object +## ![โœ”] 2.2 Extend the built-in Error object + +### `๐Ÿ“ #updated` -**TL;DR:** Many throw errors as a string or as some custom type โ€“ this complicates the error handling logic and the interoperability between modules. Whether you reject a promise, throw an exception or emit an error โ€“ using only the built-in Error object (or an object that extends the built-in Error object) will increase uniformity and prevent loss of information. There is `no-throw-literal` ESLint rule that strictly checks that (although it has some [limitations](https://eslint.org/docs/rules/no-throw-literal) which can be solved when using TypeScript and setting the `@typescript-eslint/no-throw-literal` rule) +**TL;DR:** Some libraries throw errors as a string or as some custom type โ€“ this complicates the error handling logic and the interoperability between modules. Instead, create app error object/class that extends the built-in Error object and use it whenever rejecting, throwing or emitting an error. The app error should add useful imperative properties like the error name/code and isCatastrophic. By doing so, all errors have a unified structure and support better error handling. There is `no-throw-literal` ESLint rule that strictly checks that (although it has some [limitations](https://eslint.org/docs/rules/no-throw-literal) which can be solved when using TypeScript and setting the `@typescript-eslint/no-throw-literal` rule) **Otherwise:** When invoking some component, being uncertain which type of errors come in return โ€“ it makes proper error handling much harder. Even worse, using custom types to describe errors might lead to loss of critical error information like the stack trace! @@ -295,11 +354,13 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin

-## ![โœ”] 2.3 Distinguish operational vs programmer errors +## ![โœ”] 2.3 Distinguish catastrophic errors from operational errors -**TL;DR:** Operational errors (e.g. API received an invalid input) refer to known cases where the error impact is fully understood and can be handled thoughtfully. On the other hand, programmer error (e.g. trying to read an undefined variable) refers to unknown code failures that dictate to gracefully restart the application +### `๐Ÿ“ #updated` -**Otherwise:** You may always restart the application when an error appears, but why let ~5000 online users down because of a minor, predicted, operational error? The opposite is also not ideal โ€“ keeping the application up when an unknown issue (programmer error) occurred might lead to an unpredicted behavior. Differentiating the two allows acting tactfully and applying a balanced approach based on the given context +**TL;DR:** Operational errors (e.g. API received an invalid input) refer to known cases where the error impact is fully understood and can be handled thoughtfully. On the other hand, catastrophic error (also known as programmer errors) refers to unusual code failures that dictate to gracefully restart the application + +**Otherwise:** You may always restart the application when an error appears, but why let ~5000 online users down because of a minor, predicted, operational error? The opposite is also not ideal โ€“ keeping the application up when an unknown catastrophic issue (programmer error) occurred might lead to an unpredicted behavior. Differentiating the two allows acting tactfully and applying a balanced approach based on the given context ๐Ÿ”— [**Read More: operational vs programmer error**](./sections/errorhandling/operationalvsprogrammererror.md) @@ -307,7 +368,7 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin ## ![โœ”] 2.4 Handle errors centrally, not within a middleware -**TL;DR:** Error handling logic such as mail to admin and logging should be encapsulated in a dedicated and centralized object that all endpoints (e.g. Express middleware, cron jobs, unit-testing) call when an error comes in +**TL;DR:** Error handling logic such as logging, deciding whether to crash and monitoring metrics should be encapsulated in a dedicated and centralized object that all entry-points (e.g. APIs, cron jobs, scheduled jobs) call when an error comes in **Otherwise:** Not handling errors within a single place will lead to code duplication and probably to improperly handled errors @@ -315,9 +376,9 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin

-## ![โœ”] 2.5 Document API errors using Swagger or GraphQL +## ![โœ”] 2.5 Document API errors using OpenAPI or GraphQL -**TL;DR:** Let your API callers know which errors might come in return so they can handle these thoughtfully without crashing. For RESTful APIs, this is usually done with documentation frameworks like Swagger. If you're using GraphQL, you can utilize your schema and comments as well. +**TL;DR:** Let your API callers know which errors might come in return so they can handle these thoughtfully without crashing. For RESTful APIs, this is usually done with documentation frameworks like OpenAPI. If you're using GraphQL, you can utilize your schema and comments as well **Otherwise:** An API client might decide to crash and restart only because it received back an error it couldnโ€™t understand. Note: the caller of your API might be you (very typical in a microservice environment) @@ -327,7 +388,7 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin ## ![โœ”] 2.6 Exit the process gracefully when a stranger comes to town -**TL;DR:** When an unknown error occurs (a developer error, see best practice 2.3) - there is uncertainty about the application healthiness. Common practice suggests restarting the process carefully using a process management tool like [Forever](https://www.npmjs.com/package/forever) or [PM2](http://pm2.keymetrics.io/) +**TL;DR:** When an unknown error occurs (catastrophic error, see best practice 2.3) - there is uncertainty about the application healthiness. In this case, there is no escape from making the error observable, shutting off connections and exiting the process. Any reputable runtime framework like Dockerized services or cloud serverless solutions will take care to restart **Otherwise:** When an unfamiliar exception occurs, some object might be in a faulty state (e.g. an event emitter which is used globally and not firing events anymore due to some internal failure) and all future requests might fail or behave crazily @@ -335,9 +396,11 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin

-## ![โœ”] 2.7 Use a mature logger to increase error visibility +## ![โœ”] 2.7 Use a mature logger to increase errors visibility + +### `๐Ÿ“ #updated` -**TL;DR:** A set of mature logging tools like [Pino](https://github.com/pinojs/pino) or [Log4js](https://www.npmjs.com/package/log4js), will speed-up error discovery and understanding. So forget about console.log +**TL;DR:** A robust logging tools like [Pino](https://github.com/pinojs/pino) or [Winston](https://github.com/winstonjs/winston) increases the errors visibility using features like log-levels, pretty print coloring and more. Console.log lacks these imperative features and should be avoided. The best in class logger allows attaching custom useful properties to log entries with minimized serialization performance penalty. Developers should write logs to `stdout` and let the infrastructure pipe the stream to the appropriate log aggregator **Otherwise:** Skimming through console.logs or manually through messy text file without querying tools or a decent log viewer might keep you busy at work until late @@ -347,7 +410,9 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin ## ![โœ”] 2.8 Test error flows using your favorite test framework -**TL;DR:** Whether professional automated QA or plain manual developer testing โ€“ Ensure that your code not only satisfies positive scenarios but also handles and returns the right errors. Testing frameworks like Mocha & Chai can handle this easily (see code examples within the "Gist popup") +### `๐Ÿ“ #updated` + +**TL;DR:** Whether professional automated QA or plain manual developer testing โ€“ Ensure that your code not only satisfies positive scenarios but also handles and returns the right errors. On top of this, simulate deeper error flows like uncaught exceptions and ensure that the error handler treat these properly (see code examples within the "read more" section) **Otherwise:** Without testing, whether automatically or manually, you canโ€™t rely on your code to return the right errors. Without meaningful errors โ€“ thereโ€™s no error handling @@ -367,6 +432,8 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin ## ![โœ”] 2.10 Catch unhandled promise rejections +### `๐Ÿ“ #updated` + **TL;DR:** Any exception thrown within a promise will get swallowed and discarded unless a developer didnโ€™t forget to explicitly handle it. Even if your code is subscribed to `process.uncaughtException`! Overcome this by registering to the event `process.unhandledRejection` **Otherwise:** Your errors will get swallowed and leave no trace. Nothing to worry about @@ -377,7 +444,7 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin ## ![โœ”] 2.11 Fail fast, validate arguments using a dedicated library -**TL;DR:** Assert API input to avoid nasty bugs that are much harder to track later. The validation code is usually tedious unless you are using a very cool helper library like [ajv](https://www.npmjs.com/package/ajv) and [Joi](https://www.npmjs.com/package/joi) +**TL;DR:** Assert API input to avoid nasty bugs that are much harder to track later. The validation code is usually tedious unless you are using a modern validation library like [ajv](https://www.npmjs.com/package/ajv), [zod](https://github.com/colinhacks/zod), or [typebox](https://github.com/sinclairzx81/typebox) **Otherwise:** Consider this โ€“ your function expects a numeric argument โ€œDiscountโ€ which the caller forgets to pass, later on, your code checks if Discount!=0 (amount of allowed discount is greater than zero), then it will allow the user to enjoy a discount. OMG, what a nasty bug. Can you see it? @@ -387,6 +454,8 @@ Read in a different language: [![CN](./assets/flags/CN.png)**CN**](./README.chin ## ![โœ”] 2.12 Always await promises before returning to avoid a partial stacktrace +### `๐ŸŒŸ #new` + **TL;DR:** Always do `return await` when returning a promise to benefit full error stacktrace. If a function returns a promise, that function must be declared as `async` function and explicitly `await` the promise before returning it @@ -397,15 +466,25 @@ especially if the cause of the abnormal behavior is inside of the missing functi ๐Ÿ”— [**Read More: returning promises**](./sections/errorhandling/returningpromises.md) +

+ +## ![โœ”] 2.13 Subscribe to event emitters and streams 'error' event + +### `๐ŸŒŸ #new` + +**TL;DR:** Unlike typical functions, a try-catch clause won't get errors that originate from Event Emitters and anything inherited from it (e.g., streams). Instead of try-catch, subscribe to an event emitter's 'error' event so your code can handle the error in context. When dealing with [EventTargets](https://nodejs.org/api/events.html#eventtarget-and-event-api) (the web standard version of Event Emitters) there are no 'error' event and all errors end in the process.on('error) global event - in this case, at least ensure that the process crash or not based on the desired context. Also, mind that error originating from _asynchronous_ event handlers are not get caught unless the event emitter is initialized with {captureRejections: true} + +**Otherwise:** Event emitters are commonly used for global and key application functionality such as DB or message queue connection. When this kind of crucial objects throw an error, at best the process will crash due to unhandled exception. Even worst, it will stay alive as a zombie while a key functionality is turned off +


โฌ† Return to top

-# `3. Code Style Practices` +# `3. Code Patterns And Style Practices` ## ![โœ”] 3.1 Use ESLint -**TL;DR:** [ESLint](https://eslint.org) is the de-facto standard for checking possible code errors and fixing code style, not only to identify nitty-gritty spacing issues but also to detect serious code anti-patterns like developers throwing errors without classification. Though ESLint can automatically fix code styles, other tools like [prettier](https://www.npmjs.com/package/prettier) and [beautify](https://www.npmjs.com/package/js-beautify) are more powerful in formatting the fix and work in conjunction with ESLint +**TL;DR:** [ESLint](https://eslint.org) is the de-facto standard for checking possible code errors and fixing code style, not only to identify nitty-gritty spacing issues but also to detect serious code anti-patterns like developers throwing errors without classification. Though ESLint can automatically fix code styles, other tools like [prettier](https://www.npmjs.com/package/prettier) are more powerful in formatting the fix and work in conjunction with ESLint **Otherwise:** Developers will focus on tedious spacing and line-width concerns and time might be wasted overthinking the project's code style @@ -413,9 +492,11 @@ especially if the cause of the abnormal behavior is inside of the missing functi

-## ![โœ”] 3.2 Node.js specific plugins +## ![โœ”] 3.2 Use Node.js eslint extension plugins -**TL;DR:** On top of ESLint standard rules that cover vanilla JavaScript, add Node.js specific plugins like [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node), [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) and [eslint-plugin-node-security](https://www.npmjs.com/package/eslint-plugin-security) +### `๐Ÿ“ #updated` + +**TL;DR:** On top of ESLint standard rules that cover vanilla JavaScript, add Node.js specific plugins like [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node), [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha) and [eslint-plugin-node-security](https://www.npmjs.com/package/eslint-plugin-security), [eslint-plugin-require](https://www.npmjs.com/package/eslint-plugin-require), [/eslint-plugin-jest](https://www.npmjs.com/package/eslint-plugin-jest) and other useful rules **Otherwise:** Many faulty Node.js code patterns might escape under the radar. For example, developers might require(variableAsPath) files with a variable given as a path which allows attackers to execute any JS script. Node.js linters can detect such patterns and complain early @@ -508,7 +589,7 @@ const count = 2 // it tries to run 2(), but 2 is not a function ```javascript // for global variables names we use the const/let keyword and UPPER_SNAKE_CASE -let MUTABLE_GLOBAL = "mutable value" +let MUTABLE_GLOBAL = "mutable value"; const GLOBAL_CONSTANT = "immutable value"; const CONFIG = { key: "value", @@ -557,22 +638,29 @@ function doSomething() {

-## ![โœ”] 3.9 Require modules by folders, as opposed to the files directly +## ![โœ”] 3.9 Set an explicit entry point to a module/folder + +### `๐Ÿ“ #updated` -**TL;DR:** When developing a module/library in a folder, place an index.js file that exposes the module's internals so every consumer will pass through it. This serves as an 'interface' to your module and eases future changes without breaking the contract +**TL;DR:** When developing a module/library, set an explicit root file that exports the public and interesting code. Discourage the client code from importing deep files and becoming familiar with the internal structure. With commonjs (require), this can be done with an index.js file at the folder's root or the package.json.main field. With ESM (import), if a package.json exists on the root, the field "exports" allow specifying the module's root file. If no package.json exists, you may put an index.js file on the root which re-exports all the public functionality -**Otherwise:** Changing the internal structure of files or the signature may break the interface with clients +**Otherwise:** Having an explicit root file acts like a public 'interface' that encapsulates the internal, directs the caller to the public code and facilitates future changes without breaking the contract -### 3.9 Code example +### 3.9 Code example - avoid coupling the client to the module structure ```javascript -// Do -module.exports.SMSProvider = require("./SMSProvider"); -module.exports.SMSNumberResolver = require("./SMSNumberResolver"); +// Avoid: client has deep familiarity with the internals -// Avoid -module.exports.SMSProvider = require("./SMSProvider/SMSProvider.js"); -module.exports.SMSNumberResolver = require("./SMSNumberResolver/SMSNumberResolver.js"); +// Client code +const SMSWithMedia = require("./SMSProvider/providers/media/media-provider.js"); + +// Better: explicitly export the public functions + +//index.js, module code +module.exports.SMSWithMedia = require("./SMSProvider/providers/media/media-provider.js"); + +// Client code +const { SMSWithMedia } = require("./SMSProvider"); ```

@@ -606,7 +694,7 @@ All statements above will return false if used with `===` ## ![โœ”] 3.11 Use Async Await, avoid callbacks -**TL;DR:** Node 8 LTS now has full support for Async-await. This is a new way of dealing with asynchronous code which supersedes callbacks and promises. Async-await is non-blocking, and it makes asynchronous code look synchronous. The best gift you can give to your code is using async-await which provides a much more compact and familiar code syntax like try-catch +**TL;DR:** Async-await is the simplest way to express an asynchronous flow as it makes asynchronous code look synchronous. Async-await will also result in much more compact code and support for try-catch. This technique now supersedes callbacks and promises in _most_ cases. Using it in your code is probably the best gift one can give to the code reader **Otherwise:** Handling async errors in callback style are probably the fastest way to hell - this style forces to check errors all over, deal with awkward code nesting, and makes it difficult to reason about the code flow @@ -622,12 +710,28 @@ All statements above will return false if used with `===` ๐Ÿ”— [**Read more: Itโ€™s Time to Embrace Arrow Functions**](https://medium.com/javascript-scene/familiarity-bias-is-holding-you-back-its-time-to-embrace-arrow-functions-3d37e1a9bb75) +

+ +## ![โœ”] 3.13 Avoid effects outside of functions + +### `๐ŸŒŸ #new` + +**TL;DR:** Avoid putting code with effects like network or DB calls outside of functions. Such a code will be executed immediately when another file requires the file. This 'floating' code might get executed when the underlying system is not ready yet. It also comes with a performance penalty even when this module's functions will finally not be used in runtime. Last, mocking these DB/network calls for testing is harder outside of functions. Instead, put this code inside functions that should get called explicitly. If some DB/network code must get executed right when the module loads, consider using the factory or revealing module patterns + +**Otherwise:** A typical web framework sets error handler, environment variables and monitoring. When DB/network calls are made before the web framework is initialized, they won't be monitored or fail due to a lack of configuration data +


โฌ† Return to top

# `4. Testing And Overall Quality Practices` +\_We have dedicated guides for testing, see below. The best practices list here is a brief summary of these guides + +a. [JavaScript testing best practices](https://github.com/goldbergyoni/javascript-testing-best-practices) +b. [Node.js testing - beyond the basics](https://github.com/testjavascript/nodejs-integration-tests-best-practices) +\_ + ## ![โœ”] 4.1 At the very least, write API (component) testing **TL;DR:** Most projects just don't have any automated testing due to short timetables or often the 'testing project' ran out of control and was abandoned. For that reason, prioritize and start with API testing which is the easiest way to write and provides more coverage than unit testing (you may even craft API tests without code using tools like [Postman](https://www.getpostman.com/)). Afterwards, should you have more resources and time, continue with advanced test types like unit testing, DB testing, performance testing, etc @@ -638,6 +742,8 @@ All statements above will return false if used with `===` ## ![โœ”] 4.2 Include 3 parts in each test name +### `๐ŸŒŸ #new` + **TL;DR:** Make the test speak at the requirements level so it's self-explanatory also to QA engineers and developers who are not familiar with the code internals. State in the test name what is being tested (unit under test), under what circumstances, and what is the expected result **Otherwise:** A deployment just failed, a test named โ€œAdd productโ€ failed. Does this tell you what exactly is malfunctioning? @@ -648,6 +754,8 @@ All statements above will return false if used with `===` ## ![โœ”] 4.3 Structure tests by the AAA pattern +### `๐ŸŒŸ #new` + **TL;DR:** Structure your tests with 3 well-separated sections: Arrange, Act & Assert (AAA). The first part includes the test setup, then the execution of the unit under test, and finally the assertion phase. Following this structure guarantees that the reader spends no brain CPU on understanding the test plan **Otherwise:** Not only you spend long daily hours on understanding the main code, but now also what should have been the simple part of the day (testing) stretches your brain @@ -656,11 +764,13 @@ All statements above will return false if used with `===`

-## ![โœ”] 4.4 Detect code issues with a linter +## ![โœ”] 4.4 Ensure Node version is unified + +### `๐ŸŒŸ #new` -**TL;DR:** Use a code linter to check the basic quality and detect anti-patterns early. Run it before any test and add it as a pre-commit git-hook to minimize the time needed to review and correct any issue. Also check [Section 3](#3-code-style-practices) on Code Style Practices +**TL;DR:** Use tools that encourage or enforce the same Node.js version across different environments and developers. Tools like [nvm](https://github.com/nvm-sh/nvm), and [Volta](https://volta.sh/) allow specifying the project's version in a file so each team member can run a single command to conform with the project's version. Optionally, this definition can be replicated to CI and the production runtime (e.g., copy the specified value to .Dockerfile build and to the CI declaration file) -**Otherwise:** You may let pass some anti-pattern and possible vulnerable code to your production environment. +**Otherwise:** A developer might face or miss an error because she uses a different Node.js version than her teammates. Even worse - the production runtime might be different than the environment where tests were executed

@@ -674,15 +784,7 @@ All statements above will return false if used with `===`

-## ![โœ”] 4.6 Constantly inspect for vulnerable dependencies - -**TL;DR:** Even the most reputable dependencies such as Express have known vulnerabilities. This can get easily tamed using community and commercial tools such as ๐Ÿ”— [npm audit](https://docs.npmjs.com/cli/audit) and ๐Ÿ”— [snyk.io](https://snyk.io) that can be invoked from your CI on every build - -**Otherwise:** Keeping your code clean from vulnerabilities without dedicated tools will require to constantly follow online publications about new threats. Quite tedious - -

- -## ![โœ”] 4.7 Tag your tests +## ![โœ”] 4.6 Tag your tests **TL;DR:** Different tests must run on different scenarios: quick smoke, IO-less, tests should run when a developer saves or commits a file, full end-to-end tests usually run when a new pull request is submitted, etc. This can be achieved by tagging tests with keywords like #cold #api #sanity so you can grep with your testing harness and invoke the desired subset. For example, this is how you would invoke only the sanity test group with [Mocha](https://mochajs.org/): mocha --grep 'sanity' @@ -690,7 +792,7 @@ All statements above will return false if used with `===`

-## ![โœ”] 4.8 Check your test coverage, it helps to identify wrong test patterns +## ![โœ”] 4.7 Check your test coverage, it helps to identify wrong test patterns **TL;DR:** Code coverage tools like [Istanbul](https://github.com/istanbuljs/istanbuljs)/[NYC](https://github.com/istanbuljs/nyc) are great for 3 reasons: it comes for free (no effort is required to benefit this reports), it helps to identify a decrease in testing coverage, and last but not least it highlights testing mismatches: by looking at colored code coverage reports you may notice, for example, code areas that are never tested like catch clauses (meaning that tests only invoke the happy paths and not how the app behaves on errors). Set it to fail builds if the coverage falls under a certain threshold @@ -698,15 +800,7 @@ All statements above will return false if used with `===`

-## ![โœ”] 4.9 Inspect for outdated packages - -**TL;DR:** Use your preferred tool (e.g. `npm outdated` or [npm-check-updates](https://www.npmjs.com/package/npm-check-updates)) to detect installed outdated packages, inject this check into your CI pipeline and even make a build fail in a severe scenario. For example, a severe scenario might be when an installed package is 5 patch commits behind (e.g. local version is 1.3.1 and repository version is 1.3.8) or it is tagged as deprecated by its author - kill the build and prevent deploying this version - -**Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky - -

- -## ![โœ”] 4.10 Use production-like environment for e2e testing +## ![โœ”] 4.8 Use production-like environment for e2e testing **TL;DR:** End to end (e2e) testing which includes live data used to be the weakest link of the CI process as it depends on multiple heavy services like DB. Use an environment which is as close to your real production environment as possible like a-continue (Missed -continue here, needs content. Judging by the **Otherwise** clause, this should mention docker-compose) @@ -714,7 +808,7 @@ All statements above will return false if used with `===`

-## ![โœ”] 4.11 Refactor regularly using static analysis tools +## ![โœ”] 4.9 Refactor regularly using static analysis tools **TL;DR:** Using static analysis tools helps by giving objective ways to improve code quality and keeps your code maintainable. You can add static analysis tools to your CI build to fail when it finds code smells. Its main selling points over plain linting are the ability to inspect quality in the context of multiple files (e.g. detect duplications), perform advanced analysis (e.g. code complexity), and follow the history and progress of code issues. Two examples of tools you can use are [Sonarqube](https://www.sonarqube.org/) (2,600+ [stars](https://github.com/SonarSource/sonarqube)) and [Code Climate](https://codeclimate.com/) (1,500+ [stars](https://github.com/codeclimate/codeclimate)). @@ -724,15 +818,17 @@ All statements above will return false if used with `===`

-## ![โœ”] 4.12 Carefully choose your CI platform (Jenkins vs CircleCI vs Travis vs Rest of the world) +## ![โœ”] 4.10 Mock responses of external HTTP services -**TL;DR:** Your continuous integration platform (CICD) will host all the quality tools (e.g. test, lint) so it should come with a vibrant ecosystem of plugins. [Jenkins](https://jenkins.io/) used to be the default for many projects as it has the biggest community along with a very powerful platform at the price of a complex setup that demands a steep learning curve. Nowadays, it has become much easier to set up a CI solution using SaaS tools like [CircleCI](https://circleci.com) and others. These tools allow crafting a flexible CI pipeline without the burden of managing the whole infrastructure. Eventually, it's a trade-off between robustness and speed - choose your side carefully +### `๐ŸŒŸ #new` -**Otherwise:** Choosing some niche vendor might get you blocked once you need some advanced customization. On the other hand, going with Jenkins might burn precious time on infrastructure setup +**TL;DR:** Use network mocking tools to simulate responses of external collaborators' services that are approached over the network (e.g., REST, Graph). This is imperative not only to isolate the component under test but mostly to simulate non-happy path flows. Tools like [nock](https://github.com/nock/nock) (in-process) or [Mock-Server](https://www.mock-server.com/) allow defining a specific response of external service in a single line of code. Remember to simulate also errors, delays, timeouts, and any other event that is likely to happen in production -๐Ÿ”— [**Read More: Choosing CI platform**](./sections/testingandquality/citools.md) +**Otherwise:** Allowing your component to reach real external services instances will likely result in naive tests that mostly cover happy paths. The tests might also be flaky and slow -## ![โœ”] 4.13 Test your middlewares in isolation +๐Ÿ”— [**Read More: Mock external services**](./sections/testingandquality/mock-external-services.md) + +## ![โœ”] 4.11 Test your middlewares in isolation **TL;DR:** When a middleware holds some immense logic that spans many requests, it is worth testing it in isolation without waking up the entire web framework. This can be easily achieved by stubbing and spying on the {req, res, next} objects @@ -740,6 +836,26 @@ All statements above will return false if used with `===` ๐Ÿ”— [**Read More: Test middlewares in isolation**](./sections/testingandquality/test-middlewares.md) +## ![โœ”] 4.12 Specify a port in production, randomize in testing + +### `๐ŸŒŸ #new` + +**TL;DR:** When testing against the API, it's common and desirable to initialize the web server inside the tests. Let the server randomize the web server port in testing to prevent collisions. If you're using Node.js http server (used by most frameworks), doing so demands nothing but passing a port number zero - this will randomize an available port + +**Otherwise:** Specifying a fixed port will prevent two testing processes from running at the same time. Most of the modern test runners run with multiple processes by default + +๐Ÿ”— [**Read More: Randomize a port for testing**](./sections/testingandquality/randomize-port.md) + +## ![โœ”] 4.13 Test the five possible outcomes + +### `๐ŸŒŸ #new` + +**TL;DR:** When testing a flow, ensure to cover five potential categories. Any time some action is triggered (e.g., API call), a reaction occurs, a meaningful **outcome** is produced and calls for testing. There are five possible outcome types for every flow: a response, a visible state change (e.g., DB), an outgoing API call, a new message in a queue, and an observability call (e.g., logging, metric). See a [checklist here](https://testjavascript.com/wp-content/uploads/2021/10/the-backend-checklist.pdf). Each type of outcome comes with unique challenges and techniques to mitigate those challenges - we have a dedicated guide about this topic: [Node.js testing - beyond the basics](https://github.com/testjavascript/nodejs-integration-tests-best-practices) + +**Otherwise:** Consider a case when testing the addition of a new product to the system. It's common to see tests that assert on a valid response only. What if the product was failed to persist regardless of the positive response? what if when adding a new product demands calling some external service, or putting a message in the queue - shouldn't the test assert these outcomes as well? It's easy to overlook various paths, this is where a [checklist comes handy](https://testjavascript.com/wp-content/uploads/2021/10/the-backend-checklist.pdf) + +๐Ÿ”— [**Read More: Test five outcomes**](./sections/testingandquality/test-five-outcomes.md) +


โฌ† Return to top

@@ -748,7 +864,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.1. Monitoring -**TL;DR:** Monitoring is a game of finding out issues before customers do โ€“ obviously this should be assigned unprecedented importance. The market is overwhelmed with offers thus consider starting with defining the basic metrics you must follow (my suggestions inside), then go over additional fancy features and choose the solution that ticks all boxes. Click โ€˜The Gistโ€™ below for an overview of the solutions +**TL;DR:** Monitoring is a game of finding out issues before customers do โ€“ obviously this should be assigned unprecedented importance. The market is overwhelmed with offers thus consider starting with defining the basic metrics you must follow (my suggestions inside), then go over additional fancy features and choose the solution that ticks all boxes. In any case, the 4 layers of observability must be covered: uptime, metrics with focus on user-facing symptoms and Node.js technical metrics like event loop lag, distributed flows measurement with Open Telemetry and logging. Click โ€˜Read Moreโ€™ below for an overview of the solutions **Otherwise:** Failure === disappointed customers. Simple @@ -756,7 +872,7 @@ All statements above will return false if used with `===`

-## ![โœ”] 5.2. Increase transparency using smart logging +## ![โœ”] 5.2. Increase the observability using smart logging **TL;DR:** Logs can be a dumb warehouse of debug statements or the enabler of a beautiful dashboard that tells the story of your app. Plan your logging platform from day 1: how logs are collected, stored and analyzed to ensure that the desired information (e.g. error rate, following an entire transaction through services and servers, etc) can really be extracted @@ -768,7 +884,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.3. Delegate anything possible (e.g. gzip, SSL) to a reverse proxy -**TL;DR:** Node is awfully bad at doing CPU intensive tasks like gzipping, SSL termination, etc. You should use โ€˜realโ€™ middleware services like nginx, HAproxy or cloud vendor services instead +**TL;DR:** Node is quite bad at doing CPU intensive tasks like gzipping, SSL termination, etc. You should use specialized infrastructure like nginx, HAproxy or cloud vendor services instead **Otherwise:** Your poor single thread will stay busy doing infrastructural tasks instead of dealing with your application core and performance will degrade accordingly @@ -778,7 +894,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.4. Lock dependencies -**TL;DR:** Your code must be identical across all environments, but amazingly npm lets dependencies drift across environments by default โ€“ when you install packages at various environments it tries to fetch packagesโ€™ latest patch version. Overcome this by using npm config files, .npmrc, that tell each environment to save the exact (not the latest) version of each package. Alternatively, for finer grained control use `npm shrinkwrap`. \*Update: as of NPM5, dependencies are locked by default. The new package manager in town, Yarn, also got us covered by default +**TL;DR:** Your code must be identical across all environments, but without a special lockfile npm lets dependencies drift across environments. Ensure to commit your package-lock.json so all the environments will be identical **Otherwise:** QA will thoroughly test the code and approve a version that will behave differently in production. Even worse, different servers in the same production cluster might run different code @@ -788,7 +904,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.5. Guard process uptime using the right tool -**TL;DR:** The process must go on and get restarted upon failures. For simple scenarios, process management tools like PM2 might be enough but in today's โ€˜dockerizedโ€™ world, cluster management tools should be considered as well +**TL;DR:** The process must go on and get restarted upon failures. Modern runtime platforms like Docker-ized platforms (e.g. Kubernetes), and Serverless take care for this automatically. When the app is hosted on a bare metal server, one must take care for a process management tools like [systemd](https://systemd.io/). Avoid including a custom process management tool in a modern platform that monitors an app instance (e.g., Kubernetes) - doing so will hide failures from the infrastructure. When the underlying infrastructure is not aware of errors, it can't perform useful mitigation steps like re-placing the instance in a different location **Otherwise:** Running dozens of instances without a clear strategy and too many tools together (cluster management, docker, PM2) might lead to DevOps chaos @@ -798,7 +914,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.6. Utilize all CPU cores -**TL;DR:** At its basic form, a Node app runs on a single CPU core while all others are left idling. Itโ€™s your duty to replicate the Node process and utilize all CPUs โ€“ For small-medium apps you may use Node Cluster or PM2. For a larger app consider replicating the process using some Docker cluster (e.g. K8S, ECS) or deployment scripts that are based on Linux init system (e.g. systemd) +**TL;DR:** At its basic form, a Node app runs on a single CPU core while all others are left idling. Itโ€™s your duty to replicate the Node process and utilize all CPUs. Most of the modern run-times platform (e.g., Kubernetes) allow replicating instances of the app but they won't verify that all cores are utilized - this is your duty. If the app is hosted on a bare server, it's also your duty to use some process replication solution (e.g. systemd) **Otherwise:** Your app will likely utilize only 25% of its available resources(!) or even less. Note that a typical server has 4 CPU cores or more, naive deployment of Node.js utilizes only 1 (even using PaaS services like AWS beanstalk!) @@ -816,9 +932,11 @@ All statements above will return false if used with `===`

-## ![โœ”] 5.8. Discover errors and downtime using APM products +## ![โœ”] 5.8. Discover the unknowns using APM products + +### `๐Ÿ“ #updated` -**TL;DR:** Application monitoring and performance products (a.k.a. APM) proactively gauge codebase and API so they can auto-magically go beyond traditional monitoring and measure the overall user-experience across services and tiers. For example, some APM products can highlight a transaction that loads too slow on the end-user's side while suggesting the root cause +**TL;DR:** Consider adding another safety layer to the production stack - APM. While the majority of symptoms and causes can be detected using traditional monitoring techniques, in a distributed system there is more than meets the eye. Application monitoring and performance products (a.k.a. APM) can auto-magically go beyond traditional monitoring and provide additional layer of discovery and developer-experience. For example, some APM products can highlight a transaction that loads too slow on the **end-user's side** while suggesting the root cause. APMs also provide more context for developers who try to troubleshoot a log error by showing what was the server busy with when the error occurred. To name a few example **Otherwise:** You might spend great effort on measuring API performance and downtimes, probably youโ€™ll never be aware which is your slowest code parts under real-world scenario and how these affect the UX @@ -828,7 +946,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.9. Make your code production-ready -**TL;DR:** Code with the end in mind, plan for production from day 1. This sounds a bit vague so Iโ€™ve compiled a few development tips that are closely related to production maintenance (click Gist below) +**TL;DR:** Code with the end in mind, plan for production from day 1. This sounds a bit vague so Iโ€™ve compiled a few development tips that are closely related to production maintenance (click 'Read More') **Otherwise:** A world champion IT/DevOps guy wonโ€™t save a system that is badly written @@ -848,7 +966,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.11. Get your frontend assets out of Node -**TL;DR:** Serve frontend content using dedicated middleware (nginx, S3, CDN) because Node performance really gets hurt when dealing with many static files due to its single-threaded model +**TL;DR:** Serve frontend content using a specialized infrastructure (nginx, S3, CDN) because Node performance gets hurt when dealing with many static files due to its single-threaded model. One exception to this guideline is when doing server-side rendering **Otherwise:** Your single Node thread will be busy streaming hundreds of html/images/angular/react files instead of allocating all its resources for the task it was born for โ€“ serving dynamic content @@ -856,9 +974,9 @@ All statements above will return false if used with `===`

-## ![โœ”] 5.12. Be stateless, kill your servers almost every day +## ![โœ”] 5.12. Strive to be stateless -**TL;DR:** Store any type of data (e.g. user sessions, cache, uploaded files) within external data stores. Consider โ€˜killingโ€™ your servers periodically or use โ€˜serverlessโ€™ platform (e.g. AWS Lambda) that explicitly enforces a stateless behavior +**TL;DR:** Store any type of _data_ (e.g. user sessions, cache, uploaded files) within external data stores. When the app holds data in-process this adds additional layer of maintenance complexity like routing users to the same instance and higher cost of restarting a process. To enforce and encourage a stateless approach, most modern runtime platforms allows 'reapp-ing' instances periodically **Otherwise:** Failure at a given server will result in application downtime instead of just killing a faulty machine. Moreover, scaling-out elasticity will get more challenging due to the reliance on a specific server @@ -878,9 +996,7 @@ All statements above will return false if used with `===` ## ![โœ”] 5.14. Assign a transaction id to each log statement -Also known as correlation id / transit id / tracing id / request id / request context / etc. - -**TL;DR:** Assign the same identifier, transaction-id: {some value}, to each log entry within a single request. Then when inspecting errors in logs, easily conclude what happened before and after. Until version 14 of Node, this was not easy to achieve due to Node's async nature, but since AsyncLocalStorage came to town, this became possible and easy than ever. see code examples inside +**TL;DR:** Assign the same identifier, transaction-id: uuid(), to each log entry within a single request (also known as correlation-id/tracing-id/request-context). Then when inspecting errors in logs, easily conclude what happened before and after. Node has a built-in mechanism, [AsyncLocalStorage](https://nodejs.org/api/async_context.html), for keeping the same context across asynchronous calls. see code examples inside **Otherwise:** Looking at a production error log without the context โ€“ what happened before โ€“ makes it much harder and slower to reason about the issue @@ -890,9 +1006,9 @@ Also known as correlation id / transit id / tracing id / request id / request co ## ![โœ”] 5.15. Set `NODE_ENV=production` -**TL;DR:** Set the environment variable `NODE_ENV` to โ€˜productionโ€™ or โ€˜developmentโ€™ to flag whether production optimizations should get activated โ€“ many npm packages determine the current environment and optimize their code for production +**TL;DR:** Set the environment variable `NODE_ENV` to โ€˜productionโ€™ or โ€˜developmentโ€™ to flag whether production optimizations should get activated โ€“ some npm packages determine the current environment and optimize their code for production -**Otherwise:** Omitting this simple property might greatly degrade performance. For example, when using Express for server-side rendering omitting `NODE_ENV` makes it slower by a factor of three! +**Otherwise:** Omitting this simple property might greatly degrade performance when dealing with some specific libraries like Express server-side rendering ๐Ÿ”— [**Read More: Set NODE_ENV=production**](./sections/production/setnodeenv.md) @@ -916,11 +1032,13 @@ Also known as correlation id / transit id / tracing id / request id / request co

-## ![โœ”] 5.18. Don't route logs within the app +## ![โœ”] 5.18. Log to stdout, avoid specifying log destination within the app + +### `๐Ÿ“ #updated` **TL;DR:** Log destinations should not be hard-coded by developers within the application code, but instead should be defined by the execution environment the application runs in. Developers should write logs to `stdout` using a logger utility and then let the execution environment (container, server, etc.) pipe the `stdout` stream to the appropriate destination (i.e. Splunk, Graylog, ElasticSearch, etc.). -**Otherwise:** Application handling log routing === hard to scale, loss of logs, poor separation of concerns +**Otherwise:** If developers set the log routing, less flexibility is left for the ops professional who wishes to customize it. Beyond this, if the app tries to log directly to a remote location (e.g., Elastic Search), in case of panic or crash - further logs that might explain the problem won't arrive ๐Ÿ”— [**Read More: Log Routing**](./sections/production/logrouting.md) @@ -928,7 +1046,7 @@ Also known as correlation id / transit id / tracing id / request id / request co ## ![โœ”] 5.19. Install your packages with `npm ci` -**TL;DR:** You have to be sure that production code uses the exact version of the packages you have tested it with. Run `npm ci` to strictly do a clean install of your dependencies matching package.json and package-lock.json. Using this command is recommended in automated environments such as continuous integration pipelines. +**TL;DR:** Run `npm ci` to strictly do a clean install of your dependencies matching package.json and package-lock.json. Obviously production code must use the exact version of the packages that were used for testing. While package-lock.json file sets strict version for dependencies, in case of mismatch with the file package.json, the command 'npm install' will treat package.json as the source of truth. On the other hand, the command 'npm ci' will exit with error in case of mismatch between these files **Otherwise:** QA will thoroughly test the code and approve a version that will behave differently in production. Even worse, different servers in the same production cluster might run different code. @@ -1236,6 +1354,39 @@ Also known as correlation id / transit id / tracing id / request id / request co **Otherwise:** Your project's API keys, passwords or other secrets are open to be abused by anyone who comes across them, which may result in financial loss, impersonation, and other risks. ๐Ÿ”— [**Read More: Avoid publishing secrets**](./sections/security/avoid_publishing_secrets.md) + +

+ +## ![โœ”] 6.26 Inspect for outdated packages + +**TL;DR:** Use your preferred tool (e.g. `npm outdated` or [npm-check-updates](https://www.npmjs.com/package/npm-check-updates)) to detect installed outdated packages, inject this check into your CI pipeline and even make a build fail in a severe scenario. For example, a severe scenario might be when an installed package is 5 patch commits behind (e.g. local version is 1.3.1 and repository version is 1.3.8) or it is tagged as deprecated by its author - kill the build and prevent deploying this version + +**Otherwise:** Your production will run packages that have been explicitly tagged by their author as risky + +

+ +## ![โœ”] 6.27. Import built-in modules using the 'node:' protocol + +### `๐ŸŒŸ #new` + + + +**TL;DR:** Import or require built-in Node.js modules using the 'node protocol' syntax: + +```javascript +import { functionName } from "node:module"; // note that 'node:' prefix +``` + +For example: + +```javascript +import { createServer } from "node:http"; +``` + +This style ensures that there is no ambiguity with global npm packages and makes it clear for the reader that the code refers to a well-trusted official module. This style can be enforced with the eslint rule ['prefer-node-protocol'](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-node-protocol.md) + +**Otherwise:** Using the import syntax without 'node:' prefix opens the door for [typosquatting attacks](https://en.wikipedia.org/wiki/Typosquatting) where one could mistakenly mistype a module name (e.g., 'event' instead of 'events) and get a malicious package that was built only to trick users into installing them +


โฌ† Return to top

@@ -1307,9 +1458,11 @@ CMD [ "node", "dist/app.js" ] ## ![โœ”] 8.2. Bootstrap using `node` command, avoid `npm start` -**TL;DR:** use `CMD ['node','server.js']` to start your app, avoid using npm scripts which don't pass OS signals to the code. This prevents problems with child-processes, signal handling, graceful shutdown and having zombie processes. +**TL;DR:** Use `CMD ['node','server.js']` to start your app, avoid using npm scripts which don't pass OS signals to the code. This prevents problems with child-processes, signal handling, graceful shutdown and having zombie processes -**Otherwise:** When no signals are passed, your code will never be notified about shutdowns. Without this, it will lose its chance to close properly possibly losing current requests and/or data. +Update: [Starting from npm 7, npm claim](https://docs.npmjs.com/cli/v7/using-npm/changelog#706-2020-10-27) to pass signals. We follow and will update accordingly + +**Otherwise:** When no signals are passed, your code will never be notified about shutdowns. Without this, it will lose its chance to close properly possibly losing current requests and/or data [**Read More: Bootstrap container using node command, avoid npm start**](./sections/docker/bootstrap-using-node.md) @@ -1347,7 +1500,7 @@ CMD [ "node", "dist/app.js" ] ## ![โœ”] 8.6. Shutdown smartly and gracefully -**TL;DR:** Handle the process SIGTERM event and clean-up all existing connection and resources. This should be done while responding to ongoing requests. In Dockerized runtimes shutting down containers is not a rare event, rather a frequent occurrence that happen as part of routine work. Achieving this demands some thoughtful code to orchestrate several moving parts: The load balancer, keep-alive connections, the HTTP server and other resources +**TL;DR:** Handle the process SIGTERM event and clean-up all existing connection and resources. This should be done while responding to ongoing requests. In Dockerized runtimes, shutting down containers is not a rare event, rather a frequent occurrence that happen as part of routine work. Achieving this demands some thoughtful code to orchestrate several moving parts: The load balancer, keep-alive connections, the HTTP server and other resources **Otherwise:** Dying immediately means not responding to thousands of disappointed users @@ -1399,6 +1552,8 @@ In addition, referring to an image tag means that the base image is subject to c ## ![โœ”] 8.11. Clean-out build-time secrets, avoid secrets in args +### `๐ŸŒŸ #new` + **TL;DR:** Avoid secrets leaking from the Docker build environment. A Docker image is typically shared in multiple environment like CI and a registry that are not as sanitized as production. A typical example is an npm token which is usually passed to a dockerfile as argument. This token stays within the image long after it is needed and allows the attacker indefinite access to a private npm registry. This can be avoided by coping a secret file like `.npmrc` and then removing it using multi-stage build (beware, build history should be deleted as well) or by using Docker build-kit secret feature which leaves zero traces **Otherwise:** Everyone with access to the CI and docker registry will also get access to some precious organization secrets as a bonus @@ -1437,6 +1592,8 @@ In addition, referring to an image tag means that the base image is subject to c ## ![โœ”] 8.15. Lint your Dockerfile +### `๐ŸŒŸ #new` + **TL;DR:** Linting your Dockerfile is an important step to identify issues in your Dockerfile which differ from best practices. By checking for potential flaws using a specialised Docker linter, performance and security improvements can be easily identified, saving countless hours of wasted time or security issues in production code. **Otherwise:** Mistakenly the Dockerfile creator left Root as the production user, and also used an image from unknown source repository. This could be avoided with with just a simple linter. @@ -1469,7 +1626,7 @@ All translations are contributed by the community. We will be happy to get any h ### Translations in progress - ![FR](./assets/flags/FR.png) [French](./README.french.md) ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/129)) -- ![HE](./assets/flags/HE.png) Hebrew ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/156)) +- ![HE](./assets/flags/HE.png) [Hebrew](./README.hebrew.md) ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/156)) - ![KR](./assets/flags/KR.png) [Korean](README.korean.md) - Courtesy of [Sangbeom Han](https://github.com/uronly14me) ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/94)) - ![ES](./assets/flags/ES.png) [Spanish](https://github.com/goldbergyoni/nodebestpractices/blob/spanish-translation/README.spanish.md) ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/95)) - ![TR](./assets/flags/TR.png) Turkish ([Discussion](https://github.com/goldbergyoni/nodebestpractices/issues/139)) @@ -1771,6 +1928,7 @@ Thanks goes to these wonderful people who have contributed to this repository! Scc
Scc

๐ŸŒ Mauro Accornero
Mauro Accornero

๐Ÿ–‹ no-yan
no-yan

๐Ÿ–‹ + hodbauer
hodbauer

๐ŸŒ diff --git a/README.polish.md b/README.polish.md index 8ac673428..3fdae83d2 100644 --- a/README.polish.md +++ b/README.polish.md @@ -256,7 +256,8 @@ function someFunction() { } // Avoid -function someFunction() { +function someFunction() +{ // code block } ``` diff --git a/assets/images/Privatenpm.png b/assets/images/Privatenpm.png index 9038636d8..d14466f78 100644 Binary files a/assets/images/Privatenpm.png and b/assets/images/Privatenpm.png differ diff --git a/assets/images/The backend testing checklist.png b/assets/images/The backend testing checklist.png new file mode 100644 index 000000000..0b867d9cf Binary files /dev/null and b/assets/images/The backend testing checklist.png differ diff --git a/.operations/package.json b/package.json similarity index 52% rename from .operations/package.json rename to package.json index b727d827f..66bf9d9a0 100644 --- a/.operations/package.json +++ b/package.json @@ -4,9 +4,7 @@ "description": "[โœ”]: assets/images/checkbox-small-blue.png", "main": "gen-html.js", "scripts": { - "build": "cd .. && node .operations/gen-html.js", - "test": "echo \"Error: no test specified\" && exit 1", - "lint": "markdownlint ../READM*.md" + "lint": "markdownlint ./README*.md" }, "repository": { "type": "git", @@ -19,15 +17,6 @@ }, "homepage": "https://github.com/goldbergyoni/nodebestpractices#readme", "dependencies": { - "cheerio": "^1.0.0-rc.2", - "ci-info": "^3.1.1", - "github-api": "^3.0.0", - "graceful-fs": "^4.1.15", - "imagemin": "^6.0.0", - "imagemin-jpegtran": "^6.0.0", - "imagemin-pngquant": "^6.0.0", - "is-ci": "^3.0.0", - "markdownlint-cli": "^0.18.0", - "showdown": "^1.9.0" + "markdownlint-cli": "^0.18.0" } } diff --git a/sections/drafts/readme-general-toc-4.md b/sections/drafts/readme-general-toc-4.md index 85ec9ce0f..3db7b68a7 100644 --- a/sections/drafts/readme-general-toc-4.md +++ b/sections/drafts/readme-general-toc-4.md @@ -25,7 +25,7 @@ Welcome to the biggest compilation of Node.js best practices, based on our check ## ![](../../assets/images/checkbox-sm.png) 1. Structure your solution by feature ('microservices') -**TL&DR:** The worst large applications pitfal is a huge code base where hundreds of dependencies slow down developers as try to incorporate new features. Partioning into small units ensures that each unit is kept simple and very easy to maintain. This strategy pushes the complexity to the higher level - designing the cross-component interactions. +**TL&DR:** The worst large applications pitfal is a huge code base where hundreds of dependencies slow down developers as try to incorporate new features. Partitioning into small units ensures that each unit is kept simple and very easy to maintain. This strategy pushes the complexity to the higher level - designing the cross-component interactions. **Otherwise:** Developing a new feature with a change to few objects demands to evaluate how this changes might affect dozends of dependants and ach deployment becomes a fear. diff --git a/sections/errorhandling/returningpromises.md b/sections/errorhandling/returningpromises.md index 9fe85566b..11ebc5a67 100644 --- a/sections/errorhandling/returningpromises.md +++ b/sections/errorhandling/returningpromises.md @@ -4,7 +4,7 @@ ### One Paragraph Explainer -When an error occurs, whether from a synchronous or asynchronous flow, it's imperative to have a full stacktrace of the error flow. Surprisingly, if an async function returns a promise (e.g. calls other async function) without awaiting, should an error occur then the caller function won't appear in the stacktrace. This will leave the person who diagnoses the error with partial information - All the more if the error cause lies within that caller function. There is a feature v8 called "zero-cost async stacktraces" that allow stacktraces not to be cut on the most recent `await`. But due to non-trivial implementation details, it will not work if the return value of a function (sync or async) is a promise. So, to avoid holes in stacktraces when returned promises would be rejected, we must always explicitly resolve promises with `await` before returning them from functions +When an error occurs, whether from a synchronous or asynchronous flow, it's imperative to have a full stacktrace of the error flow. Surprisingly, if an async function returns a promise (e.g. calls other async function) without awaiting, should an error occur then the caller function won't appear in the stacktrace. This will leave the person who diagnoses the error with partial information - All the more if the error cause lies within that caller function. There is a v8 feature called "zero-cost async stacktraces" that allows stacktraces to not be cut on the most recent `await`. But due to non-trivial implementation details, it will not work if the return value of a function (sync or async) is a promise. So, to avoid holes in stacktraces when returned promises would be rejected, we must always explicitly resolve promises with `await` before returning them from functions
@@ -87,7 +87,7 @@ async function asyncFn () { return await syncFn() } -// ๐Ÿ‘Ž syncFn would be missing in the stacktrace because it returns a promise while been sync +// ๐Ÿ‘Ž syncFn would be missing in the stacktrace because it returns a promise while being sync asyncFn().catch(console.log) ``` @@ -165,7 +165,7 @@ Error: stacktrace is missing the place where getUser has been called at async Promise.all (index 2) ``` -*Side-note*: it may looks like `Promise.all (index 2)` can help understanding the place where `getUser` has been called, +*Side-note*: it may look like `Promise.all (index 2)` can help understanding the place where `getUser` has been called, but due to a [completely different bug in v8](https://bugs.chromium.org/p/v8/issues/detail?id=9023), `(index 2)` is a line from internals of v8 @@ -177,9 +177,9 @@ a line from internals of v8
Javascript

-*Note 1*: in case if you control the code of the function that would call the callback - just change that function to -async and add `await` before the callback call. Below I assume that you are not in charge of the code that is calling -the callback (or it's change is unacceptable for example because of backward compatibility) +*Note 1*: if you control the code of the function that would call the callback - just change that function to +`async` and add `await` before the callback call. Below I assume that you are not in charge of the code that is calling +the callback (or its change is unacceptable for example because of backward compatibility) *Note 2*: quite often usage of async callback in places where sync one is expected would not work at all. This is not about how to fix the code that is not working - it's about how to fix stacktrace in case if code is already working as @@ -210,8 +210,8 @@ Error: with all frames present where thanks to explicit `await` in `map`, the end of the line `at async ([...])` would point to the exact place where `getUser` has been called -*Side-note*: if async function that wrap `getUser` would miss `await` before return (anti-pattern #1 + anti-pattern #3) -then only one frame would left in the stacktrace: +*Side-note*: if async function that wrap `getUser` lacks `await` before return (anti-pattern #1 + anti-pattern #3) +then only one frame would be left in the stacktrace: ```javascript [...] @@ -235,12 +235,12 @@ Error: [...] ## Advanced explanation The mechanisms behind sync functions stacktraces and async functions stacktraces in v8 implementation are quite different: -sync stacktrace is based on **stack** provided by operating system Node.js is running on (just like in most programming -languages). When an async function is executing, the **stack** of operating system is popping it out as soon as the -function is getting to it's first `await`. So async stacktrace is a mix of operating system **stack** and a rejected -**promise resolution chain**. Zero-cost async stacktraces implementation is extending the **promise resolution chain** +sync stacktrace is based on **stack** provided by the operating system Node.js is running on (just like in most programming +languages). When an async function is executing, the **stack** of the operating system is popping it out as soon as the +function gets to its first `await`. So async stacktrace is a mix of operating system **stack** and a rejected +**promise resolution chain**. Zero-cost async stacktraces implementation extends the **promise resolution chain** only when the promise is getting `awaited` [ยน](#1). Because only `async` functions may `await`, -sync function would always be missed in async stacktrace if any async operation has been performed after the function +sync function would always be missing from async stacktrace if any async operation has been performed after the function has been called [ยฒ](#2) ### The tradeoff @@ -256,13 +256,13 @@ definitely should never be done up-front ### Why return await was considered as anti-pattern in the past -There is a number of [excellent articles](https://jakearchibald.com/2017/await-vs-return-vs-return-await/) explained +There is a number of [excellent articles](https://jakearchibald.com/2017/await-vs-return-vs-return-await/) explaining why `return await` should never be used outside of `try` block and even an [ESLint rule](https://eslint.org/docs/rules/no-return-await) that disallows it. The reason for that is the fact that since async/await become available with transpilers in Node.js 0.10 (and got native support in Node.js 7.6) and until "zero-cost async stacktraces" was introduced in Node.js 10 and unflagged in Node.js 12, `return await` was absolutely equivalent to `return` for any code outside of `try` block. It may still be the same for some other ES engines. This -is why resolving promises before returning them is the best practice for Node.js and not for the EcmaScript in general +is why resolving promises before returning them is the best practice for Node.js and not for ECMAScript in general ### Notes: @@ -270,7 +270,7 @@ is why resolving promises before returning them is the best practice for Node.js must always be built synchronously, on the same tick of event loop [ยน](#1) 2. Without `await` in `throwAsync` the code would be executed in the same phase of event loop. This is a degenerated case when OS **stack** would not get empty and stacktrace be full even without explicitly -awaiting the function result. Usually usage of promises include some async operations and so parts of +awaiting the function result. Common usage of promises includes some async operations and so parts of the stacktrace would get lost 3. Zero-cost async stacktraces still would not work for complicated promise usages e.g. single promise awaited many times in different places diff --git a/sections/errorhandling/testingerrorflows.md b/sections/errorhandling/testingerrorflows.md index 552ca1f53..ac03f4146 100644 --- a/sections/errorhandling/testingerrorflows.md +++ b/sections/errorhandling/testingerrorflows.md @@ -10,72 +10,69 @@ Testing โ€˜happyโ€™ paths is no better than testing failures. Good testing code

Javascript ```javascript -describe('Facebook chat', () => { - it('Notifies on new chat message', () => { +describe("Facebook chat", () => { + it("Notifies on new chat message", () => { const chatService = new chatService(); chatService.participants = getDisconnectedParticipants(); - expect(chatService.sendMessage.bind({ message: 'Hi' })).to.throw(ConnectionError); + expect(chatService.sendMessage.bind({ message: "Hi" })).to.throw(ConnectionError); }); }); ``` +
+### Code example: ensuring API returns the right HTTP error code and log properly +
-Typescript +Javascript -```typescript -describe('Facebook chat', () => { - it('Notifies on new chat message', () => { - const chatService = new chatService(); - chatService.participants = getDisconnectedParticipants(); - expect(chatService.sendMessage.bind({ message: 'Hi' })).to.throw(ConnectionError); +```javascript +test("When exception is throw during request, Then logger reports the mandatory fields", async () => { + //Arrange + const orderToAdd = { + userId: 1, + productId: 2, + }; + + sinon + .stub(OrderRepository.prototype, "addOrder") + .rejects(new AppError("saving-failed", "Order could not be saved", 500)); + const loggerDouble = sinon.stub(logger, "error"); + + //Act + const receivedResponse = await axiosAPIClient.post("/order", orderToAdd); + + //Assert + expect(receivedResponse.status).toBe(500); + expect(loggerDouble.lastCall.firstArg).toMatchObject({ + name: "saving-failed", + status: 500, + stack: expect.any(String), + message: expect.any(String), }); }); ``` +
-### Code example: ensuring API returns the right HTTP error code +### Code example: ensuring that are uncaught exceptions are handled as well
Javascript ```javascript -it('Creates new Facebook group', () => { - const invalidGroupInfo = {}; - return httpRequest({ - method: 'POST', - uri: 'facebook.com/api/groups', - resolveWithFullResponse: true, - body: invalidGroupInfo, - json: true - }).then((response) => { - expect.fail('if we were to execute the code in this block, no error was thrown in the operation above') - }).catch((response) => { - expect(400).to.equal(response.statusCode); - }); -}); -``` -
+test("When unhandled exception is throw, Then the logger reports correctly", async () => { + //Arrange + await api.startWebServer(); + const loggerDouble = sinon.stub(logger, "error"); + const errorToThrow = new Error("An error that wont be caught ๐Ÿ˜ณ"); -
-Typescript - -```typescript -it('Creates new Facebook group', async () => { - let invalidGroupInfo = {}; - try { - const response = await httpRequest({ - method: 'POST', - uri: 'facebook.com/api/groups', - resolveWithFullResponse: true, - body: invalidGroupInfo, - json: true - }) - // if we were to execute the code in this block, no error was thrown in the operation above - expect.fail('The request should have failed') - } catch(response) { - expect(400).to.equal(response.statusCode); - } + //Act + process.emit("uncaughtException", errorToThrow); + + // Assert + expect(loggerDouble.calledWith(errorToThrow)); }); ``` -
\ No newline at end of file + + diff --git a/sections/production/lockdependencies.md b/sections/production/lockdependencies.md index 1698c9bbb..61800062f 100644 --- a/sections/production/lockdependencies.md +++ b/sections/production/lockdependencies.md @@ -4,9 +4,7 @@ ### One Paragraph Explainer -Your code depends on many external packages, letโ€™s say it โ€˜requiresโ€™ and use momentjs-2.1.4, then by default when you deploy to production npm might fetch momentjs 2.1.5 which unfortunately brings some new bugs to the table. Using npm config files and the argument ```โ€“save-exact=true``` instructs npm to refer to the *exact* same version that was installed so the next time you run ```npm install``` (in production or within a Docker container you plan to ship forward for testing) the same dependent version will be fetched. An alternative and popular approach is using a `.shrinkwrap` file (easily generated using npm) that states exactly which packages and versions should be installed so no environment can get tempted to fetch newer versions than expected. - -* **Update:** as of npm 5, dependencies are locked automatically using .shrinkwrap. Yarn, an emerging package manager, also locks down dependencies by default. +Your code depends on many external packages, letโ€™s say it โ€˜requiresโ€™ and use momentjs-2.1.4, then by default when you deploy to production npm might fetch momentjs 2.1.5 which unfortunately brings some new bugs to the table. Using npm config files and the argument `โ€“save-exact=true` instructs npm to refer to the _exact_ same version that was installed so the next time you run `npm install` (in production or within a Docker container you plan to ship forward for testing) the same dependent version will be fetched. Due to this, starting from npm version 5 a package-lock.json file is generated in every install. This lock file pins all the dependencies and child dependencies versions. When the file is committed, any future install the gets a copy of the app will install the same dependencies version

@@ -19,51 +17,31 @@ save-exact:true

-### Code example: shrinkwrap.json file that distills the exact dependency tree - -```json -{ - "name": "A", - "dependencies": { - "B": { - "version": "0.0.1", - "dependencies": { - "C": { - "version": "0.1.0" - } - } - } - } -} -``` - -

- ### Code example: npm 5 dependencies lock file โ€“ package-lock.json ```json { - "name": "package-name", - "version": "1.0.0", - "lockfileVersion": 1, - "dependencies": { - "cacache": { - "version": "9.2.6", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-9.2.6.tgz", - "integrity": "sha512-YK0Z5Np5t755edPL6gfdCeGxtU0rcW/DBhYhYVDckT+7AFkCCtedf2zru5NRbBLFk6e7Agi/RaqTOAfiaipUfg==" - }, - "duplexify": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz", - "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=", - "dependencies": { - "end-of-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz", - "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=" - } - } + "name": "package-name", + "version": "1.0.0", + "lockfileVersion": 1, + "dependencies": { + "cacache": { + "version": "9.2.6", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-9.2.6.tgz", + "integrity": "sha512-YK0Z5Np5t755edPL6gfdCeGxtU0rcW/DBhYhYVDckT+7AFkCCtedf2zru5NRbBLFk6e7Agi/RaqTOAfiaipUfg==" + }, + "duplexify": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz", + "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=", + "dependencies": { + "end-of-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz", + "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=" } + } } + } } ``` diff --git a/sections/production/productioncode.md b/sections/production/productioncode.md index a86047312..6fb624a6c 100644 --- a/sections/production/productioncode.md +++ b/sections/production/productioncode.md @@ -6,11 +6,12 @@ Following is a list of development tips that greatly affect the production maintenance and stability: -* The twelve-factor guide โ€“ Get familiar with the [Twelve factors](https://12factor.net/) guide -* Be stateless โ€“ Save no data locally on a specific web server (see separate bullet โ€“ โ€˜Be Statelessโ€™) -* Cache โ€“ Utilize cache heavily, yet never fail because of cache mismatch -* Test memory โ€“ gauge memory usage and leaks as part your development flow, tools such as โ€˜memwatchโ€™ can greatly facilitate this task -* Name functions โ€“ Minimize the usage of anonymous functions (i.e. inline callback) as a typical memory profiler will provide memory usage per method name -* Use CI tools โ€“ Use CI tool to detect failures before sending to production. For example, use ESLint to detect reference errors and undefined variables. Use โ€“trace-sync-io to identify code that uses synchronous APIs (instead of the async version) -* Log wisely โ€“ Include in each log statement contextual information, hopefully in JSON format so log aggregators tools such as Elastic can search upon those properties (see separate bullet โ€“ โ€˜Increase visibility using smart logsโ€™). Also, include transaction-id that identifies each request and allows to correlate lines that describe the same transaction (see separate bullet โ€“ โ€˜Include Transaction-IDโ€™) -* Error management โ€“ Error handling is the Achillesโ€™ heel of Node.js production sites โ€“ many Node processes are crashing because of minor errors while others hang on alive in a faulty state instead of crashing. Setting your error handling strategy is absolutely critical, read here my [error handling best practices](http://goldbergyoni.com/checklist-best-practices-of-node-js-error-handling/) +- The twelve-factor guide โ€“ Get familiar with the [Twelve factors](https://12factor.net/) guide +- Be stateless โ€“ Save no data locally on a specific web server (see separate bullet โ€“ โ€˜Be Statelessโ€™) +- Cache โ€“ Utilize cache heavily, yet never fail because of cache mismatch +- Test memory โ€“ gauge memory usage and leaks as part your development flow, tools such as โ€˜memwatchโ€™ can greatly facilitate this task +- Name functions โ€“ Minimize the usage of anonymous functions (i.e. inline callback) as a typical memory profiler will provide memory usage per method name +- Use CI tools โ€“ Use CI tool to detect failures before sending to production. For example, use ESLint to detect reference errors and undefined variables. Use โ€“trace-sync-io to identify code that uses synchronous APIs (instead of the async version) +- Log wisely โ€“ Include in each log statement contextual information, hopefully in JSON format so log aggregators tools such as Elastic can search upon those properties (see separate bullet โ€“ โ€˜Increase visibility using smart logsโ€™). Also, include transaction-id that identifies each request and allows to correlate lines that describe the same transaction (see separate bullet โ€“ โ€˜Include Transaction-IDโ€™) +- Test like production - Make developers machine quite close to the production infrastructure (e.g., with Docker-Compose). Avoid if/else clauses in testing that check if we're in testing environment but rather run the same code always +- Error management โ€“ Error handling is the Achillesโ€™ heel of Node.js production sites โ€“ many Node processes are crashing because of minor errors while others hang on alive in a faulty state instead of crashing. Setting your error handling strategy is absolutely critical, read here my [error handling best practices](http://goldbergyoni.com/checklist-best-practices-of-node-js-error-handling/) diff --git a/sections/projectstructre/breakintcomponents.md b/sections/projectstructre/breakintcomponents.md index 297daaa55..91df46d17 100644 --- a/sections/projectstructre/breakintcomponents.md +++ b/sections/projectstructre/breakintcomponents.md @@ -4,7 +4,7 @@ ### One Paragraph Explainer -For medium sized apps and above, monoliths are really bad - having one big software with many dependencies is just hard to reason about and often leads to spaghetti code. Even smart architects โ€” those who are skilled enough to tame the beast and 'modularize' it โ€” spend great mental effort on design, and each change requires carefully evaluating the impact on other dependent objects. The ultimate solution is to develop small software: divide the whole stack into self-contained components that don't share files with others, each constitutes very few files (e.g. API, service, data access, test, etc.) so that it's very easy to reason about it. Some may call this 'microservices' architecture โ€” it's important to understand that microservices are not a spec which you must follow, but rather a set of principles. You may adopt many principles into a full-blown microservices architecture or adopt only a few. Both are good as long as you keep the software complexity low. The very least you should do is create basic borders between components, assign a folder in your project root for each business component and make it self-contained - other components are allowed to consume its functionality only through its public interface or API. This is the foundation for keeping your components simple, avoid dependency hell and pave the way to full-blown microservices in the future once your app grows. +For medium sized apps and above, *non-modular* monoliths are really bad - having one big software with 'spaghetti' of dependencies is just hard to reason about. The ultimate solution is to develop smaller software: divide the whole stack into self-contained components that don't share files with others, each is a standalone logical app (e.g. has its own API, service, data access, test, etc.) so that onboarding into it and changing the code is much easier than dealing with the whole system. Some may call this 'microservices' architecture โ€” it's important to understand that microservices are not a spec which you must follow, but rather a set of principles. You may adopt many principles into a full-blown microservices architecture or adopt only a few. The very least you should do is create basic borders between components, assign a folder or repository in your system root for each business component and make it self-contained. Other components are allowed to consume its functionality only through its public interface or API. This is the foundation for keeping your components simple, avoid dependency hell and pave the way to full-blown microservices in the future once your app grows

@@ -28,10 +28,38 @@ So what does the architecture of your application scream? When you look at the t ### Good: Structure your solution by self-contained components -![alt text](../../assets/images/structurebycomponents.PNG "Structuring solution by components") +```bash +my-system +โ”œโ”€ apps (components) +โ”‚ โ”œโ”€ orders +โ”‚ โ”‚ โ”œโ”€ package.json +โ”‚ โ”‚ โ”œโ”€ api +โ”‚ โ”‚ โ”œโ”€ domain +โ”‚ โ”‚ โ”œโ”€ data-access +โ”‚ โ”œโ”€ users +โ”‚ โ”œโ”€ payments +โ”œโ”€ libraries (generic cross-component functionality) +โ”‚ โ”œโ”€ logger +โ”‚ โ”œโ”€ authenticator +``` +

### Bad: Group your files by technical role -![alt text](../../assets/images/structurebyroles.PNG "Structuring solution by technical roles") +```bash +my-system +โ”œโ”€ controllers +โ”‚ โ”œโ”€ user-controller.js +โ”‚ โ”œโ”€ order-controller.js +โ”‚ โ”œโ”€ payment-controller.js +โ”œโ”€ services +โ”‚ โ”œโ”€ user-service.js +โ”‚ โ”œโ”€ order-service.js +โ”‚ โ”œโ”€ payment-service.js +โ”œโ”€ models +โ”‚ โ”œโ”€ user-model.js +โ”‚ โ”œโ”€ order-model.js +โ”‚ โ”œโ”€ payment-model.js +``` \ No newline at end of file diff --git a/sections/projectstructre/choose-framework.md b/sections/projectstructre/choose-framework.md new file mode 100644 index 000000000..6af330a0f --- /dev/null +++ b/sections/projectstructre/choose-framework.md @@ -0,0 +1,39 @@ +# Consider all the consequences when choosing the main framework + +

+ +### Recommended frameworks: Pros and cons + +Unlike other choices, choosing the core framework determines strategic factors like the development style and how likely the team is to hit a wall. We believe that framework popularity is a supreme consideration and put our focus on the top 4 most popular frameworks in terms of downloads and GitHub stars. The text below outlines the pros and cons of each framework and how to match a framework to the right application type + +**express.js** + +Pros: Unmatched popularity; gigantic eco-system of extensions and middleware; simple to learn and use; familiar to almost every Node.js developer; tons of community articles and videos are based on express + +Cons: Covers a small subset of a typical application needs - merely a web server that invokes the app function per URL. Choosing express means leaving a handful of app concerns uncovered; outdated mechanics - no native support for async-await; barely maintained and updated; Slower than others + +**Nest.js** + +Pros: More batteries than any other option - covers many application concern including message queues, scheduled jobs and more; OOP-style is an advantage for teams who appreciate this design style; awesome docs; well-maintained; high popularity with a vibrant community + +Cons: High-level abstractions that cloud built-in Node.js conventions; The inclusion of many features, heavy usage of TypeScript and reference to sophisticated patterns might push teams for increased complexity; Steeper learning curve due to a handful of unique narratives (e.g., interceptors, guards, modules, and more); Highly opinionated + +**Fastify** + +Pros: Relatively simple and lean; mostly based on Node.js/JavaScript standards; relatively shallow learning curve; with its official plugins cover many application concerns though not as rich as Nest.js; + +Cons: Younger than others and not as popular yet; smaller eco-system compared to express and Nest.js + +**Koa** + +Pros: When compared with express: it's Simpler and nimbler; modern API with async/await support; better performance + +Cons: Covers a small subset of a typical application needs - leaves a handful of app concerns uncovered; Not as popular as express and Nest.js + +### A brief choosing guide + +**Prefer express.js when** - having an experienced architect onboard _and_ in a need to control the fine-grained pieces of the puzzle. In this circumstances, Koa is also a solid option with a more modern API than express but a much smaller eco-system + +**Prefer Fastify when -** The app consists of reasonably-sized components/Microservices (i.e., not a huge monolith); for teams who have solid JavaScript & Node.js knowledge; when sticking to Node.js narratives and spirit is desirable + +**Prefer Nest.js when** - It's desirable to design and code in OOP style; when the team is highly experienced with Java/Spring/Angular or similar; for large size app that can't be broken down (i.e. monolith) to autonomous component; for a team that lacks fundamental JavaScript/Node.js skills (not exclusively, this yet another consideration); when the decision-making overhead should be minimized; when the time to the first delivery is a critical factor diff --git a/sections/projectstructre/createlayers.md b/sections/projectstructre/createlayers.md index bd02a1af0..f67f88cec 100644 --- a/sections/projectstructre/createlayers.md +++ b/sections/projectstructre/createlayers.md @@ -2,12 +2,27 @@

- ### Separate component code into layers: web, services, and Data Access Layer (DAL) +### Separate component code into 3 layers -![alt text](../../assets/images/structurebycomponents.PNG "Separate component code into layers") +The root of every component should hold 3 folders that represent common concerns and stages of every transaction: -

+```bash +my-system +โ”œโ”€ apps (components) +โ”‚ โ”œโ”€ component-a + โ”‚ โ”œโ”€ entry-points + โ”‚ โ”‚ โ”œโ”€ api # controller comes here + โ”‚ โ”‚ โ”œโ”€ message-queue # message consumer comes here + โ”‚ โ”œโ”€ domain # features and flows: DTO, services, logic + โ”‚ โ”œโ”€ data-access # DB calls w/o ORM +``` -### 1 min explainer: The downside of mixing layers +**- Entry-points -** This is where requests and flows start, whether it's REST API, Graph, message queue, scheduled jobs or any other _door_ to the application. This layer's responsibility is quite minimal - adapt the payload (e.g., JSON) to the app format, including first validation, call the logic/domain layer and return a response. This is typically achieved with a few lines of code. Many use the term "controller" for this type of code also technically, its just an adapter -![alt text](../../assets/images/keepexpressinweb.gif "The downside of mixing layers") +**- Domain -** This is where the app flows, logic and data live. This layer accepts protocol-agnostic payload, plain JavaScript object and returns one as well. Technically it contains common code objects like services, dto/entities, and clients that call external services. It also typically calls the data-access layer to retrieve or persist information + +**- Data-access -** This is where the app holds code that interacts with DB. Ideally, it should externalize an interface that returns/gets plain JavaScript object that is DB agnostic (also known as the repository-pattern). This layer involves DB helper utilities like query builders, ORMs, DB drivers and other implementation libraries + +**What is the merit? -** When having flexible infrastructure that allows adding more API calls and DB queries promptly, a developer can code a feature faster by focusing on the domain folder. In other words, less time is spent on technical activities and more on activities with added value. This is a ubiquitous trait that is at the heart of most software architectures like DDD, hexagonal, clean-architecture and others. On top of this, when the domain layer is not aware of any edge protocol, it can serve any client and not only HTTP calls + +**Why not MVC or clean architecture? -** The 3-tier pattern strikes a great balance between achieving the separation goal while still keeping the structure simple. It also lacks abstractions - each tier represents real-world physical tier where every request will visit. On the other hand, MVC is a simplistic pattern where the letters VC represent a few lines of a code only and the letter M means anything else. Clean architecture is architecture with high level of abstractions that can achieve even greater separation but the price tag is unproportionally higher due to the increased complexity \ No newline at end of file diff --git a/sections/projectstructre/separateexpress.md b/sections/projectstructre/separateexpress.md deleted file mode 100644 index e35c67d24..000000000 --- a/sections/projectstructre/separateexpress.md +++ /dev/null @@ -1,100 +0,0 @@ -# Separate Express 'app' and 'server' - -

- -### One Paragraph Explainer - -The latest Express generator comes with a great practice that is worth to keep - the API declaration is separated from the network related configuration (port, protocol, etc). This allows testing the API in-process, without performing network calls, with all the benefits that it brings to the table: fast testing execution and getting coverage metrics of the code. It also allows deploying the same API under flexible and different network conditions. Bonus: better separation of concerns and cleaner code - -

- -### Code example: API declaration, should reside in app.js/app.ts - -```javascript -const app = express(); -app.use(bodyParser.json()); -app.use('/api/events', events.API); -app.use('/api/forms', forms); -``` - -### Code example: Server network declaration, should reside in /bin/www - -
-Javascript - -```javascript -const app = require('../app'); -const http = require('http'); - -// Get port from environment and store in Express. -const port = normalizePort(process.env.PORT || '3000'); -app.set('port', port); - -// Create HTTP server. -const server = http.createServer(app); -``` -
- -
-Typescript - -```typescript -import app from '../app'; -import http from 'http'; - -// Get port from environment and store in Express. -const port = normalizePort(process.env.PORT || '3000'); -app.set('port', port); - -// Create HTTP server. -const server = http.createServer(app); -``` -
- -### Example: test your API in-process using supertest (popular testing package) - -
-Javascript - -```javascript -const request = require('supertest'); -const app = express(); - -app.get('/user', (req, res) => { - res.status(200).json({ name: 'tobi' }); -}); - -request(app) - .get('/user') - .expect('Content-Type', /json/) - .expect('Content-Length', '15') - .expect(200) - .end((err, res) => { - if (err) throw err; - }); -``` -
- - -
-Typescript - -```typescript -import * as request from "supertest"; -const app = express(); - -app.get('/user', (req: Request, res: Response) => { - res.status(200).json({ name: 'tobi' }); -}); - -request(app) - .get('/user') - .expect('Content-Type', /json/) - .expect('Content-Length', '15') - .expect(200) - .end((err: Error) => { - if (err) throw err; - }); - -``` -
diff --git a/sections/projectstructre/typescript-considerations.md b/sections/projectstructre/typescript-considerations.md new file mode 100644 index 000000000..fcf881111 --- /dev/null +++ b/sections/projectstructre/typescript-considerations.md @@ -0,0 +1,23 @@ +# Use TypeScript sparingly and thoughtfully + +

+ +### One Paragraph Explainer + +TypeScript has won the community's hearts and is almost a standard for modern JavaScript apps. Compared to plain JS, it brings much better coding ergonomics, facilitates editor code completions, even for historical libraries that were written with JavaScript and was proven to [prevent specific type of bugs](https://earlbarr.com/publications/typestudy.pdf). With that, if you look carefully under the hype, TypeScript actually brings two **mutually-exclusive** offerings to the table: type-safety and advanced design constructs like abstract classes, interfaces, namespaces and more. Many teams chose TypeScript for better type safety yet _unintentionally_, without any proper planning, use it for other purposes, such as OOP. These teams change their design style unintentionally due to the ['law of the instruments'](https://en.wikipedia.org/wiki/Law_of_the_instrument) โ€” a cognitive bias that involves using the tooling in hand whether they are the right choice for the mission or not. In other words, if an 'abstract class' exists in the toolbox โ€” developers will use it. If teams consciously opted for the coding techniques mentioned above โ€” that's fair and legit. For others, positively consider coding with classic JavaScript, plain functions and objects, which are simply decorated with primitive types. The latter option is likely to result in lower complexity + +

+ +### Research Quote: "15% less bugs" + +From the research [To Type or Not to Type](https://earlbarr.com/publications/typestudy.pdf) + +> "our central finding is that both static type systems find an important percentage of public bugs: both Flow 0.30 and TypeScript 2.0 successfully detect 15%!" + +

+ +### Blog Quote: "TypeScript will always miss 80% of bugs" + +From the post [The TypeScript tax](https://medium.com/javascript-scene/the-typescript-tax-132ff4cb175b) + +> "Some will argue that TypeScript provides realtime bug feedback, so you can catch the bugs earlier, but so do type inference, lint, and testing... You may argue that these other measures have a cost, but because TypeScript will always miss 80% of bugs, you canโ€™t safely skip them either way, so their cost applies to both sides of the ROI math, and is already factored in" diff --git a/sections/security/limitrequests.md b/sections/security/limitrequests.md index 21482d050..0763a3b57 100644 --- a/sections/security/limitrequests.md +++ b/sections/security/limitrequests.md @@ -8,12 +8,10 @@ Rate limiting should be implemented in your application to protect a Node.js app ```javascript const http = require('http'); - const redis = require('redis'); + const IoRedis = require('ioredis'); const { RateLimiterRedis } = require('rate-limiter-flexible'); - const redisClient = redis.createClient({ - enable_offline_queue: false, - }); + const redisClient = new IoRedis({ enableOfflineQueue: false }); // Maximum 20 requests per second const rateLimiter = new RateLimiterRedis({ diff --git a/sections/testingandquality/citools.md b/sections/testingandquality/citools.md deleted file mode 100644 index 375e89fea..000000000 --- a/sections/testingandquality/citools.md +++ /dev/null @@ -1,51 +0,0 @@ -# Carefully choose your CI platform - -

- -### One Paragraph Explainer - -The CI world used to be the flexibility of [Jenkins](https://jenkins.io/) vs the simplicity of SaaS vendors. The game is now changing as SaaS providers like [CircleCI](https://circleci.com/) and [Travis](https://travis-ci.org/) offer robust solutions including Docker containers with minimum setup time while Jenkins tries to compete on 'simplicity' segment as well. Though one can setup rich CI solution in the cloud, should it required to control the finest details Jenkins is still the platform of choice. The choice eventually boils down to which extent the CI process should be customized: free and setup free cloud vendors allow to run custom shell commands, custom docker images, adjust the workflow, run matrix builds and other rich features. However, if controlling the infrastructure or programming the CI logic using a formal programming language like Java is desired - Jenkins might still be the choice. Otherwise, consider opting for the simple and setup free cloud option - -

- -### Code Example โ€“ a typical cloud CI configuration. Single .yml file and that's it - -```yaml -version: 2 -jobs: - build: - docker: - - image: circleci/node:4.8.2 - - image: mongo:3.4.4 - steps: - - checkout - - run: - name: Install npm wee - command: npm install - test: - docker: - - image: circleci/node:4.8.2 - - image: mongo:3.4.4 - steps: - - checkout - - run: - name: Test - command: npm test - - run: - name: Generate code coverage - command: './node_modules/.bin/nyc report --reporter=text-lcov' - - store_artifacts: - path: coverage - prefix: coverage - -``` - -### Circle CI - almost zero setup cloud CI - -![alt text](../../assets/images/circleci.png "API error handling") - -### Jenkins - sophisticated and robust CI - -![alt text](../../assets/images/jenkins_dashboard.png "API error handling") - -

diff --git a/sections/testingandquality/mock-external-services.md b/sections/testingandquality/mock-external-services.md new file mode 100644 index 000000000..6def85708 --- /dev/null +++ b/sections/testingandquality/mock-external-services.md @@ -0,0 +1,88 @@ +# Mock responses of external HTTP services + +

+ +### One Paragraph Explainer + +Isolate the component under test by intercepting any outgoing HTTP request and providing the desired response so the collaborator HTTP API won't get hit. Nock is a great tool for this mission as it provides a convenient syntax for defining external services behavior. Isolation is a must to prevent noise and slow performance but mostly to simulate various scenarios and responses - A good flight simulator is not about painting clear blue sky rather bringing safe storms and chaos. This is reinforced in a Microservice architecture where the focus should always be on a single component without involving the rest of the world. Though it's possible to simulate external service behavior using test doubles (mocking), it's preferable not to touch the deployed code and act on the network level to keep the tests pure black-box. The downside of isolation is not detecting when the collaborator component changes and not realizing misunderstandings between the two services - Make sure to compensate for this using a few contract or E2E tests. + + +

+ +### Code Example โ€“ a simple mock using nock + +```javascript +// Intercept requests for internal or 3rd party APIs and return a predefined response +beforeEach(() => { + nock("http://localhost/user/").get(`/1`).reply(200, { + id: 1, + name: "John", + }); +}); +``` + +### Code Example โ€“ simulating an important scenario inside the test + +```javascript +// Using an uncommon user id (7) and create a compatible interceptor +test("When the user does not exist, return http 404", async () => { + //Arrange + const orderToAdd = { + userId: 7, + productId: 2, + mode: "draft", + }; + + nock("http://localhost/user/").get(`/7`).reply(404, { + message: "User does not exist", + code: "nonExisting", + }); + + //Act + const orderAddResult = await axiosAPIClient.post("/order", orderToAdd); + + //Assert + expect(orderAddResult.status).toBe(404); +}); +``` + +### Code Example โ€“ preventing requests from going outside to the real-world + +```javascript +beforeAll(async () => { + // ... + // ๏ธ๏ธ๏ธEnsure that this component is isolated by preventing unknown calls + nock.disableNetConnect(); + // Enable only requests for the API under test + nock.enableNetConnect("127.0.0.1"); +}); +``` + +### Code Example โ€“ ensuring that the outgoing request schema is correct + +```javascript +// ๏ธ๏ธ๏ธAssert that the app called the mailer service appropriately with the right input +test("When order failed, send mail to admin", async () => { + //Arrange + // ... + let emailPayload; + nock("http://mailer.com") + .post("/send", (payload) => ((emailPayload = payload), true)) + .reply(202); + const orderToAdd = { + userId: 1, + productId: 2, + mode: "approved", + }; + + //Act + await axiosAPIClient.post("/order", orderToAdd); + + // ๏ธ๏ธ๏ธAssert + expect(emailPayload).toMatchObject({ + subject: expect.any(String), + body: expect.any(String), + recipientAddress: expect.stringMatching(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/), + }); +}); +``` diff --git a/sections/testingandquality/randomize-port.md b/sections/testingandquality/randomize-port.md new file mode 100644 index 000000000..28af118df --- /dev/null +++ b/sections/testingandquality/randomize-port.md @@ -0,0 +1,31 @@ +# Specify a port in production, randomize in testing + +

+ +### One Paragraph Explainer + +When writing component/integration tests, the web server should be started by the tests in the same process - this opens the door for many desirable testing features like mocking, coverage, and more. In a multi-process test runner, multiple web server instances will be opened. If these instances try to open the same port, they will collide. In testing only, let the server randomize a port to prevent collisions. This can easily achieved by providing an [ephemeral port](https://en.wikipedia.org/wiki/Ephemeral_port), the number zero, so the operating system will allocate an available port + +

+ +### Code Example โ€“ starting the web server with testing in-mind + +```javascript +// api-under-test.js +const initializeWebServer = async () => { + return new Promise((resolve, reject) => { + // Fixed port in production, a zero port (ephemeral) for testing + const webServerPort = process.env.PORT ? process.env.PORT : 0; + expressApp = express(); + connection = expressApp.listen(webServerPort, () => { + // No port + resolve(expressApp); + }); + }); +}; + +// test.js +beforeAll(async () => { + expressApp = await initializeWebServer(); // No port +}); +``` diff --git a/sections/testingandquality/test-five-outcomes.md b/sections/testingandquality/test-five-outcomes.md new file mode 100644 index 000000000..d87386207 --- /dev/null +++ b/sections/testingandquality/test-five-outcomes.md @@ -0,0 +1,23 @@ +# Test the five potential outcomes + +

+ +### One Paragraph Explainer + +When planning your tests, consider covering the five typical flow's outputs. When your test is triggering some action (e.g., API call), a reaction is happening, something meaningful occurs and calls for testing. Note that we don't care about how things work. Our focus is on outcomes, things that are noticeable from the outside and might affect the user. These outcomes/reactions can be put in 5 categories: + +โ€ข Response - the test invokes an action (e.g., via API) and gets a response. It's now concerned with checking the response data correctness, schema, and HTTP status. + +โ€ข A new state - after invoking an action, some data is probably modified. For example, when updating a user, it may be that the new data was not saved. Commonly and mistakenly, testers check only the response and not whether the data is updated correctly. Testing data and databases raises multiple interesting challenges that are greatly covered below in the ๐Ÿ“— section 'Dealing with data'. + +โ€ข External calls - after invoking an action, the app might call an external component via HTTP or any other transport. For example, a call to send SMS, email or charge a credit card. Anything that goes outside and might affect the user - should be tested. Testing integrations is a broad topic which is discussed in the ๐Ÿ“— section 'Testing integrations' below. + +โ€ข Message queues - the outcome of a flow might be a message in a queue. In our example application, once a new order was saved, the app puts a message in some MQ product. Now other components can consume this message and continue the flow. This is very similar to testing integrations, only working with message queues is different technically and tricky. The ๐Ÿ“— section 'Message Queues' below delve into this topic. + +โ€ข Observability - some things must be monitored, like errors or remarkable business events. When a transaction fails, not only do we expect the right response but also the correct error handling and proper logging/metrics. This information goes directly to a very important user - the ops user (i.e., production SRE/admin). Testing error handlers isn't very straightforward because many types of errors might get thrown, where some errors should lead to process crashing, and there are many other details to cover. We plan to write the ๐Ÿ“— section on 'Observability and errors' soon. + +

+ +### Example: The backend testing checklist + +![The backend testing checklist](<../../assets/images/The backend testing checklist.png> "The backend testing checklist")