בואו נראה איך לחבר את typescript ואת nodejs express ביחד, נוסיף גם את nodemon לעבודה יותר נוחה.
תחילה נדבר בקצרה על היתרון בלעבוד עם TS לעומת עבודה ב-JS.
TS מחייב אותנו לעבוד יותר נכון ומצמצם שגיאות, אם לא ניתן לכל משתנה סוג ANY כמו ב-JS אז נקבל 3 יתרונות חשובים:
- intellisense
- interfaces
- התראות ושגיאות כבר בזמן הכתיבה כאשר המידע והסוג לא תואמים או חסרים
מידי נראה איך אחרי מספר התקנות ושינויים קטנים, העבודה השוטפת ממשיכה כרגיל אבל עם כל הייתרונות של TS.
לאחר שהתקנו NODEJS על המחשב (במידה שלא אז ניתן לעשות את זה כאן)
תחילה, אנחנו צריכים לעדכן typescript גלובלי על המחשב.
npm i -g typescript // install global
לאחר מכן, ניצור ספריה בה יהיה השרת שלנו. ניתן לה שם ונכנס אליה, במקרה שלי, שם הספריה nodeTs.
בתוך הספריה (C:\nodeTs) נריץ את הפקודה הבאה:
tsc --init // creat tsconfig.json
נכנס ל-tsconfig.json ונפעיל ונשנה את השורת:
"target": "ES6" "rootDir": "./src" "outDir": "./dist" "moduleResolution": "node"
שימו לב שיש שם די הרבה שורות וצריך לחפש את השורות למעלה ולשנות אותם בהתאם.
עכשיו ניצור ספריה נוספת בשם SRC ובתוכה app.ts .
בשלב זה אין עדיין צורך להכנס לתוך ספרית SRC ואפשר להריץ את הפקודות הבאות מתוך ספרית nodeTs
npm init -y // creat package.json npm i express // creat node_modules npm i mongoose // for mongoose module npm i -D typescript ts-node nodemon @types/node @types/express // for dev only
שימו לב ששורה 3 רצה כאשר דגל מינוס D מופעל, זה אומר שהתלויות האלו יעבדו רק במצב פיתוח ולא במצב PROD – כאשר אנחנו בונים את הפרויקט לעליה לאויר.
בקובץ package.json נעשה את השינויים הבאים:
"scripts": {
"start": "node dist/app.js",
"dev": "nodemon src/app.ts",
"build": "tsc -p ."
},עד לכן עשינו את כל השינויים הדרושים.
נמשיך בעוד מספר התקנות לכלים שישמשו אותנו, אבל לא חובה להתקין אותם.
npm i jsonwebtoken --save // creat Tokens npm i -D @types/jsonwebtoken // for typescript in dev only npm i bcrypt // encode passwords npm i -D @types/bcrypt // for typescript in dev only
כמו שאפשר לראות, כל התקנה שקשורה לסוגים (types) אנחנו מתקינים רק במצב פיתוח (DEV), הסיבה הפשוטה היא שלאחר קימפול TS הופך ל-JS ושם אין TYPES ולכן אני לנו צורך שהספריות האלו יעברו קימפול ביחד עם הקוד שלנו.
מהשלב הזה נתחיל את הפיתוח עצמו, לא נעבור על פיתוח מסובך מידיי אלא נראה את השוני בכתיבה עבור כל התהליכים הרגילים שאנחנו כותבים ב-JS.
import express from "express";
const app = express();
const port: number = 5000;
console.clear();
console.log('-----------start-----------');
app.use((req, res, next) => {
res.setHeader(
"Access-Control-Allow-Origin",
"*"
);
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
)
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PATCH, PUT, DELETE, OPTIONS"
)
next();
});
app.listen(port, () => console.log(`ON-LINE ${port}`));זה הבסיס, ברגע שנריץ את הפקודה npm run dev, השרת שלנו יקשיב בפורט 5000.
כמובן שאם ננסה לגלוש ל- http://localhost:5000 , נקבל תשובה של Cannot GET / . הסיבה היא שלא יצרנו עדיין ראוט שיתן מענה לבקשה.
נקודה נוספת, ניתן להחליף את השורת 10 עד 24 ב-CORES.
נתחיל בליצור ספריה בשם APIS ובתוכה קובץ בשם user.interface.ts
export interface user {
firstName:string;
lastName:string;
age?:number;
}ניצור קובץ נוסף בשם user.models.ts
import { Schema, model } from "mongoose";
import { IUser } from "./user.interface";
const userSchema = new Schema<IUser>({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
age: { type: Number }
})
export const User = model<IUser>('User', userSchema);כפי שניתן לראות זה די דומה לכתיבה עם שינויים קטנים, כמו יצירה של סכמה מסוג של IUser על פי האינטרפייס שיצרנו.
נוכל לראות בזמן הכתיבה ויצירת האובייקט של הסכמה שה-intellisense כבר יודע להשלים לנו את המאפיינים החסרים.
בסופו של דבר אנחנו מחצינים את המודל שיצרנו.
שלב הבא הוא הקונטרולר
import { Request, Response } from "express";
import { User } from "./user.models";
export const getUserInfo = async (req: Request, res: Response) => {
User.findOne({ _id: req.params.id }).then(info => {
if (info) {
res.status(200).json({ message: 'Found', reply: info });
} else {
res.status(200).json({ message: 'Not found' });
}
}).catch(err => {
console.error(err);
res.status(500).json({ message: "Failed to execute" });
})
}יצרנו קונטרולר פשוט שמקבל ID , מבצע חיפוש ומחזיר אחת מהאופציות הבאות:
- סטטוס 200 עם אובייקט שמחזיק הודעה ותשובה של הנתונים עצמם.
- סטטוס 200 עם הודעה שלא נמצא, עדיין מדובר בסטטוס 200 בגלל שהפעולה הצליחה, פשוט המידע לא קיים בבסיס הנתונים.
- במידה שהייתה תקלה , בסיס הנתונים לא בשירות או פניה לא תקינה וכו'. חוזר סטטוס 500 עם הודעה של שגיאה.
לרוב עדיף לא להחזיר את השגיאה עצמה לצד הלקוח, זה עלול לחשוף פרטים רגישים ולעזור לפורצים לפרוץ לבסיס הנתונים.
השלב הבא הוא הנתב
זה השלב שאנחנו מחברים את הבקשרה שמגיעה מצד הלקוח לפונקציה שאנחנו רוצים להפעיל
import { Router } from "express";
import { getUserInfo } from "./user.controller";
export const routerUser = Router();
routerUser.get('/user/:id', getUserInfo);תחילה, זימנו את ה-Router
לאחר מכן, את הפונקציה שנמצאת בקונטרולר, במידה שיהיו עוד פונקציות בקונטרולר נמשוך אותם בשורה 2 .
בשורה 4, יצרנו והחצנו משתנה שיכיל את כל הראוטים שניצור ולבסוף נחבר אותו ב-app.ts
בשורה 6 יצרנו ניתוב אחד שהוא GET ומצפה לקבל גם ID, במידה שזה יקרה, תופעל הפונקציה getUserInfo.
במידה שרוצים לבצע פעולות לפני שהפונקציה תופעל, ניתן לשרשר בצורה הבאה:
import { Router } from "express";
import { checkAuth } from "../../middleware/check-auth";
import { fileUpload } from "../../middleware/file-upload";
import { getUserInfo } from "./user.controller";
export const routerUser = Router();
routerUser.get('/user/:id', checkAuth, fileUpload.single('file'), getUserInfo);שורה 8 במקרה שלנו זו רק דוגמה אבל מה שיקרה זה שפונקציה checkAuth ירוץ ראשון, אחרי זה fileUpload ולבסוף getUserInfo
הגענו לחלק הסופי, וזה החיבור ל-app.ts
נעדכן את הקובץ כך:
import express from "express";
import { routerUser } from "./apis/user.routes";
const app = express();
const port: number = 5000;
console.clear();
console.log('-----------start-----------');
app.use((req, res, next) => {
res.setHeader(
"Access-Control-Allow-Origin",
"*"
);
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
)
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PATCH, PUT, DELETE, OPTIONS"
)
next();
});
app.use('/api/users',routerUser);
app.listen(port, () => console.log(`ON-LINE ${port}`));השורה שנוספה היא שורה מספר 27 ושורה 2.
זה כל התהליך.
שימו לב!!! הקריאה לא תעבוד לנו בגלל שאנחנו מנסים בקונטרולר לגשת לבסיס נתונים ולבצע שאילתא, אין לנו בסיס נתונים מחובר ולכן הפונקציה תיכשל, אם תרצו לראות את הפונקציה עובדת, ניתן לוותר על החיפוש וישר להחזיר תשובה של 200 עם מידע בפנים.
זיכרו את החוליות בשרשרת, app קורא ל-router שקורא ל-middleware אם יש, ואז ל-controller שמשתמש ב-model שמוגדר על פי ה-interface.
לכן כאשר אנחנו בונים את השרשרת הזו, אנחנו עובדים מהסוף להתחלה, כך שלכל חוליה תהיה מוכנה החוליה שעליה מסתמכים.
בהצלחה ⛓





