25.9.16

Express.js - creating simple site (hebrew)

איך יוצרים אתר פשוט עם express.js


Express הוא מודול של nodeJS שמאפשר להרים בקלות שרות ווב שמספק שרות REST או אתר.
אומנם קל להגדיר אבל לא פשוט בהתחלה להבין מה להגדיר...

בואו נקים אתר שמאפשר לנהל בצורה בסיסית רשימת משימות לביצוע. אפשר יהיה לראות את המשימות, להוסיף משימות ולסמן משימות שבוצעו.

קישור לקוד בשלמותו כקובץ זיפ



סוגי הקבצים בתיקייה

כדי לכתוב קוד ל nodejs שמממש את האתר הזה אז כדאי לדעת שלאתר כזה יש קבצים לצד ה client וקבצים לצד ה server.  כולם יושבים בתתי תיקיות בתוך התיקייה הראשית שנותנים ל nodejs. זה קצת מקשה על ההבנה איזה קבצים מגיעים לאן ...

קבצים עבור צד ה client כוללים דפי HTML סטטיים, קבצי JS של ספריות כמו jQuery ו bootstrap (לעיצוב), קבצי עיצוב (CSS) וקבצי JS של האפליקציה עצמה שרלבנטים לצד ה client.

קבצים עבור צד ה server כוללים קוד JS ל nodejs, ספריות של nodejs, קבצים נתונים לפלאג-ים השונים שנשתמש בהם עבור express ואולי עוד דברים.


מה האתר מספק?


אנחנו רוצים לכתוב קוד ל nodejs כך שכאשר הוא ירוץ, יהיה לנו שרת ווב שמבצע את הדברים הבאים:

  • כאשר פונים ל '/' אז יחזור דף HTML שיש בו כותרת "Home Page"
  • כאשר פונים ל 'tasks/' אז מקבים בחזרה דף HTML ובו:
    • רשימת המשימות שהוכנסו עד כה. על כל משימה שבוצעה יהיה קו (זה הדבר שיסמן שהיא בוצעה). כל משימה שלא בוצעה תהיה קישור שכאשר לוחצים עליו, המשימה תסומן שבוצעה.
    • שדה קלט + checkbox שיאפשר להכניס משימה חדשה (ולסמן האם בוצעה כבר). הטופס הזה ישתמש ב REST request שמתואר בנקודה הבאה.
  • כאשר שולחים פקודת POST לכתובת 'taskCompleted/' (פקודת REST) אז צריך לשלוח יחד עם הפקודה json ובו שדה title ושדה completed. 

בנייה בשלבים

את האתר נבנה בשלבים:
  • שלב ראשון: נריץ את nodejs ברמה הכי בסיסית כדי לראות שהוא עובד כשרת ווב תקין.
  • שלב שני: נכניס את מנגנון ה route של express. המנגנון הזה מאפשר לנו להגדיר די בפשטות איזה קוד להריץ עבור כל שורת URL.
  • שלב שלישי: נכניס את מנגנון ה view של express. המנגנון הזה מאפשר לנו להשתמש בתבניות HTML כדי להחזיר דף HTML ל client. נשתמש בפורמט jade כדי להגדיר את ה HTML template. זה הרבה יותר נוח מאפשר לבנות HTML בצורה פשטנית.
  • שלב רביעי: נכניס שתי ספריות JS שימושיות - jquery ו bootstrap.
  • נוסיף התייחסות לטופס (form) שמאפשר להוסיף משימה חדשה.

שלב ראשון – הרמת אתר שיש לו דף ראשי אחד

app.js
======
var express = require('express');
var path = require('path');
var app = express();

app.use('/', function (req, res, next) {
     res.send('Site homepage');
});

app.js הוא הקובץ הראשי של התוכנית ואותו נריץ ע"י node.

פקודת ה use אומרת ל express להריץ את הפונקציה על כל בקשה שה URL בה הוא ‘/’. הפונקציה יכולה לבצע משהו ואז או להחזיר תשובה לבקשה ע"י res.send או לקרוא לעוד פונקציות טיפול שאולי מוגדרות ע"י קריאה ל next().

עכשיו (לאחר הרצת node), נקבל את ההודעה Site homepage כאשר נגלוש לאתר.

שלב שני – הכנסת מנגנון route מקצועי

אפליקציית express מכילה בדר"כ הרבה דפים ואז יש הרבה routes (ניתוב לפונקציות שונות ע"פ מה שרשום ב URL). Express מספק מנגנון מרשים יותר לטפל בניתובים כאלה – router.

