پرش به مطلب اصلی

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 استفاده میکنیم که شامل تمام مسیر ها هست

مثال :

فرض کن فولدر بندی زیر رو داشته باشیم

Router Image

ظroutes.js وظیفه‌ی تجمیع تمام مسیر هارا دارد و از آن درون server.js استفاده میکنیم

Implementation

routes/auth.router.js
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;
routes/router.js
const { Router } = require("express");
const authRouter = require("./auth.router");

const router = Router();

router.use("/auth", authRouter);

module.exports = router;
server.js
const allRoutes = require("./routes/routers");

app.use(allRoutes);

در این روش ابتدا اگر ما به مسیر /auth/login/ درخواست بدیم مقدار auth login get کال میشود.

ما میتونیم بقیه route هارو هم به همین طریق تعریف بکنیم و اضافه بکنیمشون

routes/routers.js
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 رو تعریف کردیم ست کنیم یعنی :

server.js
const allRoutes = require("./routes/routers");

const setTimeMiddleware = (req, res, next) => {
req.time = Date.now();
next();
};
app.use(setTimeMiddleware, allRoutes);

میدلور برای یک دسته از API ها

اگر مثلا بخوایم یک میدلور برای تمام API های زیرمجموعه‌ی /auth/ اجرا بشه باید اونو به روتر مدنظر اضافه کرد

routes/routers.js
const setTimeMiddleware = (req, res, next) => {
req.time = Date.now();
next();
};
router.use("/auth", setTimeMiddleware, authRouter);

میدلور برای یک API خاص

اگر بخوایم فقط یک API یک میدلور رو داشته باشه هم باید به روتر مربوطه اضافه بکنیمش

routes/auth.router.js
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 و ... از هم همیشه جدا باشن

در اینصورت خوانایی کد بسیار بالا میرود

Controller Image

اگر ساختار فولدر بندی بالا رو داشته باشیم :

controllers/auth.controller.js
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,
};
routes/auth.router.js
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");
});

برخی آپشن‌ها برای کوکی‌ها داریم. برای مثال تا کی وجود داشته باشه و کی از بین بره و ...

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");
});