פריימווארקים פופולארים לWEB בNodeJS – על קצה המזלג

Nodejs מגיעה עם המון מודלים מובנים, אבל בהרבה מהמקרים, אנו נצטרך לעשות שימוש בפריימוורקים על מנת שיענו לנו על צורך מסויים, ובהרבה מהמקים – פשוט להקל עלינו את העבודה מול המודולים המובנים.
בנוסף, שימוש בפרייווארק בדרכ עושה לנו סדר בקוד, ומקל על התחזוקה שלו.

הפופולארית ביותר – Express

התקנה באמצעות NPM

npm install express

קוד דוגמה ליצירת שרת באקספרס:

const express = require('express');
const myServer = express();

myServer.get('/', (req, res) => {
 res.send('Welcome to our website');
});

myServer.get('/products', (req, res) => {
 res.send('Products list');
});

myServer.listen(4153, () => {
 console.log('Hello from express server');
});

כאשר משתמש יכנס לכתובת הראשית של הURL שלנו, הוא יקבל בדפדפן את ההודעה
Welcome to our website.
כאשר הגולש ייכנס לכתובת /products הוא יקבל בדפדפן את ההודעה Products list.

לאתר אקספרס – https://expressjs.com/


פריימוארקים פופולאריים נוספים:
1. KOAJS – לאתר ליחצו כאן
2. Sails – דומה מאוד לRails, היא MVC, עם אפשרויות מיוחדות לREST API's. ליחצו כאן לאתר
3. Meteor – עולם ומלואו, לאתר ליחצו כאן
4. וכמובן האהובה עלי ביותר: NEST, לאתר ליחצו כאן

שימוש בNodemon בסביבות הפיתוח שלנו

NodeMon מאפשר לנו לאתחל את שרת הNode שלנו בכל פעם שמתבצע שינוי בקובץ, יעיל מאוד והכרחי כאשר אנו מפתחים בNode.

על מנת להתקין את nodemon נריץ את הפקודה הבאה:

npm i -g nodemon

נניח והקובץ הראשי שלנו הוא main.js, כדי להריץ אותו באמצעות nodemon נריץ את הפקודה הבאה:

nodemon main.js

וזאת במקום הפקודה הרגילה שהיא:

node main.js

ועכשיו בכל פעם שנשנה את הקובץ main.js שלנו – nodemon יפעיל את עצמו מחדש ואנו נוכל לראות את השינויים שעשינו מיידית ללא צורך לאתחל מחדש את השרת.

יצירת שרת HTTP בNodeJS באמצעות הAPI http – על קצה המזלג

http – הוא בילטאין בNodeJS, לכן, לא נדרשת התקנה של שום ספרייה. בExpress למשל אנו נצטרך כמובן להתקין אותה באמצעות NPM.

//השורה הבאה מחזירה את ה
// API של 
// http
const myHttpAPI = require('http');
const myRequestListener = (req, res) => {
 res.end('Hello from NodeJS !!!');
}

//הפקודה הבאה יוצרת את השרת שלנו למעשה, אך לא מפעילה אותו
const myServer = myHttpAPI.createServer(myRequestListener);

//נאזין לקריאות לשרת שיצרנו מקודם בפורט 80 או כל פורט אחר שנבחר
myServer.listen(80, () => {
 console.log('YAY! The server is ready!');
});

זוכרים שדיברנו על Events?
בכל פעם שמתקבלת בקשה – Request לשרת שלנו, משודר למעשה EVENT בשם 'request'.
הפונקציה myRequestListener שלנו בעצם "מאזינה" לEvent 'request' ומופעלת בכל פעם שמגיע אלינו request חדש.

חשוב להדגיש שהפונקציה myServer.listen היא פונקציה אסינכרונית, דהיינו, הקוד שבתוך פונקצית הcallback שלה יופעל רק כאשר הmyServer שלנו יהיה מוכן.


מצאתם טעות? הערות? שאלות? הסתדרתם? נתקעתם? כתבו לי בתגובות!

קלסטרים בNodeJS – הקדמה.

בסביבות פרודקשין, חשוב לעשות שימוש בNode Clusters, לדוגמה אם למעבד שלנו יש כמה ליבות – כל Cluster יעשה שימוש בליבה וכך נוכל לנצל את מלוא היכולת של המעבד שלנו. אגב, גם אם למעבד לשנו יש רק ליבה אחת – עדיין כדאי לעשות שימוש בCluster כיון שיש לו יכולת לנטר את הפרוסס הקיים ולהתחיל אחד חדש כאשר הקיים נכשל/נסגר.

