Node.js یک محیط اجرایی جاوااسکریپت برای سمت سرور است که به توسعهدهندگان این امکان را میدهد تا بهجای استفاده از زبانهای مختلف برای سمت کلاینت و سرور، از یک زبان یکسان یعنی JavaScript استفاده کنند. اگر به دنبال آموزش JavaScript هستید، این مقاله آموزشی شما را از مبانی اولیه Node.js تا موضوعات پیشرفتهتر مانند احراز هویت و مجوز دسترسی راهنمایی میکند.
مبانی Node.js
Node.js یک محیط اجرایی متنباز برای اجرای کدهای JavaScript در سمت سرور است که توسط رایان دال در سال ۲۰۰۹ معرفی شد. این محیط اجرایی به برنامهنویسان این امکان را میدهد که بدون نیاز به مرورگر، کدهای JavaScript خود را اجرا کنند. یکی از ویژگیهای مهم Node.js این است که برخلاف زبانهای سنتی سمت سرور مانند PHP و Ruby که برای هر درخواست یک فرآیند جدید ایجاد میکنند، از یک فرآیند واحد برای مدیریت تمامی درخواستها بهره میبرد. این رویکرد نهتنها باعث مصرف کمتر حافظه میشود بلکه به دلیل استفاده از برنامهنویسی غیرهمزمان و غیر مسدودکننده (non-blocking)، کارایی بالایی نیز دارد.
موتور V8 گوگل و نقش آن در Node.js
Node.js از موتور V8 گوگل، که همان موتوری است که مرورگر کروم از آن برای اجرای کدهای JavaScript استفاده میکند، بهره میبرد. V8 یک موتور جاوااسکریپت سریع و بهینه است که کدهای JavaScript را بهجای تفسیر کردن، به کد ماشین تبدیل میکند و سرعت اجرای آنها را به میزان قابل توجهی افزایش میدهد. این ویژگی باعث میشود که Node.js بتواند کدهای جاوااسکریپت را با سرعتی بالا و در حد زبانهای برنامهنویسی بومی (مثل C++ یا Java) اجرا کند.
مدل ورودی/خروجی غیرهمزمان و غیر مسدودکننده (Asynchronous & Non-Blocking I/O)
یکی از ویژگیهای مهم Node.js، استفاده از مدل ورودی/خروجی (I/O) غیرهمزمان و غیر مسدودکننده است. در بیشتر زبانهای برنامهنویسی سنتی، هرگاه کدی برای عملیات ورودی/خروجی (مثل خواندن فایل یا فراخوانی یک API) اجرا شود، برنامه منتظر میماند تا عملیات تکمیل شود و سپس به کد بعدی میرود؛ به این رفتار «مسدودکننده» گفته میشود. اما Node.js به روشی غیر مسدودکننده عمل میکند و عملیات ورودی/خروجی را بهصورت غیرهمزمان انجام میدهد. در نتیجه، کد بدون نیاز به منتظر ماندن برای اتمام عملیات I/O، ادامه مییابد.
برای مثال، در Node.js وقتی که یک فایل را میخوانید یا یک درخواست شبکه انجام میدهید، این درخواست بهطور غیرهمزمان اجرا میشود. به این معنا که کد به اجرای خود ادامه میدهد و زمانی که عملیات ورودی/خروجی به پایان رسید، نتیجه از طریق یک «تابع بازگشتی» (callback function)، یک «وعده» (Promise) یا «async/await» به کد شما بازمیگردد. این ویژگی کمک میکند که برنامه بتواند تعداد زیادی درخواست همزمان را بدون مسدود کردن فرآیندهای دیگر مدیریت کند.
مثال: عملیات ورودی/خروجی غیرهمزمان
برای درک بهتر این مفهوم، به کد زیر نگاه کنید:
const fs = require('fs');
// عملیات ورودی/خروجی غیرهمزمان
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('خطا در خواندن فایل:', err);
return;
}
console.log('محتوای فایل:', data);
});
console.log('این پیام بلافاصله بعد از درخواست خواندن فایل نمایش داده میشود');
در کد بالا، پس از درخواست برای خواندن فایل example.txt، پیام «این پیام بلافاصله بعد از درخواست خواندن فایل نمایش داده میشود» بلافاصله به کنسول چاپ میشود. این نشان میدهد که عملیات خواندن فایل غیرمسدودکننده است و برنامه مجبور نیست تا زمانی که فایل بهطور کامل خوانده شود، منتظر بماند. در نهایت، پس از پایان عملیات خواندن فایل، محتوای فایل در کنسول چاپ میشود.
چرخه رخدادها (Event Loop)
مدل غیرهمزمان در Node.js بر مبنای یک ساختار به نام چرخه رخدادها (Event Loop) استوار است. چرخه رخدادها یک مکانیسم اساسی در Node.js است که مسئولیت مدیریت و پردازش رخدادهای غیرهمزمان را بر عهده دارد. زمانی که یک عملیات ورودی/خروجی آغاز میشود، آن درخواست به چرخه رخدادها اضافه میشود و بهجای متوقف کردن کل فرآیند، کدهای دیگر میتوانند اجرا شوند. هنگامی که عملیات به اتمام رسید، چرخه رخدادها تابع مربوطه (مانند تابع بازگشتی) را اجرا میکند.
چرخه رخدادها این امکان را میدهد که Node.js بتواند همزمان تعداد زیادی درخواست ورودی/خروجی را مدیریت کند، بدون اینکه هر درخواست، فرآیندهای دیگر را مسدود کند. این ویژگی برای ساخت اپلیکیشنهای بلادرنگ (real-time) مانند چتها، داشبوردهای آنلاین، و بازیهای چندنفره بسیار مناسب است.
مزایای اصلی Node.js
یکپارچگی JavaScript در سمت کلاینت و سرور: با استفاده از JavaScript در سمت سرور و کلاینت، تیمهای توسعه میتوانند از یک زبان مشترک برای تمامی بخشهای پروژه استفاده کنند.
کارایی بالا: به دلیل اجرای غیرمسدودکننده و موتور V8، Node.js بهشدت برای برنامههایی که نیاز به عملکرد بالا و مدیریت همزمانی دارند مناسب است.
بستر قدرتمند npm: Node.js با استفاده از مدیریت بسته (npm)، دسترسی به هزاران بسته آماده (کتابخانهها و ابزارها) را فراهم میکند که توسعهدهندگان میتوانند به سادگی آنها را به پروژههای خود اضافه کنند.
مقیاسپذیری: برنامههای Node.js به سادگی قابلیت مقیاسپذیری دارند و میتوانند بر روی سیستمهای توزیعشده اجرا شوند.
استفادههای رایج از Node.js
به دلیل کارایی بالا و ویژگیهای مقیاسپذیری، Node.js در زمینههای مختلف کاربرد دارد، از جمله:
اپلیکیشنهای بلادرنگ: مانند چت، بازیهای چندنفره، و نرمافزارهای کنترل همزمان.
APIهای RESTful: برای ساخت و مدیریت APIها که نیاز به عملکرد سریع و قابلیت مقیاسپذیری دارند.
پروژههای میکروسرویس: که نیازمند برقراری ارتباط و مدیریت درخواستهای همزمان زیادی هستند.
در نتیجه، Node.js یک ابزار قدرتمند و موثر برای توسعهدهندگانی است که به دنبال ساخت برنامههای سمت سرور سریع و مقیاسپذیر با JavaScript برای سمت سرور هستند.
برنامهنویسی غیرهمزمان در Node.js
برنامهنویسی غیرهمزمان یکی از مفاهیم کلیدی و مزایای اصلی Node.js است. در زبانهای برنامهنویسی سنتی، هرگاه یک عملیات ورودی/خروجی (I/O) مانند خواندن یک فایل، نوشتن در پایگاهداده، یا فراخوانی یک API انجام شود، برنامه تا زمان اتمام عملیات متوقف میشود؛ این ویژگی که به آن «مسدودکننده» (blocking) گفته میشود، در Node.js به شیوهای کاملاً متفاوت پیادهسازی شده است. در Node.js، عملیات ورودی/خروجی به صورت «غیرمسدودکننده» و «غیرهمزمان» اجرا میشود، به این معنا که کد میتواند به اجرای سایر بخشها ادامه دهد و نیازی به توقف ندارد.
چرا برنامهنویسی غیرهمزمان مهم است؟
ویژگی غیرهمزمان بودن به Node.js اجازه میدهد که همزمان هزاران درخواست را بهطور کارآمد و سریع مدیریت کند. این قابلیت باعث میشود که سرور بتواند تعداد زیادی از درخواستها را به صورت همزمان پردازش کند، بدون اینکه هر درخواست منتظر اتمام عملیات قبلی بماند. در نتیجه، Node.js برای برنامههای بلادرنگ (real-time) و سرویسهای با بار ترافیکی بالا، مانند چتهای آنلاین، بازیهای چندنفره، یا سرورهای API بسیار مناسب است.
چگونگی اجرای برنامهنویسی غیرهمزمان در Node.js
Node.js از طریق مدل چرخه رخدادها (Event Loop) و با کمک توابع بازگشتی (callback functions)، وعدهها (Promises)، و دستورات async/await برنامهنویسی غیرهمزمان را مدیریت میکند. در این مدل، Node.js درخواستهای ورودی/خروجی را بدون توقف ادامه میدهد و زمانی که پاسخ آماده شد، تابع بازگشتی یا وعده مربوط به آن اجرا میشود.
مثال ساده از برنامهنویسی غیرهمزمان
در اینجا مثالی از استفاده از یک تابع بازگشتی (callback) برای عملیات غیرهمزمان آورده شده است:
const fs = require('fs');
console.log('شروع خواندن فایل');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
console.log('اتمام درخواست خواندن فایل');
در این کد، عملیات fs.readFile بلافاصله درخواست خواندن فایل را به سیستم ارسال میکند. اما چون این عملیات غیرمسدودکننده است، بلافاصله پیام «اتمام درخواست خواندن فایل» نمایش داده میشود، بدون اینکه منتظر اتمام عملیات خواندن فایل بماند. پس از اینکه فایل خوانده شد، نتیجه آن از طریق تابع بازگشتی به برنامه بازمیگردد و محتوای فایل چاپ میشود.
برنامهنویسی غیرهمزمان با استفاده از وعدهها (Promises)
وعدهها (Promises) یک روش دیگر برای مدیریت برنامهنویسی غیرهمزمان هستند و باعث خوانایی بهتر کد میشوند. با استفاده از وعدهها، میتوان از زنجیرهسازی (chaining) استفاده کرد و نیاز به استفاده از توابع تو در تو را کاهش داد.
در ادامه، یک مثال از استفاده از وعدهها برای خواندن فایل آورده شده است:
const fs = require('fs').promises;
console.log('شروع خواندن فایل');
fs.readFile('file.txt', 'utf8')
.then(data => {
console.log(data);
})
.catch(err => {
console.error('خطا در خواندن فایل:', err);
});
console.log('اتمام درخواست خواندن فایل');
در این مثال، fs.readFile یک وعده باز میگرداند که میتوانیم با استفاده از then و catch به نتیجه و خطاهای احتمالی آن دسترسی پیدا کنیم.
برنامهنویسی غیرهمزمان با async/await
async/await راهکار جدیدتری برای برنامهنویسی غیرهمزمان در جاوااسکریپت و Node.js است که خوانایی و سادگی کد را بهبود میبخشد. async/await بر اساس وعدهها کار میکند و این امکان را فراهم میکند که کد غیرهمزمان مانند کد همزمان نوشته شود.
در ادامه، همان مثال قبلی را با استفاده از async/await مشاهده میکنیم:
const fs = require('fs').promises;
async function readFile() {
console.log('شروع خواندن فایل');
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('خطا در خواندن فایل:', err);
}
console.log('اتمام درخواست خواندن فایل');
}
readFile();
در اینجا، دستور await به کد این امکان را میدهد که منتظر اتمام خواندن فایل باشد، اما همچنان به شکل غیرهمزمان عمل میکند. به این معنا که await تنها درون تابع async اعمال میشود و سایر قسمتهای برنامه متوقف نمیشوند.
چرخه رخدادها (Event Loop)
مدیریت غیرهمزمان در Node.js توسط چرخه رخدادها (Event Loop) انجام میشود. چرخه رخدادها، لیستی از عملیات غیرهمزمان را در پسزمینه مدیریت میکند. هرگاه یک درخواست غیرهمزمان، مانند خواندن فایل، به چرخه رخدادها ارسال میشود، این عملیات در لیست انتظار قرار میگیرد و پس از تکمیل شدن به برنامه بازمیگردد تا تابع بازگشتی یا وعده اجرا شود.
این ویژگی منحصر به فرد در Node.js کمک میکند تا بهجای ایجاد چندین رشته (threads) مختلف برای مدیریت درخواستها، با استفاده از چرخه رخدادها تنها یک رشته (thread) استفاده شود. در نتیجه، Node.js میتواند تعداد زیادی درخواست را به صورت همزمان و بدون مسدود کردن فرآیندها مدیریت کند.
مقایسه برنامهنویسی همزمان و غیرهمزمان
برای درک بهتر، بیایید به یک مقایسه ساده بین کد همزمان و غیرهمزمان در Node.js نگاه کنیم:
کد همزمان (سینکرون):
const fs = require('fs');
console.log('شروع خواندن فایل');
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
console.log('اتمام خواندن فایل');
در این کد، از تابع readFileSync استفاده شده که همزمان و مسدودکننده است. در نتیجه، تا زمانی که فایل خوانده نشود، برنامه متوقف میشود و هیچ کدی اجرا نمیشود.
کد غیرهمزمان (آسینکرون):
const fs = require('fs');
console.log('شروع خواندن فایل');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
console.log('اتمام درخواست خواندن فایل');
در کد غیرهمزمان، درخواست خواندن فایل بلافاصله اجرا میشود و برنامه بدون توقف ادامه پیدا میکند. پس از اتمام عملیات خواندن فایل، تابع بازگشتی اجرا شده و محتوای فایل چاپ میشود.
برنامهنویسی غیرهمزمان، بخش اصلی کار با Node.js است و به سرور این امکان را میدهد که بدون ایجاد چندین فرآیند و رشته (thread)، درخواستهای زیادی را به طور همزمان و کارآمد مدیریت کند. با استفاده از چرخه رخدادها و تکنیکهایی مانند توابع بازگشتی، وعدهها و async/await، توسعهدهندگان میتوانند اپلیکیشنهایی سریع، مقیاسپذیر و با کارایی بالا را با JavaScript برای سمت سرور ایجاد کنند.
فریمورک Express.js
Express.js یکی از معروفترین و پراستفادهترین فریمورکها برای ساخت وبسرورها و APIهای سریع با استفاده از JavaScript برای سمت سرور (Node.js) است. این فریمورک که به اختصار Express نامیده میشود، به عنوان یک لایه مینیمالیستی برای ایجاد وبسرورها و مدیریت مسیرها (routes) طراحی شده است و باعث میشود که توسعهدهندگان بتوانند با سرعت بیشتری اپلیکیشنهای سمت سرور بسازند. Express.js بسیاری از پیچیدگیهای ساخت سرور و مدیریت درخواستها را کاهش میدهد و در عین حال امکانات گستردهای برای مدیریت مسیرها، استفاده از میانافزارها (middleware)، و اتصال به پایگاهداده فراهم میکند.
مزایای استفاده از Express.js
سادگی و مینیمالیسم: Express.js بسیار ساده و مینیمال طراحی شده است و در عین حال انعطافپذیر است. این فریمورک تنها امکاناتی را که برای ساخت یک سرور نیاز دارید فراهم میکند و شما را مجبور به استفاده از امکانات اضافی نمیکند.
ساختاردهی کد: با استفاده از Express.js، کدهای شما ساختاریافتهتر و خواناتر خواهند بود. این ویژگی خصوصاً در پروژههای بزرگ کمک میکند تا نگهداری و توسعهی کد آسانتر شود.
مدیریت مسیرها (Routing): Express.js به شما اجازه میدهد مسیرهای مختلف را به سادگی تعریف و مدیریت کنید. این ویژگی به شما این امکان را میدهد که برای هر URL در اپلیکیشن خود یک تابع مخصوص برای پردازش درخواست و ارسال پاسخ بنویسید.
پشتیبانی از میانافزارها (Middleware): میانافزارها در Express.js قطعههایی از کد هستند که میتوانند قبل یا بعد از پردازش درخواستها اجرا شوند. این ویژگی به شما امکان میدهد تا عملکردهای اضافی مانند احراز هویت، مدیریت خطاها، و ثبت گزارشات را بهراحتی به سرور خود اضافه کنید.
جامعه و کتابخانههای گسترده: Express.js از جامعهی گستردهای برخوردار است و به همین دلیل، بسیاری از کتابخانهها و میانافزارهای آماده برای کار با Express توسعه داده شدهاند که میتوانید آنها را بهسادگی به پروژههای خود اضافه کنید.
ساخت اولین سرور با Express.js
برای شروع استفاده از Express.js، ابتدا باید آن را نصب کنید. شما میتوانید از npm، که مدیریت بستههای Node.js است، برای نصب Express استفاده کنید.
npm install express
پس از نصب، بیایید اولین سرور خود را با Express.js ایجاد کنیم:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('سلام، به اولین سرور Node.js خوش آمدید!');
});
app.listen(3000, () => {
console.log('سرور در حال اجرا بر روی پورت 3000 است');
});
در این کد:
با استفاده از express()، یک نمونه از اپلیکیشن سرور ایجاد میکنیم.
از app.get() برای تعریف یک مسیر استفاده میکنیم. در این مثال، مسیر / که همان صفحه اصلی است، یک پاسخ ساده “سلام، به اولین سرور Node.js خوش آمدید!” را برمیگرداند.
با استفاده از app.listen(3000)، سرور را روی پورت 3000 اجرا میکنیم و پیامی را در کنسول چاپ میکنیم تا بدانیم سرور آماده است.
مدیریت مسیرها (Routing) در Express.js
Express.js به شما اجازه میدهد تا بهراحتی مسیرهای مختلف را برای درخواستهای HTTP مختلف مانند GET، POST، PUT و DELETE تعریف کنید. به عنوان مثال، برای ایجاد مسیرهای مختلف میتوانید کد زیر را استفاده کنید:
app.get('/about', (req, res) => {
res.send('این صفحه دربارهی ما است');
});
app.post('/submit', (req, res) => {
res.send('اطلاعات شما ثبت شد');
});
در این مثال:
مسیر /about با درخواست GET پیامی را برمیگرداند.
مسیر /submit با درخواست POST، پیامی مبنی بر ثبت اطلاعات را ارسال میکند.
استفاده از میانافزارها (Middleware)
یکی از ویژگیهای کلیدی Express.js پشتیبانی از میانافزارها است. میانافزارها توابعی هستند که به درخواستها و پاسخها دسترسی دارند و میتوانند قبل یا بعد از پردازش درخواستها اجرا شوند. این ویژگی میتواند برای کارهایی مثل ثبت گزارش، مدیریت خطاها، یا احراز هویت کاربران بسیار مفید باشد.
به عنوان مثال، میانافزار زیر تمام درخواستها را در کنسول چاپ میکند:
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
در اینجا:
app.use() یک میانافزار را تعریف میکند که در این مثال، تمام درخواستها را ثبت میکند.
next() به Express میگوید که به مرحله بعدی پردازش درخواست ادامه دهد.
کار با دادههای ورودی و JSON در Express.js
Express.js به سادگی از دادههای JSON و دادههای ورودی فرمها پشتیبانی میکند. با استفاده از میانافزارهای express.json() و express.urlencoded() میتوانید دادههای JSON و فرمها را در درخواستهای POST پردازش کنید.
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/submit', (req, res) => {
const { name, email } = req.body;
res.send(`نام شما: ${name}، ایمیل شما: ${email}`);
});
در این مثال، دادههای ورودی از طریق req.body قابل دسترسی هستند.
استفاده از پارامترهای مسیر و پرسوجو در Express.js
Express.js به شما امکان میدهد پارامترهای مسیر و پرسوجو را دریافت کنید. پارامترهای مسیر بخشهایی از URL هستند که میتوانند متغیر باشند و برای دریافت اطلاعات خاصی استفاده میشوند.
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
res.send(`شناسه کاربر: ${userId}`);
});
در این مثال، درخواست به مسیر /user/:id مقدار id را از مسیر دریافت کرده و در پاسخ نمایش میدهد.
استفاده از فایلهای استاتیک در Express.js
Express.js همچنین امکان استفاده از فایلهای استاتیک مانند HTML، CSS و تصاویر را بهصورت ساده فراهم میکند. برای این کار میتوانید از express.static استفاده کنید.
app.use(express.static('public'));
در این مثال، فایلهای استاتیکی که در پوشه public قرار دارند در دسترس کاربران قرار میگیرند.
نصب و راهاندازی پروژه Express.js با استفاده از express-generator
برای ایجاد سریع پروژههای بزرگتر با ساختار مناسب، میتوانید از ابزار express-generator استفاده کنید که یک ساختار اولیه برای پروژه شما ایجاد میکند.
ابتدا express-generator را به صورت جهانی نصب کنید:
npm install -g express-generator
سپس با استفاده از دستور زیر، یک پروژه جدید با ساختار اولیه ایجاد کنید:
express myapp
با این دستور، یک پروژه Express با پوشهها و فایلهای آماده ایجاد میشود که شامل میانافزارها، مسیرها، و تنظیمات اصلی است.
Express.js به دلیل سادگی، کارایی و انعطافپذیری خود یکی از محبوبترین فریمورکها برای استفاده از JavaScript برای سمت سرور (Node.js) است. این فریمورک با امکانات گسترده و جامعهی فعال خود، به شما اجازه میدهد تا با سرعت و کارایی بالا وبسرورها و APIهای قابل اطمینان بسازید و پروژههای خود را به سطح جدیدی از توسعه و مقیاسپذیری برسانید.
REST APIها با Node و Express
یکی از کاربردهای کلیدی JavaScript برای سمت سرور (Node.js)، ساخت و مدیریت REST API است. REST API یا «رابط برنامهنویسی کاربردی انتقال وضعیت بازنمودی» به اپلیکیشنها و سرویسهای مختلف اجازه میدهد تا از طریق پروتکل HTTP با یکدیگر ارتباط برقرار کنند. این APIها برای ساخت سیستمهایی مانند اپلیکیشنهای وب، موبایل و برنامههای تکصفحهای (Single Page Applications) بسیار کاربردی هستند.
در این بخش، با استفاده از Express.js، نحوه ایجاد یک REST API ساده برای مدیریت لیست کاربران را توضیح میدهیم. این API به کلاینتها اجازه میدهد تا لیست کاربران را مشاهده کرده، کاربران جدید اضافه کنند، کاربران خاص را ویرایش و یا حذف نمایند.
اصول REST و روشهای HTTP
در REST APIها، درخواستهای HTTP بر اساس یک سری روشها (HTTP Methods) برای دسترسی و مدیریت منابع استفاده میشوند. این روشها شامل موارد زیر هستند:
GET: برای دریافت داده (مانند دریافت لیست کاربران).
POST: برای ایجاد داده جدید (مانند افزودن یک کاربر جدید).
PUT و PATCH: برای بهروزرسانی دادهها (ویرایش اطلاعات کاربر).
DELETE: برای حذف دادهها (حذف کاربر).
ساخت REST API با Express.js
برای شروع، ابتدا باید Express.js را نصب کنید:
npm install express
سپس، با ایجاد یک API ساده برای مدیریت لیست کاربران شروع میکنیم. کد زیر، یک سرور Express ساده با یک لیست کاربر در حافظه ایجاد میکند و امکان مشاهده، افزودن، ویرایش و حذف کاربران را فراهم میکند.
ایجاد فایل app.js
ابتدا یک فایل جدید به نام app.js بسازید و کد زیر را در آن قرار دهید:
const express = require('express');
const app = express();
app.use(express.json()); // برای پشتیبانی از JSON در درخواستهای POST و PUT
// پایگاهداده موقتی از کاربران
let users = [
{ id: 1, name: 'Ali' },
{ id: 2, name: 'Reza' }
];
1. تعریف یک مسیر GET برای دریافت لیست کاربران
با تعریف مسیر /api/users میتوانیم با استفاده از متد GET لیست تمام کاربران را دریافت کنیم:
// دریافت لیست کاربران
app.get('/api/users', (req, res) => {
res.json(users);
});
2. تعریف مسیر GET برای دریافت یک کاربر خاص
این مسیر به شما اجازه میدهد با ارسال شناسه کاربر، اطلاعات آن کاربر خاص را دریافت کنید:
// دریافت اطلاعات یک کاربر خاص با استفاده از شناسه
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).send('کاربر با این شناسه یافت نشد');
res.json(user);
});
3. تعریف مسیر POST برای افزودن کاربر جدید
با استفاده از متد POST، میتوانیم یک کاربر جدید به لیست اضافه کنیم. اطلاعات کاربر جدید در بدنه درخواست (req.body) ارسال میشود:
// افزودن کاربر جدید
app.post('/api/users', (req, res) => {
const newUser = {
id: users.length + 1,
name: req.body.name
};
users.push(newUser);
res.status(201).json(newUser);
});
در اینجا:
ما یک کاربر جدید را با یک شناسه یکتا ایجاد و به لیست users اضافه میکنیم.
با استفاده از res.status(201).json(newUser) وضعیت 201 (ایجاد شد) و اطلاعات کاربر جدید را برمیگردانیم.
4. تعریف مسیر PUT برای بهروزرسانی کاربر
برای ویرایش اطلاعات یک کاربر موجود، از متد PUT استفاده میکنیم. در این مسیر، کاربر با شناسه مشخص شده را جستجو کرده و در صورت وجود، اطلاعات آن را بهروزرسانی میکنیم:
// بهروزرسانی کاربر
app.put('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).send('کاربر با این شناسه یافت نشد');
user.name = req.body.name;
res.json(user);
});
در این کد:
ابتدا کاربر را با شناسه ارائهشده پیدا میکنیم.
اگر کاربر موجود بود، نام آن را با نام جدید از req.body بهروزرسانی کرده و نتیجه را برمیگردانیم.
5. تعریف مسیر DELETE برای حذف کاربر
برای حذف یک کاربر، از متد DELETE استفاده میکنیم. کاربر با شناسه مشخص را پیدا کرده و از لیست حذف میکنیم:
// حذف کاربر
app.delete('/api/users/:id', (req, res) => {
const userIndex = users.findIndex(u => u.id === parseInt(req.params.id));
if (userIndex === -1) return res.status(404).send('کاربر با این شناسه یافت نشد');
const deletedUser = users.splice(userIndex, 1);
res.json(deletedUser);
});
در این کد:
ابتدا شاخص (index) کاربر مورد نظر را پیدا میکنیم.
سپس با استفاده از splice، آن کاربر را از لیست users حذف کرده و نتیجه را برمیگردانیم.
اجرای سرور
در نهایت، سرور را روی یک پورت مشخص اجرا میکنیم تا API در دسترس باشد:
app.listen(3000, () => {
console.log('API در حال اجرا بر روی پورت 3000 است');
});
تست API با ابزار Postman
برای تست REST API میتوانید از ابزارهایی مانند Postman استفاده کنید. با استفاده از این ابزار، میتوانید درخواستهای GET، POST، PUT و DELETE را ارسال کنید و پاسخها را مشاهده نمایید.
GET /api/users: لیست کاربران را باز میگرداند.
GET /api/users/:id: اطلاعات کاربر خاصی را برمیگرداند.
POST /api/users: یک کاربر جدید ایجاد میکند (در بدنه درخواست باید name ارسال شود).
PUT /api/users/:id: اطلاعات کاربر مشخصشده را بهروزرسانی میکند.
DELETE /api/users/:id: کاربر مشخصشده را حذف میکند.
ساختار RESTful API و بهترین روشها
برای ایجاد یک RESTful API بهینه، موارد زیر توصیه میشوند:
مسیرهای منطقی: استفاده از نامهای معنادار و منطقی برای مسیرها (مانند /api/users برای کاربران).
استفاده مناسب از روشهای HTTP: برای هر عمل از متد HTTP مناسب استفاده کنید.
وضعیتهای HTTP: برای پاسخها از وضعیتهای HTTP مناسب مانند 200 (موفقیت)، 201 (ایجاد شد)، 404 (یافت نشد) و 500 (خطای سرور) استفاده کنید.
استفاده از JSON: در REST APIها، معمولاً از JSON به عنوان فرمت داده برای ارتباط بین سرور و کلاینت استفاده میشود.
Express.js فریمورکی کارآمد و آسان برای ساخت و مدیریت REST APIها است. با استفاده از Express.js، شما میتوانید APIهای RESTful با قابلیتهای خواندن، ایجاد، ویرایش و حذف دادهها ایجاد کنید و اپلیکیشنهای قدرتمندی را با JavaScript برای سمت سرور (Node.js) بسازید. این APIها به شما اجازه میدهند که بهسادگی اپلیکیشنهای متصل به پایگاهداده، سیستمهای توزیعشده و سرویسهای بلادرنگ ایجاد کنید و به نیازهای مختلف کسبوکارها پاسخ دهید.
پایگاهدادهها با MongoDB و Mongoose
MongoDB یکی از پایگاهدادههای محبوب NoSQL است که ساختاری انعطافپذیر و بدون نیاز به جدولبندی مشابه با پایگاهدادههای سنتی دارد. دادهها در MongoDB بهصورت سندها (Documents) ذخیره میشوند و به شکل JSON ذخیره میشوند که بسیار شبیه به ساختار شیءگرایی در JavaScript است. این ویژگی MongoDB را به گزینهای عالی برای پروژههای مبتنی بر Node.js تبدیل کرده است، چرا که با دادهها به همان فرمی کار میکند که جاوااسکریپت در کلاینت و سرور از آن پشتیبانی میکند.
Mongoose، یک کتابخانه محبوب برای کار با MongoDB در JavaScript برای سمت سرور (Node.js) است. این کتابخانه به ما کمک میکند تا با استفاده از مدلسازی (Modeling)، دادههای خود را ساختارمند و با ویژگیهای اضافی مانند اعتبارسنجی و متدهای سفارشی، بهتر مدیریت کنیم.
نصب MongoDB و Mongoose
پیش از هر چیز، باید مطمئن شوید که MongoDB روی سیستم شما نصب شده و در حال اجرا است. میتوانید MongoDB را از وبسایت رسمی آن MongoDB دانلود و نصب کنید.
برای نصب Mongoose، از npm استفاده کنید:
npm install mongoose
اتصال به پایگاهداده MongoDB با استفاده از Mongoose
بعد از نصب Mongoose، ابتدا باید به پایگاهداده MongoDB خود متصل شوید. با استفاده از mongoose.connect میتوانید به یک پایگاهداده محلی (یا یک پایگاهداده ابری مانند MongoDB Atlas) متصل شوید.
const mongoose = require('mongoose');
// اتصال به پایگاهداده
mongoose.connect('mongodb://localhost:27017/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('اتصال به پایگاهداده برقرار شد'))
.catch(err => console.error('خطا در اتصال به پایگاهداده:', err));
در این کد، به MongoDB متصل میشویم و در صورت موفقیت پیامی چاپ میشود و در صورت بروز خطا، خطا در کنسول نمایش داده میشود.
تعریف مدلها در Mongoose
مدلها در Mongoose به شما اجازه میدهند ساختار دادههای خود را با استفاده از Schema (طرح داده) تعریف کنید. برای مثال، فرض کنیم میخواهیم یک مدل کاربر بسازیم که شامل نام و سن باشد.
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
age: { type: Number, required: true }
});
const User = mongoose.model('User', userSchema);
در این مثال:
userSchema ساختار کاربر را با فیلدهای name و age تعریف میکند.
هر دو فیلد name و age اجباری (required) هستند و اگر در حین ذخیره این اطلاعات ارائه نشوند، خطا برمیگردد.
عملیاتهای CRUD (ایجاد، خواندن، بهروزرسانی، و حذف) با Mongoose
Mongoose تمامی عملیاتهای CRUD را بهسادگی فراهم میکند که شامل ایجاد (Create)، خواندن (Read)، بهروزرسانی (Update) و حذف (Delete) است.
1. ایجاد (Create)
برای ایجاد یک کاربر جدید، یک نمونه از مدل User میسازیم و آن را ذخیره میکنیم:
const user = new User({ name: 'Ali', age: 25 });
user.save()
.then(() => console.log('کاربر ذخیره شد'))
.catch(err => console.error('خطا در ذخیره کاربر:', err));
2. خواندن (Read)
برای خواندن اطلاعات، از متدهایی مانند find، findById و findOne استفاده میکنیم. به عنوان مثال، برای گرفتن لیست تمام کاربران:
User.find()
.then(users => console.log('لیست کاربران:', users))
.catch(err => console.error('خطا در دریافت کاربران:', err));
و برای گرفتن کاربری با شناسه خاص:
User.findById('شناسه-کاربر')
.then(user => {
if (!user) return console.log('کاربر یافت نشد');
console.log('کاربر پیدا شد:', user);
})
.catch(err => console.error('خطا در یافتن کاربر:', err));
3. بهروزرسانی (Update)
برای بهروزرسانی اطلاعات یک کاربر، میتوانیم از متدهایی مانند updateOne یا findByIdAndUpdate استفاده کنیم:
User.findByIdAndUpdate('شناسه-کاربر', { age: 30 }, { new: true })
.then(user => {
if (!user) return console.log('کاربر یافت نشد');
console.log('کاربر بهروزرسانی شد:', user);
})
.catch(err => console.error('خطا در بهروزرسانی کاربر:', err));
در این مثال:
متد findByIdAndUpdate کاربری با شناسه مشخصشده را پیدا کرده و سن او را به 30 تغییر میدهد.
پارامتر new: true تضمین میکند که کاربر بهروز شده به عنوان نتیجه برگردانده شود.
4. حذف (Delete)
برای حذف یک کاربر، میتوانیم از متد deleteOne یا findByIdAndDelete استفاده کنیم:
User.findByIdAndDelete('شناسه-کاربر')
.then(user => {
if (!user) return console.log('کاربر یافت نشد');
console.log('کاربر حذف شد:', user);
})
.catch(err => console.error('خطا در حذف کاربر:', err));
اعتبارسنجی (Validation) در Mongoose
یکی از ویژگیهای قوی Mongoose، پشتیبانی از اعتبارسنجی است. با استفاده از این قابلیت، میتوانیم شرایط مختلفی را برای دادهها تعیین کنیم تا مطمئن شویم دادههای ورودی به پایگاهداده معتبر هستند. برای مثال، میتوانیم بررسی کنیم که نام کاربر حداقل 3 حرف داشته باشد.
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 3
},
age: {
type: Number,
required: true,
min: 0
}
});
در این مثال:
ویژگی minlength: 3 در فیلد name باعث میشود که نام کاربر باید حداقل 3 کاراکتر باشد.
ویژگی min: 0 در فیلد age نیز باعث میشود که مقدار سن نمیتواند کمتر از صفر باشد.
روابط بین دادهها (References)
MongoDB یک پایگاهداده غیررابطهای است، اما با استفاده از Mongoose میتوان روابط بین دادهها را تعریف کرد. فرض کنیم میخواهیم هر کاربر دارای چند پست (Post) باشد. ابتدا مدل پست را تعریف میکنیم:
const postSchema = new mongoose.Schema({
title: String,
content: String,
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
const Post = mongoose.model('Post', postSchema);
سپس، میتوانیم از populate برای واکشی دادههای مرتبط استفاده کنیم:
Post.find().populate('user')
.then(posts => console.log('پستها با کاربران مرتبط:', posts))
.catch(err => console.error('خطا در واکشی پستها:', err));
در اینجا، populate(‘user’) باعث میشود که اطلاعات کامل کاربر مرتبط با هر پست بازگردانده شود.
با استفاده از MongoDB و کتابخانه Mongoose، میتوانید بهسادگی دادههای خود را ذخیره و مدیریت کنید و مدلهای مختلفی را با اعتبارسنجی و روابط مشخص تعریف کنید. این ترکیب برای پروژههایی که از JavaScript برای سمت سرور (Node.js) استفاده میکنند، یک گزینه ایدهآل است و امکان مقیاسپذیری و عملکرد بالا را در مدیریت دادهها فراهم میکند.
احراز هویت و مجوز دسترسی (JWT، Passport.js)
احراز هویت و مجوز دسترسی از بخشهای حیاتی هر اپلیکیشن وب محسوب میشود که نیاز به امنیت و مدیریت دسترسی کاربران دارد. JavaScript برای سمت سرور (Node.js) با ترکیبی از ابزارهایی مانند JWT (JSON Web Token) و Passport.js امکان پیادهسازی راهحلهای کارآمد و ایمن برای احراز هویت را فراهم میکند. در این بخش، به بررسی این دو ابزار و نحوه استفاده از آنها در Node.js میپردازیم.
JWT (JSON Web Token)
JWT یک استاندارد رمزگذاریشده و متنباز برای تبادل اطلاعات امن بین طرفهای مختلف است. در JWT، اطلاعات به صورت رمزگذاریشده در یک توکن ذخیره میشود و این توکن در هنگام درخواستهای بعدی به سرور ارسال میشود تا سرور از هویت کاربر و مجوز دسترسی آن مطلع شود.
توکن JWT از سه بخش اصلی تشکیل شده است:
Header (سربرگ): شامل اطلاعاتی در مورد نوع توکن و الگوریتم رمزنگاری.
Payload (بار): شامل دادههایی مثل شناسه کاربر یا نام او.
Signature (امضا): یک امضای رمزنگاریشده که صحت توکن را تضمین میکند.
این سه بخش با یکدیگر ترکیب و با رمزنگاری ایمن میشوند و توکن JWT را تشکیل میدهند.
نصب JWT در Node.js
ابتدا برای استفاده از JWT باید بسته jsonwebtoken را نصب کنید:
npm install jsonwebtoken
پیادهسازی احراز هویت با JWT
فرض کنید قصد داریم که یک کاربر پس از ورود به سیستم یک توکن JWT دریافت کند و از آن برای دسترسی به مسیرهای محافظتشده استفاده کند.
کد نمونه برای ایجاد توکن در مسیر /login
در این مثال، کاربر با ارسال اطلاعات ورود (مانند نام کاربری و رمز عبور) به سرور درخواست ورود میدهد. اگر اطلاعات کاربری معتبر باشد، سرور یک توکن JWT برای کاربر ایجاد و آن را به کلاینت برمیگرداند.
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
// ورود و ایجاد توکن
app.post('/login', (req, res) => {
const { username, password } = req.body;
// بررسی صحت نام کاربری و رمز عبور (در اینجا به صورت نمونه)
if (username === 'Ali' && password === '1234') {
const user = { id: 1, name: 'Ali' }; // دادههای کاربر
const token = jwt.sign(user, 'secret_key', { expiresIn: '1h' });
res.json({ token });
} else {
res.status(401).send('نام کاربری یا رمز عبور اشتباه است');
}
});
در اینجا:
پس از تأیید اطلاعات کاربر، توکنی ایجاد میشود که شامل شناسه کاربر و نام او است.
secret_key کلید رمزنگاری توکن است و باید در یک محیط امن نگهداری شود.
expiresIn: ‘1h’ مشخص میکند که این توکن پس از یک ساعت منقضی میشود.
کد نمونه برای محافظت از مسیر /protected
اکنون که کاربر یک توکن دریافت کرده است، میتواند از آن برای دسترسی به مسیرهای محافظتشده استفاده کند. در این مثال، مسیر /protected تنها در صورتی قابل دسترسی است که کاربر یک توکن معتبر ارسال کند.
// دسترسی به مسیر محافظتشده
app.get('/protected', (req, res) => {
const token = req.headers['authorization'];
if (!token) return res.status(403).send('دسترسی غیرمجاز');
jwt.verify(token, 'secret_key', (err, userData) => {
if (err) return res.status(403).send('توکن نامعتبر');
res.json({ message: 'دسترسی موفق', user: userData });
});
});
در اینجا:
req.headers[‘authorization’] توکن را از هدر درخواست میگیرد. (معمولاً توکن در قالب Bearer <token> ارسال میشود.)
jwt.verify توکن را با استفاده از کلید رمزنگاری بررسی میکند. اگر توکن معتبر باشد، دادههای کاربر به درخواست اضافه میشود و کاربر به محتوای محافظتشده دسترسی خواهد داشت.
Passport.js
Passport.js یک کتابخانه جامع و انعطافپذیر برای احراز هویت در Node.js است. این کتابخانه از طریق استراتژیهای مختلف (مانند احراز هویت با گوگل، فیسبوک، توییتر و یا حتی توکنهای JWT) امکان احراز هویت را فراهم میکند. Passport.js به شما این امکان را میدهد که بهسادگی از استراتژیهای مختلف احراز هویت استفاده کرده و با ایجاد توکن، مدیریت ورود کاربران را تسهیل کنید.
نصب Passport.js و استراتژی JWT
برای استفاده از Passport.js با JWT، به دو بسته نیاز دارید: passport و passport-jwt. ابتدا این دو بسته را نصب کنید:
npm install passport passport-jwt
تنظیمات اولیه Passport.js برای استفاده از JWT
برای استفاده از Passport.js با JWT، ابتدا باید استراتژی JWT را پیکربندی کنید. این استراتژی به شما این امکان را میدهد که توکنهای JWT را از درخواستها خوانده و بررسی کنید.
const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secret_key'
};
passport.use(new JwtStrategy(options, (jwt_payload, done) => {
// میتوانید در اینجا کاربر را از پایگاهداده بررسی کنید
if (jwt_payload.id === 1) {
return done(null, { id: jwt_payload.id, name: jwt_payload.name });
} else {
return done(null, false);
}
}));
در اینجا:
ExtractJwt.fromAuthHeaderAsBearerToken() توکن JWT را از هدر درخواست در قالب Bearer <token> استخراج میکند.
secretOrKey کلید رمزنگاری است که برای بررسی امضای توکن استفاده میشود.
در تابع بازگشتی استراتژی، میتوانید اطلاعات کاربر را از پایگاهداده پیدا کرده و در صورت تطابق با توکن، آن را به تابع done ارسال کنید.
محافظت از مسیرها با استفاده از Passport.js
اکنون میتوانید مسیرهای خود را با استفاده از Passport.js محافظت کنید. کافی است از passport.authenticate برای مسیرهای محافظتشده استفاده کنید:
app.get('/protected', passport.authenticate('jwt', { session: false }), (req, res) => {
res.json({ message: 'دسترسی موفق', user: req.user });
});
در اینجا:
passport.authenticate(‘jwt’, { session: false }) از استراتژی JWT برای احراز هویت استفاده میکند.
اگر توکن معتبر باشد، اطلاعات کاربر در req.user قرار میگیرد و پاسخ با پیام «دسترسی موفق» ارسال میشود.
بهترین روشها برای استفاده از JWT و Passport.js
استفاده از کلیدهای رمزنگاری امن: از کلیدهای رمزنگاری امن و قوی برای امضای JWTها استفاده کنید. بهتر است این کلیدها در فایلهای محیطی یا ابزارهای مدیریت محیط ذخیره شوند.
تنظیم تاریخ انقضا: تاریخ انقضا را برای JWTها تنظیم کنید. این کار کمک میکند تا توکنهای قدیمی منقضی شوند و از سوءاستفاده جلوگیری شود.
تجدید توکن (Token Refresh): در صورتی که مدت اعتبار توکن کوتاه است، میتوانید مکانیزمی برای تجدید توکنهای منقضیشده ایجاد کنید.
استفاده از HTTPS: توکنهای JWT در معرض خطر حملات استراقسمع هستند، بنابراین حتماً از HTTPS برای امنیت ارتباطات استفاده کنید.
مدیریت دسترسی بر اساس نقش (Role-Based Access Control): با استفاده از نقشها (مانند مدیر، کاربر عادی) میتوانید دسترسی به مسیرهای خاص را محدود کنید.
JWT و Passport.js ابزارهای قدرتمندی برای مدیریت احراز هویت و مجوز دسترسی در JavaScript برای سمت سرور (Node.js) هستند. با استفاده از JWT، شما میتوانید اطلاعات کاربران را بهصورت ایمن و فشرده در قالب توکن ذخیره کنید و Passport.js به شما امکان استفاده از استراتژیهای مختلف احراز هویت را میدهد تا بهراحتی امنیت دسترسی کاربران را مدیریت کنید. این ابزارها به شما کمک میکنند تا با سادهسازی روند احراز هویت، اپلیکیشنهایی امن و مقیاسپذیر بسازید.
نتیجهگیری
استفاده از JavaScript برای سمت سرور (Node.js) به همراه فریمورک Express.js و ابزارهای قدرتمندی مانند MongoDB، Mongoose، JWT، و Passport.js، امکان ساخت اپلیکیشنهای مدرن، امن، و مقیاسپذیر را فراهم میکند. Node.js با قابلیتهای غیرهمزمان و غیرمسدودکننده، انتخابی ایدهآل برای اپلیکیشنهایی با بار ترافیکی بالا و نیازمند کارایی بالاست. با بهرهگیری از Express.js میتوانید بهسرعت APIهای RESTful طراحی کنید و با استفاده از JWT و Passport.js احراز هویت ایمن را پیادهسازی کنید. این ابزارها به توسعهدهندگان اجازه میدهند تا با سرعت و دقت بیشتری، برنامههای کاربردی و سرورهای پیچیده و کارآمدی را با یک زبان واحد، یعنی JavaScript، بسازند.
