express.js
داکیومنت مرتبط با فریموورک express.js در اینجا آورده شده
Start Server
برای شروع سرور یک instance از express.js میسازیم و کل پروژه رو روی اون بنا میکنیم
const express = require("express");
const PORT = 8000;
const app = express();
app.listen(PORT, () => console.log(`Server listening on port ${PORT}`));
در این حالت سرور بالا میآید و حال باید route handling انجام دهیم زیرا به طور پیشفرض express فرض میکند که مسیری وجود ندارد
Route Handling
برای route handling در express باید از اینستنس app استفاده کرد به صورتی که به فرمت زیر است :
app.methoad("url", (req, res) => {
// ....
res.status(...).send(...);
});
به صورتی که methoad یکی از متد های معتبر http مثل get , post , put , ... است
تابع send به طور خودکار نوع Content-Type دیتای ارسالی رو تشخیص میدهد
اما معمولا برای ارسال دیتای json از app.json استفاده میشود
Routes
برای مثال چند نمونه از route handling هارو داریم
GET
app.get("/users", (req, res) => {
res.json([
{
id: 1,
name: "Amirhossein",
username: "am_abazari",
},
{
id: 2,
name: "Ali",
username: "alim",
},
]);
});
POST
app.post("/users", (req, res) => {
res.status(201).json({
message: "user created successfully",
});
});
PUT
app.put("/users", (req, res) => {
res.json({
message: "users updated successfully",
});
});
DELTE
app.delete("/users", (req, res) => {
res.json({
message: "users delete successfully",
});
});
regex
ما توی مسیردهی میتونیم از regex هم استفاده بکنیم برای مثال
app.get(/[a-z0-9\.\_]@[a-z]{2,6}\.[a-z]{2,10}/, (req, res) => {
res.send(req.url);
});
اگر status به صورت دیفالت ثبت نشود مقدار ۲۰۰ درنظر گرفته میشود
این یک فرمت email هست. یعنی باید متد get به مسیری به شکل زیر باشه
http://localhost:8000/amirhoss@gmail.com
Params
برای مثال اگر مسیر /api/products/123/ مقدار 123 متغیر هست پس باید از لاجیک params استفاده کرد
باید در مسیردهی از عبارت :name استفاده کرد و مقدار آنرا میتوان از const {name} = req.params دریافت کرد
app.get("/api/products/:id", (req, res) => {
const { id } = req.params;
if (!Number(id))
res.status(404).json({ status: 404, message: "product not found" });
res.json({
id: id,
message: `product ${id} returned`,
});
});
میتونیم چند dynamic route داشته باشیم و مقداری رو از داخل params بگیریم
app.get("/api/products/:id/lists/:listID", (req, res) => {
const { id, listID } = req.params;
if (!Number(id))
res.status(404).json({ status: 404, message: "product not found" });
res.json({
id: id,
message: `product ${id} returned`,
listID: listID,
});
});
Query
گاهی وقتا نیاز هست به یک مسیر query پاس بدیم
یعنی برای مثال آدرسی به فرمت زیر داشته باشیم : api/products/delete?id=12
برای گرفتن مقدار id چون متغیر هست باید از req.query استفاده کرد
app.get("/api/products", (req, res) => {
const queries = req.query;
res.json({
message: "success",
queries: queries,
});
});
حال اگر برای مثال وارد مسیر api/products?age=salam&id=12 مقدار خروجی به صورت زیر خواهد بود :
{
"message": "success",
"queries": {
"age": "salam",
"id": "12"
}
}
Middlewares
تعریف
میدلور ماژولی هست که قبل از اینکه درخواست کاربر رو به متد مدنظر و یا میدلور بعدی پاس بدهد ابتدا وارد آن میشود
request -> middleware1 -> middleware2 -> ... -> methoad handler
یعنی ابتدا درخواست دریافت میشود. تک تک وارد تمام میدلور ها میشود و درصورتی که تمام میدلور هارا پاس کند وارد متد مدنظر با url متناظرش میشود اما ممکن است از یکی از این میدلور ها با موفقیت عبور نکند و درجا همانجا به کاربر پاسخ داده شود.
استفاده
برای مثال فرض کن ما قصد داریم کاربرانی که اهل اسرائیل هستن رو راه ندیم. در این حالت یک میدلور ست میکنیم که ip کاربر رو چک بکنه و درصورت تایید به میدلور بعدی بره و دغیراینصورت ارور ۴۰۳ بده
پیاده سازی
ما با استفاده از تابع use میتونیم یک میدلور تعریف بکنیم
میدلور ها به ترتیب تعریف طی میشوند و با صدا زدن تابع next به میدلور بعدی میروند
app.use((req, res, next) => {
// ... checkes or converts
next();
});
در یک میدلور باید یا next صدا زده شود که به میدلور بعدی برود یا res صدا زده شود که پاسخ کاربر مستقیم داده میشود
بدیهی است که بعد از صدا زدن تابع next عبارت های بعد آن خوانده نمیشود و مستقیم به میدلور بعدی میرود
افزودن مقادیری به درخواست
ما با استفاده از میدلور ها میتونیم مقادیر دلخواهی به درخواست اضافه کنیم یا درخواست رو بررسی کنیم که valid هست یا نه
app.use((req, res, next) => {
const time = Date.now();
req.time = time;
next();
});
حال توی هر درخواست با استفاده از req.time میتونیم به مقدار مدنظر زمان دسترسی داشته باشیم
body parser
برای دریافت دیتای ارسالی کاربر باید از میدلوری به اسم body parser استفاده کرد. که به صورت پیشفرض در express وجود دارد
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
این دو خط کد رو به عنوان اولین میدلرور ها قرار میدهیم که هر درخواست کاربر body اون رو پارس بکنه و قابل مشاهده باشه
morgan
یک پکیجی هست که به عنوان میدلور استفاده میشود و نشان میدهد هر درخواست از کجا اومده و به کجا میره و جزییاتش چیه
app.use(morgan("dev"));
انواع مختلف داره مانند dev, tiny, ... و حتی قابل کاستوم کردن هست
custom middleware
بعضی وقتا میخوایم بعضی میدلور ها فقط برای بعضی از route ها اعمال بشه که در این حالت باید مستقیما میدلور مدنظر رو به route مدنظر پاس بدیم به فرمت زیر :
app.get("url", middleware1, middleware2, ..., middlewareN, (req, res) => {
// ....
});
برای مثال داریم :
const setTime = (req, res, next) => {
const time = Date.now();
req.time = time;
next();
};
app.get("/api/products", setTime, (req, res) => {
res.json({
message: "success",
time: req.time,
});
});
درکل عبارت app.methoad("url",...") به جای ... میتونه هرتعدادی تابع دریافت کنه که آخرین تابع باید مقدار res رو داشته باشه.
Not Found
اگر api وجود نداشته باشد بهتر هست که به صورت دستی ۴۰۴ بدیم و از دیفالت اکسپرس استفاده نکنیم.
برای اینکار باید در انتهای تعریف تمام route ها یک میدلور به صورت زیر تعریف کنیم :
app.use((req, res) => {
res.status(404).json({
status: res.statusCode,
message: "api not found",
});
});
Error Handling
درصورتی که در یک بخش اروری رخ دهد به صورت پیشفرض اکسپرس ارور را نمایش میدهد اما ظاهر بد و غیرخوانا دارد که برای اینکار ما آنرا به صورت دستی تغییر میدهیم
در آخر آخر آخر پس از میدلور 404 و not found یک میدلور به صورت زیر اضافه میکنیم
میتونیم از try catch هم برای ارور هندلینگ بهتر استفاده کنیم درکنارش
app.use((err, req, res, next) => {
res.json({
status: err.statusCode || 500,
message: err.message || "Internal server error",
});
});
یعنی تعریف تمام route ها و middleware ها باید به صورت زیر باشد :
middlware 1
middlware 2
...
middleware N
route 1
route 2
...
route M
middleware 404
middleware error
درکل ابتدا تمام میدلور ها تعریف میشوند سپس تمام مسیر ها و درنهایت صفحات ۴۰۴ و سپس ارور ها هندل میشوند
مثال :
// middlewares
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan("dev"));
// routes
app.get("/api/products", (req, res) => {
res.status(200).json({
message: "success",
time: Date.now(),
});
});
app.post("/api/products", (req, res) => {
res.status(200).json({
message: "success",
});
});
// 404 not found
app.use((req, res) => {
res.status(404).json({
status: res.statusCode,
message: "api not found",
});
});
// error handli ng
app.use((err, req, res, next) => {
res.json({
status: err.statusCode || 500,
message: err.message || "Internal server error",
});
});
Router
در بخشهای قبل در رابطه با Routing صحبت کردیم. در اینجا در رابطه با روش بهتر یعنی استفاده از ماژول Router خود express حرف میزنیم
Start
-
در کل هدف مدیریت تمام مسیرها هست برای اینکار ما یک دایرکتوری در مسیر اصلی پروژه به نام
routesدرست میکنیم. -
سپس به ازای هر مسیر یک فایل درست میکنیم. برای مثال ما داریم
/auth/login/و یا/auth/register/یک فایل برای مسیرauthدرست میکنیم. -
درصورتی که برای مثال مسیر
authسنگین شود برای ان یک دایرکتوری درست میکنیم و برای هر مسیر داخل آن یک فایل -
سپس هر مسیر را با استفاده از ماژول
Routerدرون express.js تعریف میکنیم -
درنهایت یک فایل مرجع به اسم
routes.jsمیسازیم و تمامی مسیر هارا داخل آن قرار میدهیم. -
درنهایت از این فایل
routes.jsبه عنوان یک میدلور درون فایل اصلی برنامه یعنیserver.jsاستفاده میکنیم که شامل تمام مسیر ها هست
مثال :
فرض کن فولدر بندی زیر رو داشته باشیم