ניתן להריץ קלסטרים של נוד באמצעות מודל הCluster שהוא בילט-אין בNode. לחילופין ניתן ליצור קלסטר בשימוש בכלים כמו PM2 שיכול לעשות שימוש בכל הליבות של השרת שלנו בצורה אוטומטית (עם Load Balancer מובנה) ולהפעיל מחדש את האפלקציה שלנו ללא Downtime.

גישה נוספת כמובן לשימוש בקלסטר היא שימוש בקוברנטיס – בשימוש בקוברנטיס אף אחד לא יכול להבטיח לנו שכאשר נבקש תהליך חדש – הוא ירוץ על ליבה נפרדת, בPM2 לעומת זאת – כאשר אנו מבקשים תהליך חדש – הוא ירוץ על ליבה נוספת.

"אירועים" – EventEmitter בNodeJS על קצה המזלג

EventEmitter הוא מודל בילט אין בNodeJS, הוא מאפשר "האזנה" לאירועים ו"שידור" שאירוע קיים קרה לכל ה"מאזינים".

לדוגמה, נוכל להאזין לאירוע בשם "NEW_USER_REGISTER" שישודר בכל פעם שמשתמש חדש נירשם למערכת שלנו, במקביל נוכל "להאזין" לאירוע מכל מקום שנירצה. בכל פעם שהאירוע יתקיים – נוכל להריץ פונקציות בצורה אוטומטית.

const EventEmitter = require('events');
const ourEmitter = new EventEmitter();

//נאזין לאירוע NEW_USER_REGISTER
//בכל פעם שהאירוע ישודר, הפונקציה הנל תרוץ
ourEmitter.on('NEW_USER_REGISTER'), () => {
 console.log('משתמש חדש נירשם למערכת');
}

//ניתן להאזין לאירוע ביותר ממקום אחד
ourEmitter.on('NEW_USER_REGISTER'), () => {
 console.log('שלח אימייל ברוך הבא');
}

//על מנת לשדר אירוע נריץ את הפקודה הבאה
ourEmitter.emit('NEW_USER_REGISTER');

הEventEmitter עוזר כאשר אנו צריכים לעדכן מודלים אחרים שאירוע מסויים קרה והוא שימושי מאוד, חשוב להכיר אותו!

שימו לב, אם האירוע "ישודר" לפני שאנו נאזין לו – מה שמאזין לו לא יידע שהאירוע קרה, לכן – קודם כל צריך להאזין ורק לאחר מכן לשדר.

תבניות אסינכרוניות בNodeJS – על קצה המזלג

תבניות אסינכרוניות בNodeJS,
SyncPattern, Async Error CallBack, The Promise Pattern – Async-Await

Sync Pattern

דומה לאיך שאנו כותבים בPHP למשל – קוד סינכרוני. כל פקודה מתבצעת בזו אחר זו. לדוגמה:

const fs = require('fs');
let fileName = 'logs.txt';
const fileData = fs.readFileSync(fileName);

console.log('File content is', fileData);

console.log('Sync Test');

בקוד הנל, אנו קוראים את כל התוכן של הקובץ, לאחר שקראנו אותו אנו מדפיסים את התוכן שלו. ולאחר מכן מדפיסים 'Sync Test'. בקוד הסינכרוני הנל קודם יודפס תוכן הקובץ ורק לאחר מכן יודפס 'Sync Test'. חשוב להדגיש: הקוד הנל לא עובר דרך הEvent Loop של NodeJS.


Async Error CallBack

בדוגמה הבאה, נכתוב קוד אסינכרוני בNodeJS בגישת CallBack. הקוד הנל יעבור דרך הEvent Loop ויתבצע בצורה אסינכרונית.

const fs = require('fs');

let fileName = 'logs.txt';
fs.readFile(fileName, function cback(err,data) {
 console.log('File content is', data);
});

console.log('Async Test');

בקוד הנל, אנו משתמשים במתודה readFile שהיא מתודה אסינכרונית אשר עושה שימוש בEventLoop.
אנו לא נוכל לגשת לתוכן הקובץ ישירות אחרי קריאה לפונקציה הזאת – כיוון שהוא עדיין לא זמין, וזאת הסיבה שאנו צריכים להשתמש בפונקצית CallBack – במקרה שלנו cback שמקבלת 2 פרמטרים. הפרמטר הראשון err (לשגיאות) והפרמטר השני data – התוכן של הקובץ.