tasksRoutes.js
==============
var express = require('express');
var bodyParser = require('body-parser'); // parse json body
var cookieParser = require('cookie-parser'); // parse cookies

var router = express.Router();

// URL is relative to route base URL
// GET request (to handle POST request, write router.post)
router.get('/', function(req, res, next) {
     res.send('Tasks main page');
    
     // can return JSON response
     //res.render('index', { title: 'Express' });
});

module.exports = router;


app.js
======
var express = require('express');
var path = require('path');
var tasksRoutes = require('./routes/tasksRoutes');
var app = express();

app.use('/', function (req, res, next) {
     res.send('Site homepage');
});

app.use('/tasks', tasksRoutes);

את כל פונקציות הטיפול נרשום מעכשיו ב tasksRoutes. זו תהיה חלוקה מודולרית של הקוד.

ה router נותן לנו גישה נוחה לחלקים פרמטריים מה URL:

router.get(‘/task/:name/:id?’, function (req,res,next) {
     var name = req.params.name;
     var id = req.params.id;
     var user = req.query.user;
     …
}
כאן, express, בונה לבד את האובייקט req.params ושם בו את הפרמטרים משורת ה URL. הוא גם בונה את req.query ורושם שם את ה query parameters. סימן השאלה ליד id אומר כי השדה הזה הוא אופציונלי.

שלב שלישי – שימוש ב views כדי להחזיר HTML בצורה יותר משוכללת

כאשר המשתמש מבקש דפים שונים אז רוצים להחזיר HTML. אפשר להחזיר ע"י res.send אבל זה מאוד מייגע לבנות HTML בצורה כזו.

לשם כך, express נותן מנגנון של HTML templates שבעזרתם בונים את התשובה. המנגנון הזה נקרא views.

כאשר משתמשים במנגנון הזה, אפשר להגדיר views שונים שלכל אחד יש שם. כל view מגדיר בצורה כלשהו פיסת HTML שאפשר לדחוף לתוכה מידע. כאשר השרת רוצה להחזיר HTML אז יכול להגיד שהוא מחזיר view בשם מסויים ע"י res.render(view name).

כדי להשתמש במנגנון כזה, צריך להגיד ל express את סוג השיטה לתאור ה HTML ואיפה יושבים הקבצים של ה views השונים:

app.js
======
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

השיטה השכיחה ב express לתאר view היא jade. בשיטה זו, במקום HTML רושמים את התגים ללא סימני ‘<’ או '>'.

layout.jade
===========
doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content
    
    
index.jade
==========
extends layout

block content
  h1= title
  p Welcome to #{title}

 
error.jade
==========
extends layout

block content
  h1= message
  h2= error.status
  pre #{error.stack}
 
כמה דברים לדעת על פורמט Jade:

-        אפשר לשים תגית block בקובץ. זה אומר שאפשר למלא את הבלוק מקובץ Jade אחר.
-        בקובץ האחר רושמים שהוא מרחיב את הקובץ המקורי ע"י extends

 ועכשיו כאשר רוצים להחזיר HTML אפשר להשתמש ב HTML template:

tasks.jade
==========
extends layout
block content
     ul.list-group
           each task in tasks
                li.list-item
                      li.list-group-item
                      if task.completed
                                      s(id=task.id || inx , data-completed=’true’) = task.title
                              else
                                      a(id=task.id || inx , data-completed=’false’) = task.title
    

tasksRoute.js
=============
var router = require(‘express’).Router();

router.get(‘/’, function(req, res) {
     res.render(‘tasks’);
});

module.exports=router;


שלב רביעי – נשתמש ב jquery ו bootstrap

כדי שהיישום יראה יותר ויהיה יותר קל, נשתמש ב jquery ו bootstrap:
tasks.jade
==========
html
     head
           title This is a Simple Todo List
             link(rel="stylesheet",href="/bower_components/bootstrap/dist/css/bootstrap.min.css")
               link(rel="stylesheet",href="/bower_components/bootstrap/dist/css/bootstrap-theme.min.css")
               script(src="/bower_components/jquery/dist/jquery.min.js")
     body
           div.container
                div.page-Header
                      h1 Express ToDo List
                block content

כדי שהשרת יביא את הקבצים הסטטיים האלה ל client צריך להגיד ל express איפה הקבצים הסטטיים:

app.js
======
app.use(express.statuc(path.join(__dirname,’public’));

זה אומר ל express לחפש קבצים סטטיים בתיקיית public.

שלב חמישי – הוספת טופס להוספת משימות

נוסיף טופס בעזרתו מוסיפים משימות.

נשים את הטופס ב tasks.jade

tasks.jade
==========
extends layout
block content
     ul.list-group
           each task,inx in tasks
                li.list-group-item
                      if task.completed
                              s(id=task._id || inx, data-completed='true')= task.title
                      else
                              a(id=task._id || inx,data-completed='false')= task.title
     div       
       form.form-inline(method="post")
         div.form-group
                 input.form-control(placeholder="Task title",type="text",name="title",required="")
         div.form-group.checkbox
           label
             input(type="checkbox", value="true",name="completed")
      |Completed?
           button.btn.btn-default(type="submit") Submit
     script(src="/javascripts/tasks.js")

ועכשיו נטפל במידע שמגיע מהטופס
tasks.js
=========
router.post("/",function(req,res){
     service.addTask(req.body.title,req.body.completed ? true : false)
     res.render("tasks",{tasks:service.getTasks()});
})



ההקוד המלא: קישור לקוד

20.9.16

elasticsearch for dummies (hebrew) - part 1


elastic-search  הוא סוג של database

הוא שייך למשפחת ה noSQL שזה אומר שהוא שומר את המידע בצורה שאינה מסודרת בטבלאות. 

אפשר להגיד שהייעוד של elastic-search הוא לשמור במהירות רבה כמות רבה מאוד של נתונים ולאפשר חיפוש מאוד מהיר בהם.

API
ישנם מספר דרכים לעבוד עם elastic. הדרך הפשוטה והשכיחה היא ע"י שליחת הודעות ב .REST הדרך הבאה השכיחה היא ע"י ספרייה שמספקת API לפעולות העיקריות. קיימת ספרייה לכל שפה עיקרית. 

כאשר מחפשים דוגמאות לפקודות בגוגל אז בדר"כ מוצאים את הפקודה המתאימה ב REST 

דוגמא:



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

מעשית, בדר"כ נגדיר מראש את המבנה של המסמכים השונים וכיצד יש לאנדקס כל סוג של מסמך (מה שנקרא "מיפוי" - mapping)

אינדקס
כאשר מכניסים "מסמך" ל elastic יש לציין לאיזה "אינדקס" להכניס אותו. אפשר להתייחס ל "אינדקס" כאל "סכימה" ב database רלציוני. 

הפעולה של הכנסת מסמך לאינדקס נקראית "אינדקוס". האינדקס של elastic הוא מה שמוכר כאינדקס של ספר לימוד - החלק בסוף הספר בו יש רשימה ארוכה של מילים (מסודרים לפי האלף בית) ואז מספרי עמוד בו המילים מופיעות.  אינדקס כזה נקרא inverted index 

מה ש elastic עושה כאשר מוכנס מסמך הוא ניתוח של המסמך והוצאה ממנו של כל "המילים"בשפה שלו ואז הוספה שלהם לאינדקס. כך החיפוש אח"כ נעשה כל כך מהר - פשוט מחפשים באינדקס את המילה המתאימה ואז מגיעים מאוד מהר למסמכים הרלבנטים. אין צורך לעבור על המסמכים עצמם בחיפוש.

דוגמא:



חיפוש
אח"כ אפשר לחפש מסמכים באינקס נתון שעונים על תנאי מסויים. מה שיפה פה, שאפשר לציין תנאי רגיל כמו בעולם של relational database ואז מסמך או עונה לתנאי או לא עונה לתנאי. תנאי כזה נקרא "פילטר"

אפשר להגדיר תנאי "גמיש" ואז elastic יתן דירוג לכל מסמך שמתאר את מידת ההתאמה שלו לתנאי. זה כבר מזכיר את החיפוש בגוגל שמציג קודם את הדפים שלהם יש rank יותר גבוה בהתאמה לחיפוש. תנאי כזה נקרא "שאילתא" (query)

חשוב לציין כי כל instance של elastic יכול להכיל מספר אינדקסים ולא רק אחד. זה מאוד דומה ל relational database  שבו ייתכנו מספר סכימות ב instance יחיד.

סיכום
לסיכום חלק זה - כדי לעבוד עם elastic צריך:

  • להתקין אותו על מחשב או על מספר מחשבים (cluster)
  • להגדיר בו אינדקס אחד או יותר. 
  • להכניס לאינדקס את המסמכים (לאנדקס מסמכים)
  • ואז אפשר אח"כ לחפש באינדקס את המסמכים. אפשר לחפש בצורה "מדוייקת" או בצורה "רכה" יותר כמו בגוגל.