Падготовка да спрабаў пра Node.js і Express: 70 запитанняў з точнымі адказамі.
Структураваныя аналізы 70 запитанняў для спрабаў пра Node.js і Express, якія аб’яднаюць тэмы від event loop і streams да middleware, безпекі і масштабавання, з правкамі распашчых адказаў.
Крыніцы: Node.js & Express.js Interview Guide
Інтэрв’ю на пасажу для ролей у Node.js зазвычай абаротваюцься адносна тых сабе тэм: як однапраменны рантайм обслужвае тыячы з’ўязкаў, што на самай працэ ўрабляе цыкл з’явоў, і як Express ператварае прымітны сервер HTTP у аплікацыю. Запам’ятаныя крыхкія адказы рушаюць пад дапамогай даўальных запытанняў, таму ў гэтым аптакуле 70 распашчытаўных запытанняў сгрупаваны па тэмам, а кожны адказ супакоўваецца достатнімі паспектамі, якія дазволяюць яго захаваць. Якщо популярны адказ ўстарэўшы чы неаднаковы, пазначаецца яго выправленне, бо самэ відлічванне такіх дзеянняў часта і є тым, што адзначае сильнага кандыдата ад таго, хто толькі падготавіўся.
Асновы Node.js
Зап. 1. Што такое Node.js?
Это среда выканання, яка запускае JavaScript параду браузера. У ёй вбудованы двыгун V8 ад Google і дадзеныя API для файлаў, сецыі звязку і процэсаў. Яго модель — адзінчынна-адражаная, з неблакучым вводам/выводам, што робіць яго лёгкім і адпаведным для служб з вялікай колькасцю дадзеных і у рэальны час.
Q2. Чы ў Node.js адзінны потак?
Ваш JavaScript выкананае на аднам галоўным потаку, які керуе цыклам адражэння. Пад ўсім гэтым libuv падтрымлівае пул потакоў для заданняў, якія не маюць хорашай асінхронной інтэфейса ОС, такіх як вызовы файлавой системы, dns.lookup, дзеяння функцый crypto і zlib. Таму точная адказ: адзінны потак для вашага коду, многапотаковая структура на рэвені системы.
Q3. Чаму Node.js быстрый?
- V8 кампілюе JavaScript у машынны код замест таго, каб яго інтэрпретаваць.
- Ввод і вывод асінхронныя і неблакучыя.
Q4. Сінхронны проты асінхроннага коду
Сінхронная операцыя завершаецца раней, чым запускаецца наступны кадр, блакуя всему, што знаходзіцца пасля яе. Асінхронная операцыя, такая як чытанне файла чыста запит да базы дадзеных, пачынаецца і перадаецца на обработку; ея выконанне продовжваецца, а рэзультат пазней даўаецца за дапамогою калбэка чыста праграмы. У наведанай нижэй сінхронной версіі лог чакае на завершэнне цэлага чытання (і выводзіць Buffer, так как не пасылаецца ніякое кодаванне):
// Synchronous
const data = fs.readFileSync('file.txt');
console.log(data);
У асінхронной версіі пасляднія рэкі логу выводзяцца раней, чым тэкст файла, таму што чытанне завершаецца пазней, пад час наступнага цыклу запуску задач:
// Asynchronous
fs.readFile('file.txt', (err, data) => {
console.log(data);
});
console.log("This runs first!");
Q5. Што такое npm?
npm — это менеджар пакетаў, які ўключаецца ў Node.js. Яго вы викорыстоўваете для інсталяцыі, публікацыі та керавання залежнасцямі, а ён фіксуе іх, а таксама скрыпты, у файле package.json.
Q6. package.json протыпа package-lock.json
package.json описвае проект: його назву, версію, точку запуску, скрыпты та дыапазоны залежнасцяў, якія ён падтрымле. package-lock.json фіксуе точную версію кожнага інсталаванага пакета, уключаючы транзітныя, таким чынам кожны комп’ютер та система CI запускае ідэнтычную структуру. Зберагаюце оба файла.
Q7. Каліясць основныя модулі, якія трэба знаты?
Сярод найбольш апыліваных вбудованых элементаў — fs, path, http і https, os, events, stream, buffer, crypto, util, url, querystring (які зараз лічыцца застарэлымі і ў праграмаванні викорыстоўваецца URLSearchParams), child_process, cluster і zlib.
Q8. require() протыпа import
Разлікі между CommonJS і ES-модулямі часта паднімаюцца:
requireзапоўнюецца сінхронна; ES-модулі запоўнююцца асінхронна і таксама прадстаўляюць дынамічныimport(), які вяртае прабаму.- CommonJS ўжо як стандарт для файлаў
.js; ES-модулі выклікаюцься з атрыбутам"type": "module"у файлеpackage.jsonабо з расшырэнням.mjs.
module.exports; ES-модулі выважаюць данні за дапамою export і export default.require можа быць вызваныя ў будзь-ям месца, укладзеным нават у умовы; статычныя заявы import паднімаюцца на верхній рэвень і должны знаходзіцца там.Q9. module.exports протыка exports
exports спачатку ўсё тое ж аб’ект, што і module.exports. Дадаць да яго новыя атрыбуты можна, але прызначэнне новага значэння (exports = {...}) толькі зменяе локальную змэнную, а модуль продовжвае выважаць первісны аб’ект. Ёк хочаце заменіць весь экспорт, трэба прызначыць значэнне module.exports.
Q10. Шта такое REPL?
REPL — гэта абяцейка Read, Eval, Print, Loop. Запуск node без аргументаў ачынае інтэрактыўную шэл, якая вырахоўвае кожны выраз, які вы вводзіце, і выводзіце рэзультат, што ўжоць падходзіце для быстрых эксперыментаў.
Цікл зменаў і асінхронныя патэраны
П11. Што такое цікл зменаў?
Это механізм, які дазволяе аднаму потоку JavaScript выкарыстоўваць неблакіруючы ввод/вывод. Калі стак вызваў пуст, цікл берае наступны готавы калбэк з своіх черг і запускае яго. Болей дакладнае адказаў пра тое, як ён взаімаецца з libuv, даўнае ў нашай статыце пра libuv, цікл зменаў і пул потакоў.
П12. Каліясць фаз ціклу зменаў?
- Таймеры: калбэкі з
setTimeoutіsetInterval, чыя час адбыцца вэлічыну ўжо настала.
setImmediate().socket.on('close').Калекі мікрозадач (process.nextTick спачатку, а потым рэакціі на праграмы) спраўляюцца пасля кожнага окремага калебэкі, а не толькі между фазамі. Такое падходжэнне існуе з часоў Node 11, і гэта ўсё частае пытанне.
Q13. nextTick, promises, setTimeout і setImmediate
- Калебэкі
process.nextTick()выконваюцца як толькі завершыцца текущая операцыя, раней за мікрозадачы праграм.
.then, адбываецца калі канэк nextTick пуст, але ўсё раней, чым цикл пераходзіць да наступнага крока.setTimeout(fn, 0) выконваецца пад час фазы таймера, калі спынка, заданая для яго, выйшла.setImmediate() выконваецца пад час фазы перакантролювання, адразу пасля фазы запыткаў той жа ітерацыі.Этот скрыпт выкананаў усе чатыро:
console.log('start');
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('end');
Апэктыўны выхад паказвае спачатку сінхронныя логы, а потым мікрасправы. Две макросправы можаць выйсці ў будзь-якой парады, калі яны запланаваны з главнага модуля, адтуды ўсё залежыць ад таго, чы растаяла спынка таймера на момент пачатку циклу; унутры калебака файла або сокета setImmediate завжды выконваецца перш:
// Output:
// start
// end
// nextTick
// promise
// setTimeout (or setImmediate — order can vary if not inside I/O)
// setImmediate (or setTimeout)
Q14. Калебакі і пекло калебакоў
Калебэк — это функцыя, якая пасаецца оперэйшну, каб ёў мог лячыцца, калі тая оперэйшн завершыцца. Калі кожны крок залежыць ад пярэднего, калебэкі нараджаюцца все глыбей і глыбей, ствараючы піраміду, вядомую як «яму калебэкаў», якая ўскладнюе чытанне і адбаротку памылак:
getUser(id, (user) => {
getPosts(user, (posts) => {
getComments(posts, (comments) => {
// deeply nested - hard to manage
});
});
});
Promise’і і async/await спяшчаюць гэтую структуру.
Q15. Што такое Promise?
Promise — это об’ект, які представляе будучы рэзультат асінхронной оперэйшні. Ён можа быць у стане чакання, заполненым значэннем або адхілёным з прычыной, і як толькі ён набере стан, ёго статус больш не зменяецца. Адпаведныя працэўнікі прыўязваюцца за дапамогою .then і .catch:
const promise = new Promise((resolve, reject) => {
if (success) resolve(data);
else reject(error);
});
promise.then(data => console.log(data)).catch(err => console.error(err));
Q16. Што такое async/await?
Это сынтаксіс, падбудованы на абяцках, які дазваляе асінхроннаму коду чытацца зверху в ніж. Функцыя async завжды вяртае абяцку, а await паўзівае выконанне гэтай функцыі пакуль абяцка, якую чакаюць, не будзе вырашаная; адмовы праказуюцца як эксцэпціі, якія можна паследзіць:
async function getUser() {
try {
const res = await fetch('/api/user');
const data = await res.json();
return data;
} catch (err) {
console.error(err);
}
}
Заўважыце, што ў гэтым прыкладзе фіксуецца адмава і неявнаа вяртаецца значэнне undefined, таму вызываючыя код не можуць адразніць адмаву ад порожняга рэзультата. Часта кращым выборам є паследзіць эксцэпцыю занова.
Q17. Комбінаторы абяцак
Promise.all()вырашанае, калі вырашаныя ўсе вхідныя абяцкі, і адмовляецца як толькі хоць адна з вхідных абяцак упадзе.Promise.race()вырашанае тады, калі першая з вхідных абяцак упадзе, незалежна ад таго, чы ў яе рэзультат успех чы адмава.Promise.allSettled()чакае на вырашанне ўсіх вхідных абяцак і праказвае кожны з рэзультатаў.
Promise.any() выпаняецца, калі першы з вхідных элементаў выпаняецца, і адмовляецца (з AggregateError) толькі тады, калі ўсе адмовяюцца.Q18. Блакуючы проты неблакуючага коду
Блакуючы код зупіняе ўсё інша, пакуль не завершыць свою роботу, як це робіць fs.readFileSync. Неблакуючы код запускае роботу і дазволяе програме продаваць, як це робіць fs.readFile. На серверы адна блакуючая вызовка зупіняе ўсе актуальныя запиты.
Стрымы, буферы і запускі
Q19. Чаму ёсць стрымы?
Стрымы обрабоцваюць данні поступова, часткамі, замест таго, каб хаваць іх усе ў памяці. Яны яўляюцца правым інструментам для вялікіх файлаў, медыя і сетевага трафіку. Існуе чатыры відменныя:
- Чытальныя, напрыклад
fs.createReadStream - Запісныя, напрыклад
fs.createWriteStream - Дуплексны, можна чытаць і запісваць адночасна, напрыклад, TCP-сокет
- Трансфармацыя – дуплексны стрым, який зменшае данні пад час ўсунення, напрыклад
zlib.createGzip
Q20. Што такое Buffer?
Buffer зберагае необработаны бінарны дадзеныя ў памяці, выдыранай за межами купы V8. Ён викорыстоўваецца там, дзе данні є байтамі, а не стрэлкамі чы робамі: стрымамі, файламі, сокетамі. Стварэнне Buffer з стрэлкі паказвае базовыя значэння байтав у шасцяткавом формате:
const buf = Buffer.from('Hello');
console.log(buf); // <Buffer 48 65 6c 6c 6f>
Q21. Што такое EventEmitter?
EventEmitter з модуля events рэалізуе шаблон спазыральніка (pub/sub), на яком паўтараецца вялкая частка API Node. Вы ствараеце екземпляр:
const EventEmitter = require('events');
const emitter = new EventEmitter();
потым рэўіструеце слухачоў за дапамогою on і запускаеце іх за дапамогою emit, перадаючы всі аргументы:
emitter.on('greet', (name) => console.log(`Hello, ${name}`));
emitter.emit('greet', 'John');
Лісенары выконваюцца сінхрональна, па порядку рэгістрацыі, калі вызываецца emit.
Q22. Як обрабатваць вялікія даныя без выкарыстання всей памяці?
Іспользуйце стрымы: чытайце маленькія фрагменты і перадавайце чытальны стрым у пісьменны, так ўжо викорыстоўваная памяць залишаецца прыбліжна стаячай, незалежна ад розмеру файла:
fs.createReadStream('input.txt').pipe(fs.createWriteStream('output.txt'));
Для коду, які выкарыстоўваецца у працэсе, stream.pipeline є кращым выборам чым .pipe(), таму што ён перадае паслявыкі і чыстаўае кожны стрым у ланцузе.
Болей глыбокія концэпцыі часу выконання
Q23. process.nextTick() проты setImmediate(), дэтальна
nextTick калебэкі выконваюцца прытаму, як толькі разрэшаецца продажчыця циклу, нават раней за мікразадачы promise. Калебэкі setImmediate чакаюць фазы перагляду, пасля фазы запытання. Пакалькі кожны nextTick можа запланаваць іншы, рэкурзіўны ланцуг можа «зголадзіць» цикл, так што незавершаныя чытанні і запісы ніколі не будуць адрабатаны. Гэты спосаб абякання дакладна расказаны ў як process.nextTick тыха «зголадзіць» цикл задач Node.js.
Q24. Што такое кластэруванне і чаму яго викорыстоўваюць?
Адзін процес Node выкарыстоўвае адну ядро для JavaScript. Модуль cluster стварае калькі працоўных процэсаў, кожны з якіх мае свой цикл задач і памяць, і дазволяе ўсім ім дзеліцца аднам портам сервера, так што ўсе ядро комп’ютера могу обслужваць трафік. Модулі імпортуецца так:
const cluster = require('cluster');
const os = require('os');
Потым з галоўнага процэсу ствараецца по аднаму працоўніку на кожны CPU, пакалі працоўнікі завантажаюць сервер:
if (cluster.isMaster) {
os.cpus().forEach(() => cluster.fork());
} else {
require('./server');
}
cluster.isMaster яшчэ працюе, але ўсталоўлены як застарэлы элемент на кантэкст cluster.isPrimary; у новым кодзе трэба вжываць другі варыянт.
Q25. cluster versus worker_threads
clusterстварае адзінаковыя процэсы з ізольаванай памяцю, што ід゙альна падходзіць для масштабавання HTTP-сервераў.worker_threadsстварае ніткі ўнутрь адного процэсу. Яны можаюць дзеліцца памяцю черезSharedArrayBufferабо перадаваць буферы, што падходзіць для задач, якія выкарыстоўваюць большую кантыгітэт CPU, напрыклад, обробку зяўроў.
Q26. Як адбываецца обработка задач, якія выкарыстоўваюць большую кантыгітэт CPU?
Код, які выкарыстоўвае моц CPU, блакуе цыкл адбывання запытоў, таму яго трэба перасунуць іншыя: у worker_threads, у дзеціны процесаў, або цэлкам выйсці з сервіса за дапамогою кялі і фонавых працоўнікаў. Для абмеркавання патэранаў адзірніце як пераказваць роботу, якая выкарыстоўвае моц CPU, за дапамогою worker threads і пулів.
Q27. Чаму ёсць мідлвэр?
Мідлвэр — это код, які знаходзіцца межы прийманням запыту і адправленнем адпаведзення, дзе ён можа пераглядаць, трансфармаваць, верыфікацыяваць або логаваць. Express будуе свой весь дизайн навакол гэтай ідеі, як показана ў наступных раздзелах Express.
Q28. Чаму ёсць дзеціны процесы?
Модуль child_process запускае зовнішнія прыграмы або іншыя скрыпты Node ў окремых процесах. Важныя чатыры функцыі:
spawn()перадае выходныя даны праграмавана, таму ён добра справляецца з вялікімі выходамі.
exec() запускаецца чераз шэл і зберагае выходны тэкст у буферы, што падходзіць для невялікіх рэзультатаў.execFile() запускае файл безпосередна, без шэла, што ўбезпечывае большую стабільнасць праз працэўванне з ненадзеянымі аргументамі.fork() — спецыялізаваны варіянт spawn для модуляў Node, які стварае канал IPC для абмену паведамленнямі.Q29. Што такое libuv?
libuv — это біблія на мове C, якая забезпечвае цыкл задач Node, асінхронны ввод/вывод і пул задач, маскуючы разніцу ў механізмах паведамлення пра змяны ў Linux, macOS і Windows.
Q30. setTimeout versus setInterval
setTimeout(fn, delay) запускае fn аднойчы пасля прынеймна delay мілісекунд. setInterval(fn, delay) запускае яго пастаянна ў гэтым інтэрвале, пакуль не будзе вызваны clearInterval().
Q31. Адмініструванне некаптаваных асэксіянаў і некарыстоўваных адхоўленняў
Для якіх застосовуюцца працэсавыя хендлеры. Для сінхронных асэксіянаў, якія ніхто не каптаваў, трэба запісаць лог і завершыць працэс, таму што працэс можа знаходзіцца ў нестабільным стане:
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1); // recommended: exit after cleanup
});
Для прамісаў, якія адхоўлены без хендлера:
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
});
Зачынення Node 15 змусіла працэс завершыцца па значчыні, калі адхоўленне не карыстуецца хендлерам, таму рэгістрацыя гэтага слухальніка зменяе гэтая поведзенне. Спрыяйце якіму-небудзь з гэтых спосабаў як апошняе рашэнне для запісу логаў, а не як частка контролю прайходу; выкорыстоўваеце try/catch і .catch() у самам кодзе.
Q32. Што такое вытэк памяці і як яго знайсці?
Адыяванне — это пам’ець, яка застаёцца доступной пасля таго, калі ёй больш не трэба: зростаючыя глобальныя кешы, таймеры або слухачы, якія ніколи не адчыняюцца, клаўзуры, якія зберагаюць великія об’екты. Дыягноставаць можна за дапамою флага --inspect і знімакаў памяці Chrome DevTools, або за дапамою інструментаў такіх як heapdump і clinic.js, а таксама паўтарнае перагляданне знімакаў з часам.
Q33. __dirname versus __filename
__dirname — это абсалютны шлях да папкі, якая знаходзится ў тым жа модуле; __filename — это абсалютны шлях да самага файла. Абодва існуюць толькі ў CommonJS. У ES-модулях іх можна атрыбутамі import.meta.url, альбо import.meta.dirname і import.meta.filename у новейшых версіях Node.
Q34. Чаму викорыстоўваць модуль шляхоў?
path дазваляе ствараць і аналізаваць шырокі спектар падчынакоў файлаў за дапамойкай такіх функцый, як path.join, path.resolve, path.basename і path.extname; такім чынам вам не патрэбна ручная з’едыненне страк, а таксама не будзе проблем з разнымі знакамі слэшаў у Windows і Unix.
Q35. fs.readFile versus fs.createReadStream
fs.readFile завантажае весь файл у памяць прычымоўваючыся, што є рызыкавана для большых файлоў. fs.createReadStream доставляе файл часткамі, залічваючы памяць на некалькіх розмерах і дазваляючы начыніць обробку негайна.
Асновы Express
Q36. Што такое Express?
Express — это мінімалістычная, нейкантрольваная веб-фрамворк, створаны на базе модуля http з Node. Ён дадае можлівасць для нарабаткі маршрутаў, викорыстання сервісаў-прамежуточных элементаў і інтеграцыі з двойкамі шаблонаў, адчыняючы шлях да абходу большай часткі стандартных процедур обробкі запытоў.
Q37. Адміністратыўны сервер Express
Створыце об’ект прыкладнай програмы:
const express = require('express');
const app = express();
потым зарэгіструйце маршрут і запускайце праслухоўванне:
app.get('/', (req, res) => {
res.send('Hello World');
});app.listen(3000, () => console.log('Server running on port 3000'));
Q38. Чым являецца мідлвэр у Express?
Мідлвэр у Express — это функцыя, якая прымеўа параметры req, res і next. Яна можа выкананыць будзь-які код, зменіць запит або адпаведзь, завершыць цикл паразначэнням адпаведзі, або вызваць next(), ўскладніўшы керуванне наступной функцыі. Класычным прыкладам являецца логгер запытоў:
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
Якщо мідлвэр не паразначае адпаведзь і не вызвае next(), запыт застаёцца у стане очаквання.
Q39. Відмены мідлвэраў у Express
- На роўні прыкладнай програмы, прыўязаныя за дапамогай
app.use()абоapp.METHOD(). - На роўні маршрута, прыўязаныя да экзампляра
express.Router().
(err, req, res, next).express.json() і express.static().cors, morgan і helmet.Q40. Як працюе маршрутазаванне?
Маршрут спаўнае метод HTTP з шляхам і працоўнікам. Тыповы ресурс CRUD выглядае так:
app.get('/users', (req, res) => { /* get all users */ });
app.post('/users', (req, res) => { /* create user */ });
app.put('/users/:id', (req, res) => { /* update user */ });
app.delete('/users/:id', (req, res) => { /* delete user */ });
Q41. Чым ёсць express.Router()?
Маршрутавальнік — это самастойная міні-аплікацыя з сае маршрутамі і проміжнім програмаванням, якую можна разместіць на певным шляху. Ён дапамагае арганізавацыі вялікіх аплікацый па функцыях. Маршруты трэба задаць у сабеўскім модулі:
// userRoutes.js
const router = require('express').Router();
router.get('/', (req, res) => res.send('All users'));
module.exports = router;
і разместіць яго ў галоўнай аплікацыі, дзе його / стане /users:
// app.js
app.use('/users', require('./userRoutes'));
Q42. app.use() проты app.get() і іншых
app.use() падае на кожны метод HTTP і любы шлях, які пачынаецца з заданага прыфіксу, і самэй таму ён встановляе мідлвэры і рутэры. app.get(), app.post() і падобныя падаюць на адзін метод і цэлы шаблон маршрута.
Q43. req.params, req.query і req.body
req.paramsхраніць іменаваныя сегменты маршрута:/users/:idдаеreq.params.id.req.queryхраніць значэння строкі запыткі:/users?age=25даеreq.query.age, якое завжды ў формате строкі.req.bodyхраніць апрацаваны пакет запыткі для такіх методаў, як POST і PUT, і застаецца незначэным, якщо спачатку не запрацаваны мідлвэр для апранткі тэла, напрыкладexpress.json().
Q44. Апрантка JSON і дадзеных формы
Два вбудованыя парсары падтрымляюць большасць случаў: адны для тэла JSON, а другі — для HTML-форм у формате URL-кодавання:
app.use(express.json()); // parses application/json
app.use(express.urlencoded({ extended: true })); // parses form data
Q45. Што такое express.static()?
Это вбудованы мідлвэр, які служыць файламі такімі, як з’явы, шаблонамі стылю, скрыптамі і HTML, з падкаталога:
app.use(express.static('public'));
Промежуточны Express
Q46. Адрабатка памылак
Памылкі адрабатваюцца за дапомогай мідлвэра з чатырма параметрамі, які рэгіструюцца пасля всіх маршрутаў:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: 'Something went wrong' });
});
Усередзіне маршрутаў і мідлвэра перадавайце памылкі за дапомогай next(err):
app.get('/user/:id', async (req, res, next) => {
try {
const user = await getUser(req.params.id);
res.json(user);
} catch (err) {
next(err); // passed to error-handling middleware
}
});
У режыме працы з кліентамі ухіляйцеся ад адправкі err.stack чы роўназначных внутраніх паведамленняў.
Q47. Адрабатка коду 404
У Express няма вбудованага падтрымкі коду 404 для JSON. Дадайце універсальны мідлвэр пасля кожнага маршрута; якщо запит дасягне яго, нічы інша не падходзіць:
app.use((req, res) => {
res.status(404).json({ message: 'Route not found' });
});
Q48. CORS і пакет cors
Cross-Origin Resource Sharing — гэта механізм прыгледача, які выявляе, чы можа стораніца з аднаго источніка чытаць адпаведныя даны з іншага. Прыводзіць яго ў дзеянне прыгледач; сервер уключае яго за дапамою заголовкаў адпаведзення, якія пакет cors задае сам.
const cors = require('cors');
app.use(cors()); // allow all origins
app.use(cors({ origin: 'https://example.com' })); // restrict origin
У гэтым фрагменте паказаны два варыянты: дазволаць усім істочнікам або толькі аднаму; трэба выбраць адзін з іх, а не оба, калі ўжо таму, што прадуктыўны варыянт будзе застосаваны першым.
Q49. Сесіі і кукі
Завантажыце сервісны медіум для кукі і сесій:
const cookieParser = require('cookie-parser');
const session = require('express-session');
потым зарэгіструйце іх за дапамою секретнага ключа для падписвання і адпаведных настаўленняў:
app.use(cookieParser());
app.use(session({
secret: 'mySecretKey',
resave: false,
saveUninitialized: true
}));
Зберагаюце секретны ключ у зменнай аптэкі, а не ў коде, і разважыце варыянт saveUninitialized: false, каб ужо не ствараць сесій для анонімных візітароў. Стандартны внутрашній сховыш не падходзіць для працы ў продакшэне.
Q50. Аутантыкач JWT
Імпортуйте бібліятэку:
const jwt = require('jsonwebtoken');
У прыказе нижчэй пад час заходу адмаўніка ствараецца токен, які містіць ідэнтыфікатор пользователя, маршруты захоўваныя са адпаведным мідлвэром, які выкарыстоўвае токен з загаловка Authorization для яго пераканання, а раскодаваны пакет дадзеных прыўязуецца да req.user:
// Generate token on login
const token = jwt.sign({ id: user.id }, 'secretKey', { expiresIn: '1h' });// Middleware to protect routes
function auth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ message: 'No token' });
try {
req.user = jwt.verify(token, 'secretKey');
next();
} catch (err) {
res.status(403).json({ message: 'Invalid token' });
}
}app.get('/protected', auth, (req, res) => res.json({ data: 'secret data' }));
Як і у разе сэсій, значэнне 'secretKey', заданае як статычная вялічынь, павінна бацца з конфігурацыі. Стандартна практыка — вярнуць код 401, калі токен не ўсунуты, і код 403, калі ён невярны; пры тым часо многі API вярнаюць код 401 у обох случаях.
Q51. Автарызацыя запитоў
Бібліятэкі такія як express-validator або joi задаюць правіла і скупляюць адносныя памылкі. З express-validator імпортуецца неабходныя дапаможнікі:
const { body, validationResult } = require('express-validator');
Пасля чаго перад обробнікам запыту паўзаюцься мідлвэры з правіламі, а пасля — пераканваецца рэзультат ўсіх перакананняў:
app.post('/user',
body('email').isEmail(),
body('password').isLength({ min: 6 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
// proceed
}
);
Для абсалютнейшага парабярання падходаў да верыфікацыі, адзірніце Zod проты express-validator.
Q52. Аплоад файлаў
multer парсуе multipart/form-data. Наладзіце месца размешчэння:
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
і застосаваце яго па кожнай маршруту; upload.single('file') чакае аднаго файла ў полі пад назвай file і выдае яго як req.file:
app.post('/upload', upload.single('file'), (req, res) => {
res.json({ file: req.file });
});
У рэальных системах таксама трэба обмежыць размаўку і тип файла, а таксама ніколі не даверяць імену файла, якое даў кліент.
Q53. З’яўленне ў MongoDB за дапамогою Mongoose
Ачыніце з’яўленне:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mydb');
потым задаце модэль і выкарыстоўваеце яе з маршруту:
const User = mongoose.model('User', new mongoose.Schema({
name: String,
email: String
}));app.post('/users', async (req, res) => {
const user = await User.create(req.body);
res.status(201).json(user);
});
Перадача req.body без перапрацоўкі ў функцыю create дозволяе кліентам задаўць будзь-якія значэння поль шымату, таму спачатку трэба пераканацца ў правільнасці дадзеных або выбраць толькі дазволеныя поля.
Q54. Парадок выканання мідлвэра
Express выканаўвае мідлвэры за тым порядкам, які ўстановлены для іх рэгістраціі, зверху вярху, і ланцоўка зупіняецца на будзь-яй функцыі, якая не вызывае next(). Самэй гэтым порядкам адпавядаюць обробнікі кэйсаў 404 і абрабоцчыкі памылак, якія знаходзяцца ў канцы.
Q55. Машыны для стварэння шаблонаў
Машыны для стварэння шаблонаў перадаюць HTML на адной стороне сервера на базе дадзеных. Популярныя варыянты – EJS, Pug (ранейша Jade) і Handlebars. Ёх настройка выконвачыцца за дапамою настаўкі машыны для стварэння шаблонаў і вызова res.render:
app.set('view engine', 'ejs');
app.get('/', (req, res) => res.render('index', { name: 'John' }));
Развіты Express і Node
Q56. Асінхронныя памылкі ў Express 4 проты Express 5
У Express 4 абякаваныя запаветы з async-хэндлераў не ператрымваюцца фрэймворкам; без try/catch плюс next(err) або кэпсулы ўсё гэта стае неадмовленным абякаваннем. Express 5 автаматычна перадае абякаваныя запаветы з хэндлераў і мідлвэраў у адпаведны хэндлер памылак. Звычны спосаб рашэння проблемы ў Express 4 — это невялікая функцыя вышэйшага порядку:
// Express 4 workaround
const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
Кэпсулаванне хэндлера з такой функцыяй направляе будзь-якое абякаванне да next:
app.get('/data', asyncHandler(async (req, res) => {
const data = await fetchData(); // if this throws, asyncHandler catches it
res.json(data);
}));
Гэты патэрн дакладна расказаны ў стацыі пра адключэнне стандартных блокавання try/catch за дапамогою asyncHandler.
Q57. Структураванне большога прыемніка Express
Звычны макет у стылі MVC раздзеляе функцыі на папкі:
project/
├── controllers/ # business logic
├── routes/ # route definitions
├── models/ # DB schemas
├── middlewares/ # custom middleware
├── config/ # DB, env configs
├── utils/ # helper functions
├── app.js
└── server.js
Маршруты адыявляюць тэрміны выканання і перадаюць ў кантролеры, кантролеры зберагаюць логіку обработкі запитоў, а модэлі інкапсулююць доступ да дадзейнаў. Раздзелэнне app.js (канфігурацыя аплікацыі) ад server.js (яе запуск) таксама спакойвае імпорт аплікацыі ў тэстах.
Q58. Обмежэнне частоты запытоў
express-rate-limit пісае колькість запытоў ад кожнага кліента за певны перыяд. Яго трэба імпортаваць:
const rateLimit = require('express-rate-limit');
і застосаваць абмежэчылец, напрыклад 100 запытоў ад кожнага IP за 15 хвілін:
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per window
});app.use(limiter);
Стандартны хранільнік знаходзіцца ў памяці, таму кожны процес у кластэре пісае даны окрема; калі расширяеце аплікацыю, выкорыстоўваеце спяльны хранільнік, такі як Redis. За проксі канфігуруйце trust proxy, ўбедзіцеся, што выкорыстоўваецца рэальны IP кліента.
Q59. Захаванне аплікацыі Express
- Установіце захоўныя HTTP-загаловкі з
helmet.
dotenv, а не зберагаць іх у коде.npm audit.Q60. res.send() versus res.json() versus res.end()
res.send()прымае строкі, об’екты чыра буферы і выбирае падходячыContent-Type.res.json()серыялізуе свой аргумент у формате JSON і задаеContent-Type: application/json.res.end()завершае адпаведзь без дадатковых даных, яэн проста абгортка сабе методу Node.
Q61. Чым займаецца next('route')?
Ён прыскакае праз засталія каллбэкі ныявегае маршруту і пераходзіць да наступнага падходящага маршруту. Гэта дзеянне дыяўуе толькі для працоўнікаў, зарэгістраваных через app.METHOD() або router.METHOD() з калькама каллбэкаў.
Q62. Падвышэнне працоўной спроможнасці
- Заўядзейце адны процэс на кожны ядро за дапамогою кластэрізацыі або PM2.
- Зберагачуйце адпаведзі ва Redis або ў памяці.
- Скомпрэсавайце адпаведзі за дапамогою мідлвэра
compression, або нехай гэта зрабіць проксі. - Разместіце Nginx або іншы зворачны проксі чы рэзалёйт-балансер перад сабой.
- Ухіліце роботу, якая сильна навантажвае CPU, ад цыклу змаганняў.
- Складзіце пулы з’яўленняў да базы дадзенаў.
- Разглядзіце варыянт HTTP/2.
- Іспользуйце асінхронны ввод/выход у всім і ухіліцеся ад варыянтав
Syncу шляхах запитоў.
Q63. REST проты GraphQL з Express
REST адмініструе многа канцэнтры з фіксаванымі форматамі адпаведзей, таму кліенты часта збіраюць больш за неабяжнае колькасць дадзэйнай інфармацыі або менш. GraphQL адмініструе адзін канцэнтр, дзе кліент точна апісвае тыя данні, якія ём нужны. Яго зазвычай інтегруюць у Express за дапамою express-graphql або apollo-server-express; аднак оба гэтыя пакеты ўжо зняты з апошней падтрымкі на корыстце новэйшых варыянтов інтеграцый, таму трэба пераканаліцца з актуальной документацыяю Apollo або GraphQL Yoga.
Q64. Як Express обрабоцвае адночасныя запыткі?
Кожны запит — это простае выкліканне функцый у цыкле змагчароў. Пакалі запыты да базы дадзеных і сеті не блакуюць, адна нить можа обрабатваць многа запытов: сетавы трафік перадаецца на асінхронныя механізмы операцыйной системы, робота з файламі — на пул нітей libuv, а функцыі выклікаюцца праз адпаведныя рэзультаты. Не існуе адной ніты на кожны запит, як у класычных багатанітных серверах, і самэ гэта прычына таго, што адні сповольны сінхронныя працоўнікі вплываюць на всіх.
Q65. PM2 проты кластэрнага модуля
cluster — это вбудованы прымітів для масштабавання за дапамою калькуляцый. PM2 — это менеджер процэсаў, створаны на адной і той жа падставе, які дагэтуль додае автаматычныя перзапускі пасля збоев, балансаванне навантажэння, карыстаўчаснае адрабатванне логаў, перзапуск без перыяду і монітарынг, таму ён викорыстоўваецца у багатых продакшн-рэалізацыях.
Q66. Чаму ставіць зворачны проксі перед Node?
Адварсны проксі, такі як Nginx, завершаець процес TLS-шифравання, распадзяе навантажэнне між экземплярамі, выконвае кэшаванне, стыскае і супляе статычныя файлы, таму ўзьмоўваючы гэтую роботу з Node, каб процес прыемлівання могаў сфокусавацца на бізнес-логіцы.
Q67. Канфігурацыя, спецыфічная для среды
Пакет dotenv завантажае зменныя з файла .env у process.env, а прыяплівання пераходзіць на стандартныя значэння, калі якое-небудзь з іх не ўсёродзе:
require('dotenv').config();
const PORT = process.env.PORT || 3000;
Няўжоныя версіі Node таксама можуць завантажваць файлы средовых налашчэнняяў натывна з паметкай --env-file. Ніколы не комітаваць файлы .env, якія маюць секрэтную інфармацыю.
Q68. app.all() проты app.use()
app.all() падходзіць да кожнага методу HTTP на адной конкрэтнай дароге. app.use() таксама падходзіць да кожнага методу, але да дарогі і всього, што знаходзіцца пад яю, таму ён выкорыстоўваецца для падключэння мідлвэра.
Q69. Пагінацыя з Express і MongoDB
Чытайце значэння page і limit з рэчы запиту з разумнымі стандартнымі значэннямі, а пасля сумавайце skip і limit:
app.get('/posts', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const posts = await Post.find()
.skip((page - 1) * limit)
.limit(limit);
res.json(posts);
});
Прыяўнайце захоплення пры выкарыстоўванні ў працэсе: перадайце параметр радыку для parseInt, обмежыце значэння page не менш чым 1, а limit — максімальным значэнням, а таксама дадзіце стабільны параметр sort. Для вельмі большых калекцыяў значэння skip стае медленейшым з ростам афсэты, тады як пагінацыя на базе курсара ў індексаванай полі здаецца кращай.
Q70. Што такое ідэмпотентнасць?
Аперэйція ўважаецца ідэмпатным, калі яе павтарэнне залучае сервер у той жа стан, як і адзін раз яе выкарыстоўванне. GET, PUT і DELETE вядомы як ідэмпатныя; POST зазвычай не ўважаецца такім, таму што кожны вызов можа стварыць новы ресурс. Павторныя спробы пасля выканання таймауту є безпечныя толькі для ідэмпатных запитоў, і самэ гэта ўважаецца прычыной таго, чаму API для платежаў часта дадаюць ключы ідэмпатнасці да запитоў типу POST.
Галоўныя выводы
- Большасць запытанняў пра Node можна звузіць да аднаго модэлю: адна нитка JavaScript, цыкл здачы справаў з вяліканутымі фазамі, мікрозадачы, якія адмахваюцца пасля кожнага калебэка, і libuv, який адпавядае за решта.
- Большасць запытанняў пра Express можна звузіць да іншага модэлю: архітектура з абараною ланцоўкай мідлвэра, дзе кожная функцыя альбо адпавідае, альбо вызывае
next(), пры чым будзь-якія выключэння пераходзяць да працоўнікаў, якія прыманяюць чатыры аргументы.
cluster.isPrimary, проблемы з крахам праграмы пад неконтрольваныя адмовы, асінхронныя кашталкі Express 5 і памагчыцы для працы з маршрутамі ESM.