השרת שלנו סובל מ"אמנזיה" — כל הנתונים נעלמים ברגע שמפעילים אותו מחדש. בשיעור זה נחבר חדר אחסון קבוע ונלמד את שתי הפילוסופיות המרכזיות: SQL הנוקשה ו-NoSQL הגמישה.
✓נושאי השיעור
למה צריך מסד נתונים? RAM מול Disk — נדיף לעומת קבוע
SQL — מודל רלציוני עם טבלאות וSchema נוקשה
NoSQL / MongoDB — מסמכי JSON גמישים ללא Schema
CRUD — 4 פעולות היסוד: Create, Read, Update, Delete
Mongoose — ספרייה שמוסיפה Schema ל-MongoDB
חיבור שרת Express למסד הנתונים — פעולת Create ב-POST
אבטחה: Mass Assignment ו-Schema כחומת הגנה
נושא טכני
שלב במיון
ביטוי במבחן
SQL vs NoSQL
ראיונות מקצועיים
איזה מסד לבחור לכל סוג מערכת?
פעולות CRUD
השאלון המקוון
קריאת שאילתות, JOINs, Aggregations
DB Injections
מבחני C ו-CTF
איתור וניצול SQL/NoSQL Injection
// פרק 1
למה צריך מסד נתונים?
השרת שומר נתונים ב-RAM — זיכרון זמני שנמחק בכל Restart. מסד נתונים שומר על הדיסק הקשיח לצמיתות, ועושה זאת בצורה שמאפשרת שליפה מהירה.
RAM (זיכרון שרת)
Database (דיסק)
נדיף — נמחק בכל Restart
קבוע — שורד כיבויים והפעלות מחדש
גישה מהירה מאוד
גישה מהירה עם Indexes
קיבולת מוגבלת
קיבולת כמעט ללא הגבלה
אין שאילתות מובנות
שפות שאילתה (SQL, MongoDB Query)
💡 Index — כמו תוכן עניינים בספר
מסד נתונים יוצר אינדקסים כדי למצוא נתונים בלי לסרוק את כל הקובץ. חיפוש משתמש מתוך מיליון רשומות לוקח חלקיקי שנייה — לא דקות.
✏תרגול עצמי2 שאלות
שאלה 1.1
מדוע לא ניתן לשמור נתונים לצמיתות במשתנים ב-Node.js?
✓ תשובה: ב) — RAM הוא זיכרון נדיף (Volatile). ברגע שהשרת נסגר, כל הנתונים אובדים. מסד נתונים שומר על הדיסק (Disk) שממשיך להחזיק את המידע.
שאלה 1.2
מדוע מסד נתונים יכול לחפש משתמש מתוך מיליון רשומות תוך חלקיק שנייה?
✓ תשובה: ד) — Index הוא כמו תוכן עניינים בספר. במקום לקרוא את כל הספר כדי למצוא פרק, אתה הולך ישירות לעמוד. Index מאפשר למסד לדלג ישירות לרשומה הנכונה.
// פרק 2
SQL מול NoSQL — שתי פילוסופיות
שתי גישות שונות לניהול מידע. SQL — הרואה חשבון הנוקשה. NoSQL — המחסנאי הגמיש. הבחירה ביניהן תלויה בסוג האפליקציה.
האנלוגיה: המסעדה
SQL (הרואה חשבון הנוקשה): דורש טבלאות מושלמות. עמודת "גיל" מקבלת רק מספרים — כתיבת "צעיר" תגרום לשגיאה ודחייה. הטבלאות מקושרות — לא ניתן למחוק לקוח שיש לו הזמנה פתוחה. קפדן, מדויק, מתאים לבנקים.
NoSQL/MongoDB (המחסנאי הגמיש): עובד עם "קולקציות" של מסמכי JSON. בפתק אחד יש גיל, בפתק שני אין גיל אבל יש העדפות אלרגיה. למחסנאי לא אכפת. מתאים לרשתות חברתיות ואפליקציות דינמיות.
קריטריון
SQL
NoSQL (MongoDB)
מודל נתונים
טבלאות (Relations)
מסמכי JSON (Documents)
Schema
נוקשה — חייב להגדיר מראש
גמיש — Schemaless
שאילתות
SQL (SELECT, JOIN, WHERE)
MongoDB Query Language
יתרון
אמינות, עסקאות (Transactions)
גמישות, מהירות פיתוח
מתאים ל
בנקים, ERP, מערכות פיננסיות
רשתות חברתיות, real-time apps
🛡️ שאלת דיון — סייבר הגנתי
למדנו ש-MongoDB מקבל כל אובייקט JSON. אם לקוח ממלא טופס הרשמה ואנחנו שולחים את האובייקט ישירות למסד, מה ימנע מתוקף להוסיף שדה:
התשובה: כלום — ללא הגנה. זהו Mass Assignment Attack. הפתרון הוא Mongoose Schema שמסנן שדות לא מורשים.
✏תרגול עצמי3 שאלות
שאלה 2.1
אתה בונה מערכת בנקאית שמנהלת העברות כספים. איזה מסד נתונים מתאים יותר ולמה?
✓ תשובה: ג) — מערכת בנקאית דורשת Transactions: אם העברה נכשלת באמצע (ניכוי מחשבון א' הצליח אבל הזיכוי לחשבון ב' נכשל), SQL יחזיר הכל למצב הקודם. NoSQL מסורתי לא תמיד ערב לכך.
שאלה 2.2
מה פירוש "Schemaless" ב-MongoDB?
✓ תשובה: ב) — ב-SQL כל שורה בטבלה חייבת לכלול את אותן עמודות. ב-MongoDB מסמך אחד יכול לכלול "גיל" ומסמך שני לא — גמישות מלאה, אבל דורש זהירות מהמפתח.
שאלה 2.3 — סייבר
תוקף שולח בקשת הרשמה עם השדה "isAdmin": true בתוך ה-JSON. מה יקרה אם השרת מעביר את ה-Body ישירות ל-User.create(req.body) ב-MongoDB?
✓ תשובה: ד) — זוהי מתקפת Mass Assignment. ללא Schema, MongoDB מקבל ושומר כל שדה שנשלח. ה-isAdmin: true יישמר ויעניק לתוקף הרשאות מנהל. הפתרון הוא Mongoose Schema.
// פרק 3
CRUD — 4 פעולות היסוד
כל מערכת בעולם מתנקזת ל-4 פעולות בסיסיות. הכרתן היא בסיס לכל עבודה עם מסדי נתונים.
פעולה
תיאור
HTTP Method
דוגמה
Create
יצירת רשומה חדשה
POST
הוספת משתמש חדש
Read
קריאת/שליפת נתון — ללא שינוי
GET
שליפת רשימת משתמשים
Update
עדכון רשומה קיימת
PUT / PATCH
שינוי שם משתמש
Delete
מחיקת רשומה
DELETE
מחיקת חשבון
הרחבה PUT לעומת PATCH — מה ההבדל? ›
PUT — מחליף את כל הרשומה. אם שולחים PUT עם שם בלבד, שאר השדות ייאפסו.
PATCH — מעדכן רק את השדות שנשלחו. שאר השדות נשמרים כמות שהם.
בפרקטיקה: PATCH עדיף לרוב — לא מסכן מחיקת נתונים בטעות.
✏תרגול עצמי2 שאלות
שאלה 3.1
משתמש רוצה לשנות רק את כתובת האימייל שלו. איזו HTTP Method מתאימה ביותר?
✓ תשובה: ד) — PATCH מעדכן רק את השדות שנשלחו. PUT יחליף את כל הרשומה — כל שדה שלא נשלח ייאפס. לעדכון חלקי, PATCH הוא הבחירה הנכונה.
שאלה 3.2
API מחזיר קוד סטאטוס 201. מה המשמעות?
✓ תשובה: א) — 200 OK = הצליח. 201 Created = הצליח ונוצר משאב חדש (בדרך כלל כתשובה ל-POST). 400 = שגיאה בבקשה. 500 = שגיאת שרת.
// פרק 4
Mongoose — Schema כחוזה אבטחה
MongoDB גמיש מדי ועלול להיות מסוכן. Mongoose מוסיף Schema — חוזה מחייב שמסנן כל JSON לפני כניסה למסד. שדה לא מורשה? נמחק אוטומטית.
// 1. ייבוא הספרייה
const mongoose = require('mongoose');
// 2. יצירת Schema — חוזה האבטחה
const userSchema = new mongoose.Schema({
username: { type: String, required: true },
password: { type: String, required: true },
age: { type: Number }, // שדה אופציונלי
isAdmin: { type: Boolean, default: false }, // ברירת מחדל בטוחה!
});
// 3. הפיכת Schema למודל פעיל
const User = mongoose.model('User', userSchema);
🔑מה הגנה הזו עושה
שדות שלא מוגדרים ב-Schema נמחקים אוטומטית — isAdmin: true של תוקף? נעלם.
required: true — MongoDB יסרב לשמור רשומה ללא שדה זה
default: false — גם אם הלקוח לא שלח את השדה, ערך ברירת המחדל הבטוח נשמר
חיבור שרת Express למסד — פעולת Create
app.post('/api/users', async (req, res) => {
try {
// המודל מסנן דרך ה-Schema ושומר — פעולה אסינכרונית!
const newUser = await User.create(req.body);
// מחזיר את המשתמש שנוצר (כולל ה-_id שמסד הנתונים הוסיף)
res.status(201).json(newUser);
} catch (error) {
// Schema דחה את הנתונים (למשל, חסרה סיסמה)
res.status(400).json({ error: error.message });
}
});
! באג נפוץ — שכחת await ›
// ❌ קוד פגום — שולח Promise ריק, לא נתונים אמיתיים
app.get('/api/users', (req, res) => {
const allUsers = User.find({}); // חסר await!
res.json(allUsers); // שולח Promise object
});
// ✅ קוד תקין
app.get('/api/users', async (req, res) => {
const allUsers = await User.find({});
res.json(allUsers); // שולח מידע אמיתי
});
הסיבה: פעולות מסד נתונים הן אסינכרוניות — לוקח זמן לקרוא מהדיסק. ללא await, JavaScript ממשיך לשורה הבאה לפני שהנתונים הגיעו.
✏תרגול עצמי3 שאלות
שאלה 4.1
תוקף שולח { username: "hack", password: "123", isAdmin: true } לשרת עם Mongoose Schema. מה יקרה?
✓ תשובה: א) — Mongoose מסנן שדות שלא בSchema. ה-isAdmin שנשלח מהלקוח יימחק, וה-isAdmin שיישמר יהיה false (ברירת המחדל שהגדרנו). זו הגנת Trust Boundary.
שאלה 4.2
מדוע פעולות מסד הנתונים (כמו User.find({})) דורשות async/await?
✓ תשובה: ב) — I/O (קריאה מדיסק, רשת) הוא אסינכרוני ב-Node.js. ללא await, הקוד ממשיך לשורה הבאה לפני שהנתונים הגיעו, ומחזיר Promise ריק במקום נתונים אמיתיים.
שאלה 4.3
מה ה-_id שמסד הנתונים מוסיף לכל רשומה?
✓ תשובה: ג) — MongoDB יוצר ObjectId ייחודי לכל מסמך אוטומטית. הוא מבוסס על זמן + מזהה מכונה + מספר רנדומלי, ומבטיח ייחודיות גם בסביבה מבוזרת.
// תרגול נוסף
תרגול נוסף — SQL: JOINs, Injection ו-Aggregation
שאלות העשרה נוספות לחיזוק החומר של השיעור — באותו פורמט בחירה של הקורס.
✏תרגול נוסף3 שאלות
שאלה 5.1 [קל]
מה ההבדל בין INNER JOIN ל-LEFT JOIN?
✓ תשובה נכונה: א — INNER = חיתוך. LEFT = כל השמאלית + התאמות (NULL כשאין). RIGHT ו-FULL משלימים. ב-Debug: LEFT חושף שורות 'יתומות'.
שאלה 5.2 [בינוני]
מה תוציא השאילתה כאשר user_input = "admin'--"?query = f"SELECT * FROM users WHERE name='{user_input}' AND active=1"
✓ תשובה נכונה: ב — admin'-- → WHERE name='admin'-- AND active=1. ה--' הופך את השאר להערה. תוצאה: כל שורות admin, גם לא-פעילות. Login Bypass קלאסי.
שאלה 5.3 [קשה]
מה ה-OUTPUT של SELECT COUNT(*), AVG(age) FROM users WHERE age BETWEEN 18 AND 30 על [(Alice,25),(Bob,17),(Carol,28),(Dave,30)]?
✓ תשובה נכונה: ד — BETWEEN 18 AND 30 כולל קצוות. Bob(17) בחוץ. Alice(25)+Carol(28)+Dave(30) = 3 שורות. AVG = 83/3 ≈ 27.67.
// אתגר מעשי
אתגר מעשי — מסדי נתונים — Schema ו-SQL בטוח
שיא השיעור: לבנות רכיב עובד עם זווית אבטחה. ההגשה נבדקת אוטומטית.
T2 · בסיסי250 נק'~18 דק'build
ולידציית Schema (סגנון Mongoose)
ממש validate(schema, doc) המחזיר רשימה ממוינת של שגיאות. schema ממפה שדה→{'type':'str'|'int'|'bool', 'required':bool}. עבור כל שדה: שדה חובה חסר → "missing:FIELD"; טיפוס שגוי → "type:FIELD". שדה במסמך שאינו ב-schema → "extra:FIELD" (הגנה מפני mass-assignment). שים לב: True/False אינם int תקין.
def validate(schema: dict, doc: dict) -> list:
errors = []; types = {'str': str, 'int': int, 'bool': bool}
for f, rule in schema.items():
if f not in doc:
if rule.get('required'): errors.append(f"missing:{f}")
else:
t = types[rule['type']]; val = doc[f]
if t is int and isinstance(val, bool): errors.append(f"type:{f}")
elif not isinstance(val, t): errors.append(f"type:{f}")
for f in doc:
if f not in schema: errors.append(f"extra:{f}") # מונע mass-assignment
return sorted(errors)
T3 · בינוני400 נק'~25 דק'build
בניית שאילתה בטוחה (נגד SQL Injection)
ממש build_query(table, filters) המחזיר (sql, params) לשאילתת SELECT מפורמטרת. ערכי המשתמש חייבים להפוך ל-placeholders (?) ולעולם לא לשרשור למחרוזת — כך מונעים SQL injection. עמודות ממוינות לפי א"ב. מזהי טבלה/עמודה (שאי אפשר לפרמטר) חייבים לעבור ולידציה מול ^[A-Za-z_]\w*$; מזהה פסול → זרוק ValueError. ללא פילטרים → "SELECT * FROM table".
דוגמאות
build_query('users', {'id':5})→('SELECT * FROM users WHERE id = ?', [5])
build_query('users', {'name':'a','age':2})→('SELECT * FROM users WHERE age = ? AND name = ?', [2, 'a'])
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס בניית שאילתה בטוחה (נגד SQL Injection) ›
import re
def build_query(table: str, filters: dict):
ident = re.compile(r'^[A-Za-z_]\w*$')
if not ident.match(table): raise ValueError("bad table") # מזהים לא ניתן לפרמטר
cols = sorted(filters)
for c in cols:
if not ident.match(c): raise ValueError("bad column")
if not cols:
return (f"SELECT * FROM {table}", [])
where = " AND ".join(f"{c} = ?" for c in cols) # ערכים = placeholders
return (f"SELECT * FROM {table} WHERE {where}", [filters[c] for c in cols])
// סיכום
סיכום — שיעור 20
ציידנו את המערכת שלנו בזיכרון לטווח ארוך. המערכת כעת Full Stack אמיתית — קולטת נתונים, מאמתת אותם ושומרת אותם לצמיתות.
✓מה למדנו בשיעור 20
RAM מול Disk — נתונים במשתנים נמחקים; מסד נתונים שומר לצמיתות
SQL — טבלאות, Schema נוקשה, Transactions. מתאים לבנקים ו-ERP
NoSQL/MongoDB — מסמכי JSON גמישים. מתאים לרשתות חברתיות
ב. Mass Assignment — המשתמש יכול לשלוח role: "admin"
ג. חסר סטטוס 201
ד. ה-JSON לא מפוענח
תשובה: ב
🎯 דגש למבחן — Mass Assignment: לעולם אל תעבירו את req.body כמו שהוא למסד. תוקף יוסיף שדות כמו isAdmin: true או role: "admin" וישיג הרשאות. תמיד בוחרים שדות מותרים במפורש (whitelist): User.create({ email: req.body.email, name: req.body.name }).
שאלה 8 · מה מטרת האינדקס?
CREATE INDEX idx_email ON users(email);
א. לדחוס את הטבלה
ב. להאיץ חיפוש על העמודה, במחיר כתיבה איטית יותר
ג. למנוע כפילויות
ד. להצפין את העמודה
תשובה: ב
אינדקס מונע סריקה מלאה של הטבלה, אך מתעדכן בכל INSERT/UPDATE.
שאלה 9 · מה מבטיחה טרנזקציה?
א. שהשאילתה תרוץ מהר
ב. שכל הפעולות מתבצעות במלואן או שאף אחת לא מתבצעת
ג. שהנתונים יגובו
ד. שהחיבור יישאר פתוח
תשובה: ב
אטומיות: אם שלב נכשל מבצעים ROLLBACK — קריטי בהעברת כסף בין חשבונות.