לאחר שקריאת הקובץ תסתיים, מתבצעת קריאה לפונקצית הCallBack שלנו ורק בה יש לנו גישה לתוכן של הקובץ (Event Loop)

  1. קריאת הקובץ
  2. הדפסת Async Test
  3. הדפסת תוכן הקובץ על ידי פונקציית הCallBack
    כאשר למעשה הEvent Loop רץ רק פעמיים, בפעם הראשונה קריאת הקובץ + הדפסת הAsync Test ובפעם השנייה הרצת פונקצית הCallBack שלנו.

    שימו לב, שכיון שמדובר בכתיבה אסינכרונית, שורת הAsync Test שלנו תופיע לפני שורות התוכן של הקובץ שלנו.

חשוב לשים לב שפונקצית הCallBack שלנו תמיד תקבל בפרמטר הראשון את משתנה הerr, אם תחול שגיאה בקוד פרמטר הerr שלנו יהיה אוביקט מסוג error ובמידה ולא תתרחש שגיאה הערך של הפרמטר err יהיה null.

החיסרון העיקרי בשימוש בכתיבה עם callbacks הוא הצורך לשרשר את הפונקציות שלנו, מה שקרוי "עץ אשוח" – דבר הגורם לקוד לא קריא וקשה לתיחזוק.
אם נירצה לדוגמה גם לקרוא מהקובץ וגם לכתוב לקובץ אחר בשימוש בתוכן הקובץ הראשון נקבל קוד בסיגנון הבא:

const fs = require('fs');

let fileName = 'logs.txt';
let newFileName = 'newlog.txt';
fs.readFile(fileName, function cback(err,data) {
 fs.writeFile(newFileName, data, function cback2(err) {
  //yada yada
 });
});

console.log('Async Test');

שימו לב, איך 2 הפונקציות הנל משורשרות, עכשיו דמיינו לכם קוד אסיכרוני כזה שעושה 5 ויותר פעולות..
פיתרון לבעיתיות הזו נמצא בתבנית אסינכרונית אחרת בשם Promise Pattern


The Promise Pattern, Async – Await

Nodejs מגיע עם כלי שמאפשר לעשות "להמיר" לpromise כל פונקציה אסינכרונית שהיא בילט אין.

const fs = require('fs');
const util = require('util');

const readLogFile = util.promisify('fs.readFile');

async function readLogFile(fileName) {
 const logFileData = await readFile(fileName);
 console.log('Log Data is', logFileData);
}

readLogFile('logs.txt');

console.log('Promise Async Test');

בדוגמה זו אנו משתמשים בpromisify כדי להשתמש בפונקציה readFile (שהיא פונקציה אסינכרונית בילט אין) – בצורה שמחזירה Promise. אנו יכולים להמיר כל פונקציה אסינכרונית שכתובה בצורת CallBack לצורה שמחזירה Promise.
הערת אגב: למודל FS יש כבר פונקציות Promise מובנות, להלן:

const readFile = util.promisify(fs.readFile);

בדוגמה הקודמת כמובן לא השתמשנו בהן ועשינו במקום זאת שימוש בutil.promisify.

ראו את הדוגמה הבאה בה אנו גם קוראים מקובץ וגם כותבים לקובץ עם Promise

const fs = require('fs').promises;

let fileName = 'logs.txt';
let newFileName = 'newlog.txt';

async function copyLog(filename, newFileName) {
 await logFileData = await fs.readFile(filename);
 await fs.writeFile(newFileName, logFileData);
}

copyLog('logs.txt','logs-copy.txt');
console.log('Async Test');

נכון שהקוד הרבה יותר ברור מהקוד הבא?

const fs = require('fs');

let fileName = 'logs.txt';
let newFileName = 'newlog.txt';
fs.readFile(fileName, function cback(err,data) {
 fs.writeFile(newFileName, data, function cback2(err) {
  //yada yada
 });
});

console.log('Async Test');


לסיכום: לטעמי האישי, Promises הן הרבה יותר נוחות לתחזוקה וגם לכתיבה מאשר CallBacks.


מצאתם טעות? הערות? שאלות? הסתדרתם? נתקעתם? כתבו לי בתגובות!