ظroutes.js وظیفهی تجمیع تمام مسیر هارا دارد و از آن درون server.js استفاده میکنیم
Implementation
const { Router } = require("express");
const router = Router();
router.get("/login", (req, res, next) => {
res.send("auth login get");
});
router.post("/register", (req, res, next) => {
res.send("auth register post");
});
router.delete("/:id", (req, res, next) => {
res.send(`auth delete ${req.params.id}`);
});
module.exports = router;
const { Router } = require("express");
const authRouter = require("./auth.router");
const router = Router();
router.use("/auth", authRouter);
module.exports = router;
const allRoutes = require("./routes/routers");
app.use(allRoutes);
در این روش ابتدا اگر ما به مسیر /auth/login/ درخواست بدیم مقدار auth login get کال میشود.
ما میتونیم بقیه route هارو هم به همین طریق تعریف بکنیم و اضافه بکنیمشون
const { Router } = require("express");
const authRouter = require("./auth.router");
const userRouter = require("./user.router");
const dashboardRouter = require("./dashboard.router");
const router = Router();
router.use("/auth", authRouter);
router.use("/user", userRouter);
router.use("/dashboard", dashboardRouter);
module.exports = router;
Middleware For Router
در اینجا بررسی میکنیم که چطور میتونیم میدلور برای router ها تعریف و استفاده بکنیم.
درکل توی یک درخواست چه با router هندلش بکنیم و چه با خود express فرمت به اینصورت هست که
app.get("url", middleware1, middleware2, ...middlewareN);
app.use("url", middleware1, middleware2, ...middlewareN);
router.get("url", middleware1, middleware2, ...middlewareN);
router.use("url", middleware1, middleware2, ...middlewareN);
یعنی تمامی تابع ها بعد url یک میدلور هستند که دونه به دونه توشون next صدا زده میشه و باید یکیشون جواب رو برگردونه پس فرقی نمیکنه که از app.get استفاده کنیم یا از router.get یا از app.use یا router.use
از هرکدوم که استفاده کنیم میتونیم میدلور داخلش ست بکنیم
میدلور برای تمام API ها
اگر یک میدلور برای تمامی api ها بخوایم باید جایی که allRoutes رو تعریف کردیم ست کنیم یعنی :
const allRoutes = require("./routes/routers");
const setTimeMiddleware = (req, res, next) => {
req.time = Date.now();
next();
};
app.use(setTimeMiddleware, allRoutes);
میدلور برای یک دسته از API ها
اگر مثلا بخوایم یک میدلور برای تمام API های زیرمجموعهی /auth/ اجرا بشه باید اونو به روتر مدنظر اضافه کرد
const setTimeMiddleware = (req, res, next) => {
req.time = Date.now();
next();
};
router.use("/auth", setTimeMiddleware, authRouter);
میدلور برای یک API خاص
اگر بخوایم فقط یک API یک میدلور رو داشته باشه هم باید به روتر مربوطه اضافه بکنیمش
const setTimeMiddleware = (req, res, next) => {
req.time = Date.now();
next();
};
router.get("/login", setTimeMiddleware, (req, res, next) => {
console.log(req.time);
res.send("auth login get");
});
Other Way
یک روش دیگه برای ست کردن میدلور استفاده از router.use(middleware1,...middlewareN) هست
const setTimeMiddleware = (req, res, next) => {
req.time = Date.now();
next();
};
router.use(setTimeMiddleware, middleware1, ... , middlewareN);
router.use("/auth", authRouter);
router.use("/user", userRouter);
router.use("/dashboard", dashboardRouter);
Controller
بهتر هست ما همواره بخشهای مستقل از هم رو جدا کنیم. برای مثال middleware از router از controller از model و ... از هم همیشه جدا باشن
در اینصورت خوانایی کد بسیار بالا میرود

