מדריך | יצירת שרת אקספרס
-
מדריך זה מניח שאתם כבר יודעים איך עובדים עם NodeJS, בקרוב אעלה אי"ה מדריך מסכם על אופן העבודה המיטבי עם NodeJS.
במדריך זה ניצור שרת אקספרס עם כל הרכיבים הנדרשים והמומלצים.
יצירה ועבודה עם app.get
-
ניצור לפרויקט שלנו תיקייה בשם myExpress או כל שם אחר שתרצו.
-
נריץ בטרמינל את הפקודה
npm init --y
ע"מ ליצור קובץ package.json (הארגומנט --y מורה לו לייצר קובץ כאילו עניתי yes לכל השאלות כך שזה מייצר קובץ ברירת מחדל).
במידה ונרצה לעבוד בשיטת ES6, נוסיף ל-package.json את הערך הזה:"type": "module",
וכמובן שנצטרך להתנהל עם ה-import וכל החוקים של ES6. כאן לא אכסה את זה עם ES6 כי לא הצלחתי לשלב קטעי קוד יחד.
- נתקין את המודול אקספרס עם הפקודה:
npm i express
a3. אופציונלי, במידה ולא תרצו לרענן את השרת לאחר כל שינוי שתעשו. תתקינו את זה:
npm i nodemon
. ואז ב-package.json תכניסו בערך של המפתח scripts את הערך הזה:"start": "nodemon app.js"
להרצה עם console דפדפני גם ל-node הכניסו את הדגל --inspect ככה:
"start": "nodemon --inspect app.js"
-
ניצור קובץ ראשי בשם app.js.
-
נדביק את הקוד שמופיע בדוקומנטציה של המודול עם אי אילו שינויים:
const express = require("express"); const app = express(); const port = 3000; app.listen(port, () => { console.log(`Example app listening on port ${port}!`); console.log(`click with ctrl key: => http://localhost:${port}/`); }); app.get("/", (req, res) => { res.send("Hello World"); });
- תוכלו כמובן להכניס כל מה שתרצו בשורה [11] של
res.send
, לדוגמא תוכלו להכניס את השורות הבאות במקום שורה [10] והילך:
app.get('/', (req, res) => { fs.createReadStream(path.join(__dirname, './index.html')).pipe(res); });
וכך בעצם המשתמש יופנה לדף html ממשי, ולא רק יקבל טקסט, כמובן שזה דורש ייבוא של המודולים (בשורות הראשונות)
fs
ו-path
ככה:const fs = require("fs"); const path = require("path");
בהמשך נראה כיצד נוכל לחסוך זאת.
- נבדוק האם אכן השרת רץ ע"י הפקודה בטרמינל
node app.js
(אוnpm start
במידה ויישמנו את סעיף a3). ואז נכנס לכתובת:
http://localhost:3000/ , אם נראה את הכיתוב Hello World או את קובץ ה-html במידה והגדרנו, השרת שלנו עובד.
-
-
עבודה עם app.use
כעת כשיש לנו שרת ביד, נתחיל לשכלל אותו, ניצור הוראות ל-middleware על-ידי המתודה
()app.use
, וכדלהלן:לוגים
- ניצור middleware ראשון לשרת שהוא יהיה בעצם אחראי ליצירת לוגים:
- ניצור תיקייה בשם src ובתוכו ניצור עוד תיקייה בשם middlewares.
- בתוכו ניצור קובץ בשם logger.js.
- נכניס שם את הקוד הבא:
const myLogger = (req, res, next) => { console.log("Someone entered to the site", Date.now()); next(); }; module.exports = myLogger;
- בקובץ ה-app.js הראשי שלנו נוסיף למעלה:
const logger = require('./src/middlewares/logger');
- ואז נוסיף לפני ה-app.use הראשון שלנו את השורה הבאה:
app.use(logger);
- נעבור שניה על התהליך, ברעיון ניתן להדביק את הקוד ששמנו בקובץ logger.js ישירות בקובץ app.js, הקוד הזה בעצם מעביר את התעבורה דרכו, עושה מה שביקשנו ממנו, ומעביר את התעבורה הלאה, אבל למען הסדר הטוב, עשינו זאת עם תיקיות וקבצים מסודרים.
נוכל כמובן לכתוב דברים אחרים שנרצה שהקוד יבצע. - לצורך השלמה, נציג כאן צורה נוספת בו ניתן להשתמש עם ה-app.use כדי לתפוס תעבורה ולעשות לוגים איתם.
- נתקין מודול בשם morgan ע"י הפקודה
npm i morgan
. - נקרא למודול ע"י כתיבה בשורות הראשונות של הקובץ app.js:
const morgan = require('morgan')
- ואז בשורה הראשונה של ה-app.use נכתוב:
app.use(morgan('dev'))
זה בעצם יציג לנו מידע בקונסול באיזה נתיב ובאיזו שיטה נכנסו כרגע לאתר ועוד.
- כמובן שנוכל לכתוב את ה-app.use הזה בכל קובץ שנרצה גם בקובץ הראוטר למשל (אלא שאז נשתמש עם router.use), ואם נרצה לבצע לוגים רק בראוטר מסויים נוכל להדביק למשל בקובץ ה-users.js שיצרנו את הקריאה ל-logger.js ואז נשתמש עם זה כך:
router.use(logger);
כשנכתוב
router
במקום app כאמור.דף לא נמצא
- מכירים את השגיאה 404 הדף לא נמצא? אז אקספרס נותן לנו איזו שורה קטנטנה לזה, כך שבמקום שנגיע לדף מעצבן של "לא ניתן לגשת לאתר הזה" מגיעים לפחות לטקסט שאומר שהוא לא מצא את הנתיב הרצוי.
-
אם נרצה לשדרג את המידע שהלקוח יקבל בעת שגיאת 404, נוסיף middleware שמעביר את כל התעבורה של הרשת דרכו, ובמידה והשרת מגיע לשגיאת 404 נכניס בתגובה משהו יותר מעניין, בדוגמא שלפנינו נשלח למשתמש קובץ json עם השגיאה, כמובן שנוכל לשלוח לו קובץ html או כל מה שעולה בדעתנו.
-
נוסיף בתיקיית ה-middlewares קובץ בשם NotFoundHandler.js, ושם נכתוב:
class NotFoundHandler { pageNotFound(req, res, next) { const error = new Error(`the page is Not Found`); error.status = 404; next(error); } serverNotFound(error, req, res, next) { res.status(error.status || 500); res.json({ error: { message: error.message, }, }); } } module.exports = new NotFoundHandler();
- ובקובץ app הראשי נקרא ל-middleware למעלה:
const NotFoundHandler = require("./src/middlewares/NotFoundHandler.js");
- ואז בסוף הקוד, אחרי כל השורות נכתוב:
app.use(NotFoundHandler.pageNotFound); app.use(NotFoundHandler.serverNotFound);
נכתוב את זה בסוף מהסיבה הפשוטה שרק במידה והוא לא מצא בנתיבים ובראוטרים למעלה את הנתיב שהלקוח ביקש להגיע אליהם, הוא יעביר את התעבורה דרך ה-middleware הזה, שבעצם לא תעשה כלום מלבד שליחת שגיאת דף לא נמצא, או שרת לא נמצא.
תיקף נכתוב עוד שימושים ל-app.use כמו הפניות לראוטרים, לתיקיות סטטיות, ושיטות תעבורה ועוד.
-
סדר בקוד
כמו כל מתכנת מסודר, נרצה גם אנו ליצור קוד מסודר כך שבמידה ונרצה לתקן או לשדרג משהו בקוד, נדע תמיד היכן אנו אמורים לחפש את השורות המתאימות.
אם נכניס את כל נתיבי ה-URL של המשתמש שאנו רוצים שהשרת ייתן להם מענה בדף הראשי של app.js. נקבל קוד ארוך, מסורבל ולא קריא בעליל, לשם כך אקספרס נותן לנו מענה מצוין לניהול הקוד שלנו, בצורה תקינה ומסודרת.
אז השלב הראשון זה ראוטינג, היינו קבצים שאמורים לטפל בבקשות מהלקוח, בסלש אחרי כתובת האתר:ראוטינג
- ניצור תיקייה לראוטינג, כך שנוכל לטפל בצורה מסודרת בפניות של ה-URL שיגיעו לשרת.
- כדי לעשות זאת ניצור בתיקייה הראשית תיקייה בשם routes.
- בתוכו ניצור קובץ שיטפל לצורך הדוגמא במשתמשים, כך שבמידה ונכתוב http://localhost:3000/users נגיע בעצם לקובץ הזה, ומשם יתנהל המשך הטיפול בבקשה, ולכן ניצור קובץ בשם
users.js
.
בתוך הקובץ נדביק את הקוד הזה (שימו לב שכרגע נתיב/
הוא הנתיב users ללא סימון סלש אחריו):
const express = require("express"); const router = express.Router(); router.get("/", (req, res) => { res.send("all users"); }); router.get("/login", (req, res) => { res.send("Login page"); }); module.exports = router;
כשההבדל המרכזי כאן שמשתמשים עם
()express.Router
, ובסוף מייצאים את הראוטר.- כדי לקרוא לראוטר הזה, נחזור לקובץ app.js שלנו, נקרא לראוטר הזה ע"י הפקודה הידועה לקריאת מודולים וקבצים:
const users = require('./routes/users');
ונכתוב את ה-app.use עם שם המשתנה שקבענו לו ב-require כך:
app.use('/users', users);
את ה-app.use נכתוב תמיד לפני שורות של app.get כך שהשרת ייתקל בשורות אלו מיד, ויעביר את התעבורה לפי המופיע שם.
- כעת נבדוק האם השינויים אכן בוצעו בהצלחה, ניכנס ל-http://localhost:3000/users, ונבדוק האם קיבלנו תגובה אותה אנו מצפים לראות, ואז ננווט ל-http://localhost:3000/users/login ונראה האם אכן הראוטר עובד כמו שצריך.
controllers
- כשנתחיל לכתוב קוד ממשי, נשים לב שהארגומנט (הפרמטר) השני שמועבר למתודת get (שמכיל הוא עצמו 2 ארגומנטים נוספים
req
ו-res
), מתארך מעבר ל"מצופה בקוד מסודר". זה יכיל כנראה שורות קוד עם מניפולציות, חישובים, בדיקות של req ושליחת res מחושבים, זה יכול להתארך להמון שורות קוד, ואז קובץ הראוטר נהיה בלתי קריא, ומה הועילו חכמים בתקנתם.
-
בדיוק לשם כך ניצור תיקייה בתיקייה הראשית של הפרויקט בשם controllers, שם בעצם יהיו ויאוחסנו כל קטעי הקוד שאנו רוצים שיועברו כארגומנט שני למתודת get.
-
בתוך התיקייה ניצור קובץ בשם זהה (מומלץ, לא חובה כמובן) לראוטר שלנו, במקרה הזה, ניצור בתיקיית controllers קובץ בשם users.js, ובתוכו נכתוב את זה:
module.exports = { add: (req, res) => { res.send("add user"); }, }
אם נשים לב נראה שהערך של המפתח add שיצרנו מכיל את אותו ארגומנט שהיינו צריכים לשלוח במתודת get.
- כעת נעבור בחזרה לקובץ users.js שבתיקיית routes, שם נכתוב בשורות הראשונות:
const { add } = require("../../controllers/users");
- ואח"כ בשורות שמטפלות בבקשת הלקוח נכתוב:
router.get("/add", add);
היינו שבמידה והלקוח יכתוב /users/add, הוא בעצם יעבור לקוד שכתבנו ב-controllers, הפלא ופלא.
נוכל לעשות את הייצוא והייבוא בדרך שונה כמובן, לדוגמא עם קלאס. נכתוב בקובץ users.js שב-controllers את הקוד ככה:class Users { static add(req, res) { res.send("add user"); } } module.exports = new Users;
ובקובץ users.js בתיקיית routs נכתוב את זה:
const Users = require("../../controllers/users"); router.get("/add", Users.add);
תיקייה סטטית לדפי ה-html
- בסעיף 5, כתבנו שניתן לפנות לדף html בשרת, ולהציג אותה בצד לקוח במידה והמשתמש מזין את ה-url הרצוי. הבעיה בשימוש עם זה, זה קריאה לעוד מודולים (fs) בכל קובץ ראוטר שיש לנו בשרת, וגם השורה הארוכה בשליחה ל-res.
-
במקום כל זה ניצור תיקייה חדשה בשם public.
-
ניצור קובץ בשם index.html שהוא בעצם הקובץ הדיפולטי שיופיע בצד הלקוח במידה ולא הוכנס שום נתיב ב-url.
בקובץ app.js הראשי שלנו נכניס את השורה הבאה הקוראת למודול path למעלה:
const path = require("path");
ובהמשך הקוד נכתוב:
app.use(express.static(path.join(__dirname, "public")));
- כעת, כל נתיב שהמשתמש יכניס ב-url, השרת יחפש בתיקייה public האם קיים קובץ בשם זה, ובמידה וכן, זה יוצג במלוא הדרו למשתמש.
כמובן שלא מומלץ לשים את זה בשורות הראשונות של app.js, מכיוון שזה יגביל את הנתיבים שלנו ותמיד יציג את התוכן של התיקייה הסטטית, נוכל לכתוב את השורה הזאת בסוף, כך שרק במידה ולא יהיה התאמה של הכתובת לשום נתיב עד כה, יתחיל התיקייה הסטטית להיכנס לפעולה.
-
קבלת נתונים מהמשתמש-צד לקוח
- כעת ניגש לחלק היותר מעניין בתעבורה בין צד-לקוח צד-שרת, והוא קבלת נתונים מהמשתמש ויצירת הקשר בין הלקוח לשרת.
- בכל שרת, נרצה גם שהלקוח יוכל גם לעדכן פרטים בשרת, ולא רק לקרוא מידע מהשרת.
- לדוגמא בעת שמשתמש נרשם לאתר, או רוצה לבצע עדכון לפרופיל המשתמש שלו, ואפי' כשהמשתמש רוצה לבצע חיפוש במסדי הנתונים של השרת וכד'.
- ניתן לעשות תקשורת זו עם כמה שיטות כשהשיטות הנפוצות הן
get
,post
,put
,delete
:
GET
- מתודת-שיטת
get
היינו העברת המידע שהתקבל מהלקוח ישירות דרך ה-url למעלה בדפדפן, לדוגמא:
http://localhost:3000/users/david
ואז בשרת נקבל את המידע הזה כך:
req.params.someData
וזה מעביר אותנו לתכונה של url דינאמי:
url דינאמי
- ניצור url דינאמי, כך שכל משתמש שיכנס לאתר, יופנה לדף הייחודי שלו, וכך לא נצטרך ליצור ידנית ראוטר עבור כל משתמש ומשתמש.
- נעבור לקובץ users.js שיצרנו מקודם בתיקייה src/routes. ונכניס שם את השורות הבאות:
router.get("/:anyName/:anyId", (req, res) => { res.send( `User ${req.params.anyName} entered the system. The ID is ${req.params.anyId}` ); });
מה שיגרום לכך, שאם מישהו יכנס לנתיב
22/users/david
לדוגמא, יקבל תשובה שתציג את שמו ואת ה-ID שלו על המסך.-
על-מנת לא להתנגש עם נתיבים שאנו כן רוצים שיהיו קבועים מראש וסטאטיים, מומלץ לעשות קובץ ראוטר מיוחד שמטפל בנתיבים דינאמיים, ולא נכניס את האופציה של הדינאמי מיד בקובץ ה-app הראשי שלנו.
-
דרך נוספת, להכניס את השורות של הנתיב הדינאמי אחרי כל הנתיבים הסטטיים והקבועים שהגדרנו, כך שקודם כל ייבדק האם הכנסנו נתיב שווה לנתיב אותו הגדרנו מראש, ורק במידה ואין התאמה, האתר יעבור לאופציית הנתיב הדינאמית.
-
במידה ונכניס פרמטר נוסף לנתיב (כמו בדוגמא שיש גם שם וגם ID) נצטרך להכניס בכתובת ה-URL בדפדפן את שני הפרמטרים, אם לא, נעבור לדף 404 - לא קיים.
query
- במידה ונרצה לקבל את פרטים שנוספו ב-URL אחרי סימן שאלה
?
לדוגמא:
http://localhost:3000/users?admin=1
נשתמש עם המתודה הזאת:
req.query.admin
ונקבל את התוצאה
1
.POST
- מתודת-שיטת
post
היא שיטה בה שולחים את הנתונים והמידע של הלקוח, בקוד js בצד הלקוח ע"י פונקציית fetch וכד' כך:
fetch("http://localhost:3000/", { method: "POST", body: JSON.stringify({ data: "sampledata" }), }) .then((response) => response.text()) .then((data) => console.log(data));
במידה ונשלח את זה, אנו אמורים לקבל איזה מידע בחזרה ע"י השרת.
- אבל כל זה לא יקרה במידה ולא נגדיר בשרת ראוטר האזנה למתודות מסוג post.
- ע"מ להגדיר בשרת את המתודה הזאת, ואת ההתנהלות של השרת מול המידע שמגיע למשתמש, נשתמש עם
app.post
אוrouter.post
כך:
router.post("/", (req, res) => { res.send("Hello POST!"); });
כאן בעצם יצרנו את האזנה של השרת לבקשות post.
- בתוך שיטת post זו, יש כמה וכמה דרכים לשלוח את הנתונים במפתח body.
- או ע"י row במחרוזת טקסט או json.
- או ע"י urlencoded.
- או ע"י שיטות פחות נפוצות כמו form-data וכד'.
פרסור ל-json
- במידה ונעביר את המידע ב-row בפורמט json (שזוהי הדרך היותר מקובלת), נצטרך לכתוב בקובץ app.js:
app.use(express.json())
שורות אלו יגדירו שמעכשיו כל התעבורה שלנו, יעבור דרך middleware שיפרסר את התעבורה שלנו בשיטת json - כל התעבורה בין צד לקוח לצד שרת יהיה בשיטת ובפורמט json כך שנוכל לקרוא את המידע המתקבל, ונקבל את המידע כך:
req.body
ואם נרצה רק ערך מסוים נקבל את זה כך:
req.body.someJsonObjectKey
- זה ישפיע גם על שליחת הנתונים שלנו ללקוח, כך שנכתוב מעתה:
res.json({"name":"david"});
במקום:
res.send("hello!");
נוכל כמובן להגדיר שרק בראוטר ספציפי השרת יעבוד בשיטה הזאת, ובכל שאר הנתיבים, השרת יעבוד בצורה הרגילה.
נעשה זאת פשוט ע"י כתיבת השורה router.use בקובץ הרצוי (צריכים להוסיף עוד משהו לכך).urlencoded
- ובמידה ונעביר את המידע ע"י urlencoded, נצטרך להוסיף ב-app.js גם את השורה הזאת:
app.use(express.urlencoded({ extended: true }));
ורק אז נוכל לקבל את הנתונים הללו מהלקוח.
- כדי להבין מתי הכי נוח להשתמש עם כל שיטה, וכן כדי לעבוד בקלות ולדמות קלט ופלט של המשתמש, נעבוד עם התוכנה postman או התוסף ל-VSCode בשם Thunder Client שהוא נותן לנו עשרות סוגי תקשורת בין לקוח לשרת, ובנוסף הוא נותן לנו קוד שנוכל להטמיע בדפדפן בצד הלקוח.
PUT ו-DELETE
עוד 2 שיטות נפוצות הן PUT לעדכון נתונים שעובדת בגדול באותה שיטה של POST, אלא שהיא מתייחסת רק לנתונים חדשים, ו-DELETE למחיקת נתון, שעובדת באותה שיטה של GET אלא שכאן היא לוקחת ID של המשתמש ומוחקת את ה-ID הספציפי.
-
בניית מסד נתונים בפורמט json
- על-מנת להבין את רעיון העבודה בין צד לקוח לצד שרת, נתחיל לבנות מסד נתונים בפורמט json, כך שאם המשתמש יפנה דרך שורת הכתובת או דרך fetch לנתיב ספציפי שנגדיר, הוא יקבל את ה-json שלנו.
- ניצור בתיקיית src/routes קובץ בשם
products.js
ונדביק שם את תוכן הקובץ users.js. - ניצור עוד קובץ בתיקייה src בשם
products.json
, ושם נכניס את json של האתר הזה. - כעת נגדיר בקובץ
products.js
את השורה הבאה שבעצם קוראת ל-products.json
:
const productsjson = require("../products.json"); const myjson = productsjson['products']
ולמטה נכתוב:
router.get("/", (req, res) => { res.json(myjson); });
בקובץ app.js נקרא לראוטר ככה:
const products = require("./src/routes/products"); app.use("/products", products);
מה שיקרה כרגע, שאם המשתמש פונה ל-url בנתיב products/ הוא יקבל קובץ json טהור.
- כרגע נגדיר שאם המשתמש יכניס ב-url מספר ID יוצג לו רק המוצר עם ה-id הייעודי.
- נוסיף בקובץ
products.js
את השורה הבאה:
router.get("/:id", (req, res) => { res.json(myjson.products[req.params.id-1]); });
יצירת דף HTML להצגת המוצרים בצורה טבלאית וידידותית
כדי שנוכל לצפות בתוצאות המתקבלות בצורה נוחה ולא רק כ-json טהור, ננווט לתיקייה הסטטית שעשינו בסעיף 11, וניצור שם קובץ HTML בשם
index.html
שם תכניסו את הקוד הבא:<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <title>Products List</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous"> </head> <body> <div class="container mt-3"> <h2>רשימת מוצרים</h2> <p>להלן רשימת המוצרים שהתקבלו על פי החיפוש שלך:</p> <table class="table table-hover table-bordered"> <thead> <tr id="itemHead"> <!-- כאן מיובא הכותרת ע"י הקוד למטה --> </tr> </thead> <tbody id="itemRow"> <tr> <!-- כאן מיובא תוכן ה-json ע"י הקוד למטה --> </tr> </tbody> </table> </div> <script src="./index copy.js"></script> </body> </html>
ניצור עוד קובץ בשם index.js שם נכניס את הקוד הבא (קוד נחמד כשלעצמו, מבוסס על תכנות מונחה עצמים):
class Product { constructor(json, header, row) { this.rows = document.getElementById(row); this.head = document.getElementById(header); this.json = json; } //יוצר אלמנט ומכניס קלאס וטקסט createElement(name, text, src, className) { const el = document.createElement(name); el.innerText = text ? text : ""; el.src = src ? src : ""; el.classList = className ? className : ""; return el; } // יוצר כותרת לטבלה ע"י הנתונים מה-json createHeader() { let getKeys = this.json[0]; for (let key in getKeys) { if (key !== "images") { // console.log(`key: ${key}, value: ${getKeys[key]}`); let tableHead = this.createElement("th", key); // console.log(tableHead); this.head.append(tableHead); } } } //יוצר שורה לכל אובייקט נפרד createRow() { this.json.forEach((product) => { let row = this.createElement("tr"); //יוצר עמודה לכל איבר באובייקט for (let key in product) { //החרגת עמודת IMAGES if (key !== "images" && key !== "thumbnail") { // קבלת שמות של מפתחות של האובייקט הראשון for (let nameOfKey in this.json[0]) { console.log(nameOfKey); // השארת תא עמודה ריקה במידה ולא מופיע שם ערך if (key === nameOfKey) { // console.log(`key: ${key}, value: ${product[key]}`); let element = this.createElement("td", product[key]); row.append(element); //יוצר ID לכל שורה }} } else if (key === "thumbnail") { let element = this.createElement("img", null, product[key]); element.style.height = "75px"; row.append(element); // console.log(element); } row.id = product.id; //מחבר האלמנטים יחדיו //מכניס העמודות לשורה this.rows.appendChild(row); } // console.log(row); }); } } //יוצר קשר עם השרת וטוען קובץ JSON async function getProductsJson() { const getList = await fetch("http://localhost:3000/products"); return await getList.json(); } //פונה לקבלת JSON ושולח אותו לקלאס async function listOfProducts() { const list = await getProductsJson(); const item = new Product(list, "itemHead", "itemRow"); item.createHeader(); item.createRow(); } listOfProducts();
- כעת, אם תריצו את השרת, תגיעו לקובץ index.html ותוכלו לצפות במסד הנתונים שלכם בצורה נחמדה.
cors
- אם תריצו את הקובץ index.html לא דרך השרת, אלא דרך התוסף live server או פתיחת הקובץ דרך סייר הקבצים במחשב שלכם (כמובן כשהשרת מופעל, כדי לקבל את נתוני מסד הנתונים), תוכלו לראות בקונסול שגיאת אבטחה בשם
CORS policy
כזאת:
Access to fetch at 'http://localhost:3000/products' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
זוהי בעצם אבטחה לשרת שקוד זנודי של הלקוח לא יוכל לדלות מידע חסוי מהשרת, אבל אנחנו כן רוצים שהלקוח יוכל לצפות בתוכן ה-json שלנו ולכן נתקין מודול בשם
cors
, ונוסיף ב-app.js שלנו את השורות הבאות:const cors = require("cors");
ובשורות של ה-app.use נוסיף:
app.use( cors({ origin: "*", }) );
הגדרה זו תתן ללקוח אפשרות לצפות בתכני השרת שנרצה לחשוף, כמובן שבמידה ונרצה שרק מהשרת יוכלו לפנות למסד הנתונים (כמו בהרצה רגילה של השרת ולא דרך live server שמדמה שרת חיצוני), נסיר הגדרה זו, אם תעשקו באבטחת מידע תוכלו ללמוד יותר על ההרשאות של תוסף זה, ולהגביל נתונים אותם לא תרצו בשום אופן לחשוף).
שימוש במתודת post להוספת מידע
מה נעשה במקרה והמשתמש ירצה להוסיף מידע למוצרים שלנו?
נוסיף את השורות הבאות ל-product.js שלנו (שימו לב שלא הדגשתי כאן את השימוש בתיקייה controllers כדי לחסוך בכתיבה כאן, אבל רצוי ומומלץ להכניס את הקטעים הנוגעים לפעולת השרת לאחר קבלת הראוטר, ל-controllers כמו שהוסבר בסעיף 10).router.post("/add", (req, res) => { const newProduct = req.body; newProduct.id = myjson.length + 1; myjson.push(newProduct); fs.writeFile( path.join(__dirname, "../products.json"), JSON.stringify(productsjson), (err, result) => { if (err) console.log("error", err); } ); res.json(myjson[newProduct.id - 1]); });
כרגע אם ניכנס לנתיב הזה
products/add/
ונדביק בקונסול את זה:var raw = JSON.stringify({ "description": "An apple mobile", "title": "apple " }); var requestOptions = { method: 'POST', body: raw, }; fetch("/products/add", requestOptions) .then(response => response.text()) .then(result => console.log(result)) .catch(error => console.log('error', error));
נקבל פלט של האובייקט החדש עם הערך החדש שנוסף.
תוכן ההוספה לא תתעדכן בפועל בקובץ ה-json שלנו, לולי מה שכתבנו בשורה 5 עד 11, מה שעשינו שם זה המרת המערך בחזרה ל-json ואז דריסת הקובץ products.json עם הנתונים החדשים, (מומלץ לבצע בדוק גם גיבוי של הקובץ הישן, במקרה והשרת נופל תוך כדי הדריסה).כמובן שלצורך כך, נצטרך לקרוא למודולים המובנים
fs
ו-path
כדי לעבוד עם הקוד הזה כך:const fs = require("fs"); const path = require("path");