שרת HTTP בלי ספריות

לפני שעוברים ל-Express, כדאי לראות מה קורה מתחת למכסה: Node.js יודע לבנות שרת ווב בעצמו, עם המודול המובנה http. זה עוזר להבין מה Express עושה בשבילכם.

השרת הראשון

import http from 'node:http';

const server = http.createServer((request, response) => {
    response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    response.end('שלום מהשרת!');
});

server.listen(3000, () => {
    console.log('השרת רץ בכתובת http://localhost:3000');
});

שמרו כ-server.js, הריצו node server.js ופתחו את הכתובת בדפדפן. הפונקציה שמועברת ל-createServer רצה על כל בקשה, ומקבלת שני אובייקטים:

  • request - מה הדפדפן שלח: method, url, headers.
  • response - מה שחוזר: קוד סטטוס, headers ותוכן. חובה לסיים ב-response.end(), אחרת הדפדפן מחכה לנצח.

עצרו את השרת עם Ctrl+C. כדי שיעלה מחדש בכל שמירה: node --watch server.js.

ניתוב ידני

אין כאן app.get. כל ההחלטות הן if על הכתובת והשיטה:

import http from 'node:http';

const users = [{ id: 1, name: 'דנה' }, { id: 2, name: 'יוסי' }];

function sendJson(response, status, data) {
    response.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
    response.end(JSON.stringify(data));
}

const server = http.createServer((request, response) => {
    // URL מפרק את הכתובת לחלקים (pathname, searchParams)
    const url = new URL(request.url, `http://${request.headers.host}`);

    if (request.method === 'GET' && url.pathname === '/api/users') {
        return sendJson(response, 200, users);
    }

    const match = url.pathname.match(/^\/api\/users\/(\d+)$/);
    if (request.method === 'GET' && match) {
        const user = users.find((item) => item.id === Number(match[1]));
        return user ? sendJson(response, 200, user) : sendJson(response, 404, { error: 'לא נמצא' });
    }

    sendJson(response, 404, { error: 'אין נתיב כזה' });
});

server.listen(3000);

קריאת גוף הבקשה (POST)

הגוף מגיע בחתיכות (stream), וצריך לאסוף אותו לבד. זה בדיוק מה ש-express.json() עושה בשורה אחת:

async function readJsonBody(request) {
    const chunks = [];
    for await (const chunk of request) {
        chunks.push(chunk);
    }
    const text = Buffer.concat(chunks).toString('utf8');
    return text ? JSON.parse(text) : {};
}

const server = http.createServer(async (request, response) => {
    if (request.method === 'POST' && request.url === '/api/users') {
        try {
            const body = await readJsonBody(request);
            const user = { id: users.length + 1, name: body.name };
            users.push(user);
            return sendJson(response, 201, user);
        } catch (error) {
            return sendJson(response, 400, { error: 'JSON לא תקין' });
        }
    }
    sendJson(response, 404, { error: 'אין נתיב כזה' });
});

קודי סטטוס שכדאי להכיר

קודמשמעות
200הצליח
201נוצר (אחרי POST)
204הצליח, בלי תוכן (אחרי DELETE)
400בקשה לא תקינה
401 / 403לא מחובר / אין הרשאה
404לא נמצא
500שגיאה בשרת

גם לקוח: fetch מתוך Node.js

Node.js כולל fetch מובנה, בדיוק כמו בדפדפן, כך שאפשר גם לפנות לשרתים אחרים:

const response = await fetch('https://api.github.com/repos/nodejs/node');
const repo = await response.json();
console.log(`ל-Node.js יש ${repo.stargazers_count} כוכבים`);

אז למה Express?

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

דוגמאות מלאות להרצה

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. מה חובה לקרוא בסוף כל תשובה בשרת http?

    1. response.send()
    2. return
    3. response.end()
    4. response.close()
    הצגת התשובה

    תשובה ג. בלי end() הדפדפן ממשיך לחכות לתשובה.

  2. למה Express קיים אם אפשר לבנות שרת עם http לבד?

    1. http לא עובד
    2. Express חוסך את הקוד החוזר: ניתוב, גוף בקשה, שגיאות וקבצים סטטיים
    3. Express הוא שפה אחרת
    4. Express מהיר פי 10
    הצגת התשובה

    תשובה ב. Express עוטף את מודול http ונותן לכל צורך שורה אחת.