اگر ساختار فولدر بندی بالا رو داشته باشیم :
const loginController = (req, res, next) => {
res.send("auth login get");
};
const registerController = (req, res, next) => {
res.send("auth register post");
};
const deleteController = (req, res, next) => {
res.send(`auth delete ${req.params.id}`);
};
module.exports = {
loginController,
registerController,
deleteController,
};
const { Router } = require("express");
const {
loginController,
deleteController,
registerController,
} = require("../controllers/auth.controller");
const router = Router();
router.get("/login", loginController);
router.post("/register", registerController);
router.delete("/:id", deleteController);
module.exports = router;
Cookies
Start
برای استفاده از کوکی ها باید اونارو پارس کنیم و ما از پکیج cookie-parser استفاده میکنیم
npm install cookie-parser
و اونو به عنوان میدلور قرار میدهیم :
const cookieParser = require("cookie-parser");
app.use(cookieParser());
Get Cookies
برای دسترسی به کوکی های کاربر از req.cookies استفاده میکنیم
app.get("/get-cookies", (req, res, next) => {
const cookies = req.cookies;
res.json(cookies);
});
Set Cookies
برای ست کردن کوکی از تابع res.cookie(key,value) استفاده میکنیم
app.get("/set-cookies", (req, res, next) => {
res.cookie("key", "value");
res.send("successfully added cookie");
});
Remove Cookies
برای حذف کوکی از مرورگر باید از تابع res.clearCookie استفاده کرد
app.get("/set-cookies", (req, res, next) => {
res.clearCookie("key");
res.send("successfully remoeved cookie");
});
Cookie Options
برخی آپشنها برای کوکیها داریم. برای مثال تا کی وجود داشته باشه و کی از بین بره و ...
maxAge
سن کوکی رو نشون میده و میگه چقدر دیگه وجود داشته باشه به میلیثانیه
app.get("/set-cookies", (req, res, next) => {
res.cookie("key", "value", {
maxAge: 5000, // 5sec
});
res.send("successfully added cookies");
});
بعد ۵ ثانیه کوکی از بین میرود
expires
تاریخ از بین رفتن کوکی رو میگیره که باید به فرمت new Date(date) باشه. یعنی همراه با تاریخ روز ساعت و ...
app.get("/set-cookies", (req, res, next) => {
res.cookie("key", "value", {
expires: new Date(Date.now() + 5000), // after 5 sec
});
res.send("successfully added cookies");
});
بعد ۵ ثانیه کوکی از بین میرود
httpOnly
کوکی httpOnly به این منظور هست که کوکی مدنظر رو امنیتش رو بالاتر میبره.
مفهومش اینه که تنها کوکی از طریق درخواست http قابل دسترس هست و برای مثال کتابخونههایی که توی فرانتاند از اونا استفاده کردیم دسترسی به این کوکیها ندارد
app.get("/set-cookies", (req, res, next) => {
res.cookie("key", "value", {
httpOnly: true,
});
res.send("successfully added cookies");
});
signed
در این متند مقدار value کوکیها توسط یک private key که به cookie-parser میدهیم هش میشوند و در مرورگر کاربر به صورت هش شده ذخیره میشوند
برای دریافت کوکیهای هش شده به جای req.cookies باید از req.signedCookies استفاده کرد
app.use(cookieParser("40!331msdkkdsanf")); // custom private key
app.get("/get-cookies", (req, res, next) => {
const signedCookies = req.signedCookies;
res.json(signedCookies);
});
app.get("/set-cookies", (req, res, next) => {
res.cookie("key", "value", {
signed: true,
});
res.send("successfully added cookies");
});
مقدار private-key که توی توی قسمت cookieParser قرار میدیم باید یک کلید هش هشده باشه ترجیها
secure
این آپشن در کوکیها امنیت کوکی هارو بالاتر میبره و دسترسی بهشون رو محدود تر میکنه
app.get("/set-cookies", (req, res, next) => {
res.cookie("key", "value", {
secure: true,
});
res.send("successfully added cookies");
});
sameSite
این آپشن سه حالت داره
-
lax: حالت پیشفرض کن کوکی هست و به معنی سست(ریلکس) هستش -
strict: حالت سختگیرانه هست و با ریدارکت از یک صفحه سایت مثلا به درگاه پرداخت کوکی ها ارسال نمیشن و غیرقابل دسترس هست -
none: برای مرورگر های قدیمی هست که کوکیها همواره دردسترس هستن
همواره sameSite رو با secure استفاده کن. اگر samSite برابر strict شد secure رو هم برابر true بزار و اگر برابر lax یا none شد secure رو هم برابر false بزار
app.get("/set-cookies", (req, res, next) => {
res.cookie("key", "value", {
secure: true, // false
sameSite: "strict", // lax - none
});
res.send("successfully added cookies");
});