Node.js به یکی از پایههای اصلی دنیای نرمافزار تبدیل شده. توی این چند سال، جاوااسکریپت حسابی پرطرفدار شده و همین باعث شده استفاده از Node.js هم توی پروژههای بزرگ خیلی بیشتر بشه. با Node.js و جاوااسکریپت، برای اولین بار میتونی با یه زبان، همه بخشهای یه اپلیکیشن رو بسازی؛ از قسمت فرانت که کاربر میبینه تا قسمتهای بکاند که توی سرور اتفاق میافته.
حالا، توی هر فریمورک وبی، یه سری چیزهای پایه و ضروری هست که باید باشه. توی Node.js هم این چیزهای پایه مثل آبجکتهای درخواست و پاسخ (request و response)، مدیریت درخواستهای HTTP، ارتباط با دیتابیس از طریق ORMها (یه جور ابزار برای کار با دیتابیس)، و کتابخونههایی برای راهاندازی اپلیکیشن توی محیط واقعی وجود داره. اپلیکیشنهایی که فقط با Node ساخته میشن، خیلی سریع هستن، ولی یه مشکل دارن: کمبود یه سری امکانات مهم مثل میدلویر (که برای پردازش درخواستها بین مسیر استفاده میشه)، مسیردهی (برای مدیریت آدرسهای مختلف سایت) و پلاگینها (افزونههایی که قابلیتهای جدید به اپلیکیشن اضافه میکنن). این امکانات به برنامهنویسها کمک میکنن که راحتتر و با کد کمتر، یه اپلیکیشن مدرن بسازن.
اینجاست که فریمورکهای وب وارد صحنه میشن و کار رو آسونتر میکنن! تو این مطلب میخوایم درباره Hapi.js صحبت کنیم؛ یه فریمورکی که داره به سرعت معروف میشه. دلیل این محبوبیت چیه؟ پایداری بالا و این که خیلی راحت میتونی باهاش اپلیکیشنهای بزرگ و قابل گسترش بسازی. حالا بیا با هم بیشتر در مورد این فریمورک جذاب بخونیم.
Hapi.js (کوتاهشدهی Http-API، تلفظش همون “هپی” یعنی شاد!) یه فریمورک متنباز برای ساختن اپلیکیشنهای تحت وب مقیاسپذیر هست. یکی از سادهترین کاربردهای Hapi، ساختن REST APIهاست. با Hapi.js میتونی سرورهای API، سایتها و حتی برنامههای پروکسی HTTP بسازی.
این فریمورک توسط تیم موبایل شرکت Walmart Labs به رهبری Eran Hammer ساخته شد تا بتونه ترافیک سنگینی مثل بلک فرایدی رو مدیریت کنه؛ همون روزی که یکی از شلوغترین روزهای خرید آنلاین در تقویم آمریکا هست!
بذار یه مثال بزنم تا بفهمی چرا برنامهنویسها Hapi رو انقدر قوی و مطمئن میدونن. Hapi تونست توی بلک فرایدی، که شلوغترین روز خرید سال توی آمریکا هست، میلیونها تراکنش خرید، برگشت کالا و خیلی چیزهای دیگه رو تو Walmart مدیریت کنه، بدون اینکه مشکلی پیش بیاد.
تو بلک فرایدی، Hapi تمام ترافیک موبایل Walmart رو با ۱۰ هسته پردازنده (CPU) و ۲۸ گیگابایت رم هندل کرد. البته منابع بیشتری هم داشتیم، ولی بیشترشون فقط ۰.۷۵٪ مشغول بودن و عملاً بیکار بودن! یعنی حجم عظیمی از ترافیک با منابع خیلی کم مدیریت شد. ایران همر، خالق Hapi، این رو یه دستاورد شگفتانگیز میدونه.
نسخههای اولیه Hapi با استفاده از فریمورک Express ساخته شده بود، ولی تیم Walmart فهمیدن که Express یه سری محدودیت داره و نمیتونه همه نیازهای پیچیدهشون رو برآورده کنه. واسه همین، Hapi رو بهعنوان یه فریمورک مستقل توسعه دادن که حالا دیگه به Express وابسته نیست.
Walmart تنها شرکتی نیست که از Hapi استفاده میکنه. برندهای بزرگ دیگهای هم هستن که Hapi رو توی پروژههای تولیدی خودشون به کار گرفتن و از امکانات و قابلیتهای فوقالعادهاش بهره میبرن:
توی مسیر بازسازی سرویسهای موبایلی Walmart و کار با فریمورکهای مختلف، اونا به یه سری اصول کلیدی رسیدن که برای ساخت اپلیکیشنهای Node خیلی مهم بودن؛ اصولی که باعث میشد کدها ساده، قابل نگهداری و مقیاسپذیر باشن. نتیجه این شد که تیم توسعه Hapi، این فریمورک رو بر اساس چندتا ایده اصلی طراحی کرد:
Hapi یه سیستم پلاگین خیلی قوی داره که بهت اجازه میده خیلی راحت قابلیتهای جدید به اپلیکیشن اضافه کنی و مشکلات رو سریع برطرف کنی. برای Hapi کلی پلاگین وجود داره که از مستندسازی گرفته تا احراز هویت و کلی چیزای دیگه رو شامل میشه. چند تا از مهمترین پلاگینها رو اینجا برات معرفی میکنم.
یکی از بزرگترین مزایای تقسیم کد به پلاگینها اینه که نهتنها به نفع کد خودته، بلکه به بقیه هم اجازه میده که از کدهای مشترکی که مفیده استفاده کنن. مثلاً ممکنه یه پلاگین مخصوص احراز هویت فیسبوک (مثل bell) بنویسی یا یه پلاگین خیلی ساده که وقتی سرور بالا میاد، مسیرها رو نشون بده (مثل blipp).
یکی دیگه از خوبیهای سیستم پلاگین Hapi اینه که تو رو به یه روش خاص محدود نمیکنه. اگه بعداً یه پلاگین بهتر پیدا کنی، خیلی راحت میتونی اون رو جایگزین کنی بدون اینکه کدهای اصلی پروژه به هم بریزه. این ماجرا نهتنها برای پلاگینهای شخص ثالث صدق میکنه، بلکه برای پلاگینهای خودت هم همینطوره.
وقتی شرکتهای بزرگ روی پلاگینها تو تیمهای مختلف کار میکنن، هر تیم میتونه مثلا درباره کش کردن دادهها تصمیم خودش رو بگیره. تیمها لازم نیست که برای هماهنگی دائم با هم در ارتباط باشن. حتی اگر پلاگینها روی یه سرور اجرا بشن، هرکدوم میتونن روش کشینگ خودشون رو داشته باشن که مناسب همون بخش از کده.
حالا بیایم چندتا از پلاگینهای مهم Hapi رو معرفی کنیم که بهت کمک میکنن قابلیتهای جدید به اپلیکیشن اضافه کنی:
این پلاگینها کمک میکنن تا خیلی سریع و با کدهای کم، قابلیتهای پیشرفتهای به اپلیکیشنت اضافه کنی و کار رو برای تیم توسعه راحتتر کنی.
هر دو فریمورک Express و Hapi قصد دارن ساده، انعطافپذیر و قابلگسترش باشن. به همین خاطر، کار با هر دوی اونها آسونه و به راحتی میتونی به کمک APIهای ساده و مدولار، اپلیکیشنت رو توسعه بدی. اگر با یکی از این فریمورکها کار کرده باشی، یادگیری اون یکی برات خیلی سخت نخواهد بود.
اما تفاوتهایی هم بینشون وجود داره:
در نهایت، اگه دنبال یه راه سادهتر و با تنظیمات پیشفرض امن هستی، Hapi میتونه برات مناسب باشه. اما اگه انعطاف بیشتری میخوای و دوست داری همه تصمیمات توسعه رو خودت بگیری، Express میتونه گزینه بهتری باشه.
Hapi مخصوص محیطهای پیچیده سازمانی طراحی شده، جایی که چندین تیم روی پروژههای مختلف یا بخشهای مشترکی کار میکنن که در نهایت باید به یک نتیجه یکپارچه برسن. Hapi این امکان رو به برنامهنویسها میده تا روی کدهای مدولار و قابلاستفاده مجدد کار کنن، بدون اینکه درگیر چارچوبهای پیچیده بشن.
تو چرخه درخواست Hapi، برنامهنویسها از hooks یا همون نقاط اتصال مثل handlers و extensions استفاده میکنن تا منطق موردنظرشون رو پیاده کنن. این ساختار کمک میکنه تا با رشد پیچیدگی پروژه، Hapi بهعنوان یک رابط واسط عمل کنه و بخشهای مختلف منطق تجاری به واحدهای کوچکتر و قابل مدیریت تقسیم بشه.
از زمان انتشار Hapi نسخه 16 تا نسخههای جدیدتر مثل 21، تغییرات اساسی تو این فریمورک اتفاق افتاده. یکی از بزرگترین تغییرات در نسخه 17 بود که Hapi از callbacks به async/await تغییر کرد. این تغییر، برنامهنویسی با Hapi رو خیلی سادهتر و بهینهتر کرد، مخصوصاً برای پروژههای بزرگ.
در حال حاضر، آخرین نسخه Hapi، نسخه 21.3.10 هست (منتشر شده در سال 2024)، که از ویژگیهای جدیدی مثل پشتیبانی از Node.js نسخه 18 و بهبودهای مختلف در سازگاری با ESM (ماژولهای استاندارد جاوااسکریپت) برخورداره. همینطور، از Node.js نسخه 12 و پایینتر دیگه پشتیبانی نمیکنه، پس بهتره مطمئن بشی که پروژت با نسخههای جدید Node کار میکنه.
حالا با استفاده از این نسخه جدید، همچنان میتونی خیلی راحت یه سرور ساده API بسازی. برای مثال، همون سرور سادهای که توی ورژنهای قبلی هم بود، با چند تغییر جزئی هنوز هم قابل اجراست. اما حالا باید از ویژگیهای بهروز Hapi استفاده کنی تا بهترین کارایی رو داشته باشی.
'use strict';
const Hapi = require('@hapi/hapi');
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello, world!';
}
});
server.route({
method: 'GET',
path: '/{name}',
handler: (request, h) => {
return `Hello, ${encodeURIComponent(request.params.name)}!`;
}
});
const init = async () => {
await server.start();
console.log(`Server running at: ${server.info.uri}`);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
این سرور رو با دستور node server.js اجرا کن و به آدرس http://localhost:3000 برو تا پیام Hello, world! رو ببینی. اگه به http://localhost:3000/yourname بری، با پیامی مثل Hello, yourname! مواجه میشی.
چند نکته مهم برای مهاجرت به نسخههای بالاتر:
برای شروع یه پروژه ساده با Hapi، اول باید یه پوشه جدید به اسم /hapi بسازی و واردش بشی. برای این کار، از دستور زیر استفاده کن:
npm init -y
npm install @hapi/hapi
این دستور یه فایل package.json جدید میسازه و Hapi رو به عنوان وابستگی (dependency) نصب میکنه. بعد از نصب، میتونی با لیست کردن محتویات پوشه /node_modules ببینی که فقط پوشه hapi داخلش قرار گرفته و Hapi به درستی نصب شده.
حالا بریم سراغ اضافه کردن اولین endpoint ساده به سرورمون. قراره که لیستی از دستورهای پخت پاستا رو توی فرمت JSON به صورت RESTful نمایش بدیم. برای این کار، یه فایل به اسم index.js بساز و کدهای زیر رو توش بنویس:
بیاید سراغ کد اصلی که endpoint سادهمون رو پیادهسازی میکنه. تو این قسمت، یه سرور Hapi درست میکنیم که لیستی از دستورهای پخت پاستا رو بهصورت JSON برمیگردونه. کد به این صورته:
const Hapi = require('@hapi/hapi');
const pastaRecipes = [
{ id: 1, name: 'Spaghetti Bolognese' },
{ id: 2, name: 'Fettuccine Alfredo' },
{ id: 3, name: 'Penne Arrabiata' }
];
const init = async () => {
const server = Hapi.server({
port: 5173,
host: '0.0.0.0' // این رو حذف کن تا فقط لوکال هاست رو گوش بده
});
server.route({
method: 'GET',
path: '/recipes',
handler: (request, h) => {
return pastaRecipes;
}
});
await server.start();
console.log('Server running at:', server.info.uri);
};
init();
برای تست کردن این endpoint، کافیه از دستور cURL زیر استفاده کنی یا مستقیماً تو مرورگر آدرس http://localhost:5173/recipes رو وارد کنی:
curl http://localhost:5173/recipes
اگه میخوای از دستگاه دیگهای هم وصل شی (مثلاً تو شبکه محلی)، کافیه تو تنظیمات سرور host: '0.0.0.0' رو بذاری تا بتونه به همه رابطهای شبکه وصل بشه.
این کد خیلی ساده و سرراسته:
این کد یه مثال خیلی خوب از اینه که چطور میتونی با چند خط ساده کد، یه API کوچک و کاربردی با Hapi بسازی!
حالا وقتشه که یه endpoint جدید برای POST اضافه کنیم که بشه باهاش یه دستور پخت جدید به لیست اضافه کرد. این درخواست POST سادهست و قراره یه دستور پخت جدید رو به آرایه دستورها اضافه کنه. اینم کدش:
server.route({
method: 'POST',
path: '/recipes',
handler: (request, h) => {
const recipe = request.payload;
recipe.id = pastaRecipes.length + 1; // ID جدید بر اساس تعداد دستورهای فعلی
pastaRecipes.push(recipe); // اضافه کردن دستور جدید به آرایه
return recipe; // برگردوندن دستور جدید به عنوان پاسخ
}
});
برای ارسال درخواست POST، میتونی از cURL استفاده کنی. این دستور یه دستور پخت جدید به لیست اضافه میکنه:
curl -X POST -d '{"name":"Penne alla Vodka"}' http://localhost:5173/recipes -H 'Content-Type: application/json'
بعد از اینکه این درخواست رو ارسال کردی، اگه دوباره یه GET به /recipes بزنی، میبینی که دستور جدید به لیست اضافه شده.
حالا با این تغییر، میتونی خیلی راحت با اضافه کردن دستورهای جدید پاستا، یه API کوچیک و کاربردی داشته باشی!
فرض کن میخوای فقط کاربرای ادمین بتونن به endpoint POST دسترسی داشته باشن. یکی از راههای ساده برای این کار، استفاده از مکانیزم احراز هویت HTTP Basic هست. Hapi بهراحتی این امکان رو فراهم میکنه. حالا با این روش میتونیم درخواستهای POST رو هم با cURL تست کنیم، چه با اطلاعات ورود و چه بدون اون.
const Hapi = require('@hapi/hapi');
const Bcrypt = require('bcrypt');
const users = {
john: {
username: 'john',
password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm', // پسورد: 'secret'
name: 'John Doe',
id: '2133d32a'
}
};
const validate = async (request, username, password) => {
const user = users[username];
if (!user) {
return { credentials: null, isValid: false };
}
const isValid = await Bcrypt.compare(password, user.password);
const credentials = { id: user.id, name: user.name };
return { isValid, credentials };
};
const init = async () => {
const server = Hapi.server({
port: 5173,
host: '0.0.0.0'
});
await server.register(require('@hapi/basic'));
server.auth.strategy('simple', 'basic', { validate });
server.route({
method: 'POST',
path: '/recipes',
options: {
auth: 'simple'
},
handler: (request, h) => {
const recipe = request.payload;
recipe.id = pastaRecipes.length + 1;
pastaRecipes.push(recipe);
return recipe;
},
});
await server.start();
console.log('Server running at:', server.info.uri);
};
init();
برای اجرای این کد، اول باید سرور رو متوقف کنی و پلاگین bcrypt رو نصب کنی:
npm i bcrypt
حالا سرور رو دوباره اجرا کن:
node index.js
با این کار، هر درخواست POST به /recipes باید اطلاعات ورود معتبر داشته باشه.
حالا وقتشه که endpoint امن رو تست کنیم. به کمک دستورات cURL، میتونیم ببینیم که چطور این endpoint در برابر درخواستهای مختلف عمل میکنه.
درخواست بدون اطلاعات احراز هویت:
وقتی بدون اطلاعات ورود تلاش میکنیم درخواست POST بفرستیم، خطای عدم احراز هویت رو دریافت میکنیم:
curl -X POST -H "Content-Type: application/json" -d '{"name": "Penne alla Vodka"}' http://localhost:5173/recipes
پاسخ:
{"statusCode":401,"error":"Unauthorized","message":"Missing authentication"}
درخواست با اطلاعات ورود اشتباه:
وقتی اطلاعات کاربری اشتباه باشه (مثلاً پسورد اشتباه)، همچنان اجازه دسترسی نخواهیم داشت:
curl -X POST -u john:badsecret -H "Content-Type: application/json" -d '{"name": "Penne alla Vodka"}' http://localhost:5173/recipes
پاسخ:
{"statusCode":401,"error":"Unauthorized","message":"Bad username or password","attributes":{"error":"Bad username or password"}}
درخواست با اطلاعات ورود صحیح:
وقتی اطلاعات ورود درسته، دستور پخت جدید به لیست اضافه میشه:
curl -X POST -u john:secret -H "Content-Type: application/json" -d '{"name": "Penne alla Vodka"}' http://localhost:5173/recipes
پاسخ:
{"name":"Penne alla Vodka","id":5}
حالا بیایم یه مثال پیچیدهتر رو بررسی کنیم. تو این قسمت، میخوایم از طریق ID بتونیم یک دستور پخت رو از آرایه بگیریم.
server.route({
method: 'GET',
path: '/recipes/{id}',
handler: (request, h) => {
const recipeId = parseInt(request.params.id);
const recipe = pastaRecipes.find(recipe => recipe.id === recipeId);
if (recipe) {
return recipe;
} else {
return h.response({ error: 'Recipe not found' }).code(404);
}
}
});
برای دریافت یه دستور پخت بر اساس ID، میتونیم این دستور cURL رو اجرا کنیم:
curl http://localhost:5173/recipes/1
پاسخ:
{"id":1,"name":"Fettuccine Alfredo"}
در اینجا، از پارامتر {id} استفاده کردیم تا یه مقدار از URL بگیریم و بر اساس اون، دستور پخت رو از آرایه پیدا کنیم. اگه دستور پختی با اون ID موجود باشه، برمیگرده وگرنه یه پیام خطای 404 دریافت میکنی.
Hapi از همون ابتدا پشتیبانی از کش رو بهصورت داخلی داره. برای مثال، میتونیم از کش درون حافظهای با نام CatBoxMemory استفاده کنیم. البته در اپلیکیشنهای واقعی معمولاً از سیستمهای کشی مثل Redis یا Memcached استفاده میکنن، ولی برای شروع همین CatBoxMemory عالیه.
برای استفاده از کش، باید با متد server.cache() یه کش جدید بسازی و اون رو به مسیرها (routes) اعمال کنی. اینم یه نمونه ساده از استفاده از کش در Hapi:
const CatboxMemory = require('@hapi/catbox-memory');
const cache = server.cache({
segment: 'recipes',
expiresIn: 60 * 60 * 1000, // کش برای 1 ساعت
generateFunc: async (recipeId) => {
const recipe = pastaRecipes.find((recipe) => recipe.id === recipeId);
return recipe ? { ...recipe, cacheHit: true } : null;
},
generateTimeout: 2000
});
server.route({
method: 'GET',
path: '/recipes',
handler: async (request, h) => {
const cachedRecipes = await cache.get('all');
if (cachedRecipes) {
return cachedRecipes;
}
await cache.set('all', { ...pastaRecipes, cacheHit: true });
return pastaRecipes;
}
});
توی این کد:
حالا میتونیم کش رو تست کنیم:
curl localhost:5173/recipes
پاسخ اول:
[{"id":0,"name":"Spaghetti Bolognese"},{"id":1,"name":"Fettuccine Alfredo"},{"id":2,"name":"Penne Arrabiata"}]
پاسخ بعد از استفاده از کش:
{"0":{"id":0,"name":"Spaghetti Bolognese"},"1":{"id":1,"name":"Fettuccine Alfredo"},"2":{"id":2,"name":"Penne Arrabiata"},"cacheHit":true}
در اینجا میبینی که توی پاسخ دوم فیلد cacheHit اضافه شده، که نشون میده دادهها از کش گرفته شدن و نه مستقیماً از دیتابیس یا آرایه. این باعث میشه که عملکرد اپلیکیشن سریعتر و بهینهتر بشه.
Hapi یه قابلیت جالب به اسم server method داره که بهطور سادهتری میتونی برای کش کردن نتایج استفاده کنی. فرض کن میخوای برای endpoint که دستورهای پخت رو بر اساس ID میگیره، کش اضافه کنی. بهجای پیچیدگیهای قبلی، با استفاده از این متد سرور میتونیم خیلی راحتتر این کار رو انجام بدیم.
const init = async () => {
const server = Hapi.server({
port: 5173,
host: '0.0.0.0'
});
server.method('getRecipe', (id) => {
const recipe = pastaRecipes.find(recipe => recipe.id === id);
return recipe ? recipe : { error: 'Recipe not found' };
}, {
cache: {
expiresIn: 10 * 1000, // کش برای 10 ثانیه
generateTimeout: 2000
}
});
server.route({
method: 'GET',
path: '/recipes/{id}',
handler: async (request, h) => {
return await server.methods.getRecipe(parseInt(request.params.id));
}
});
await server.start();
console.log('Server running at:', server.info.uri);
};
init();
توی این کد، با استفاده از server.method یه متد جدید به اسم getRecipe تعریف کردیم که دستور پخت رو بر اساس ID پیدا میکنه. با اضافه کردن cache به این متد، نتایج درخواستها بهطور خودکار کش میشن و نیاز به نوشتن کدهای اضافی برای مدیریت کش نداریم. پس اگه چند بار به همون ID درخواست بفرستی، نتیجه رو از کش میگیره و سریعتر برمیگردونه.
Hapi امکانات بیشتری بهصورت پیشفرض ارائه میده و برای پروژههای بزرگتر و تیمهای چندنفره خیلی مناسبتره، چرا که ساختارهای مدولار و پلاگینهای قویتری داره. از طرف دیگه، Hapi نسبت به Express کنترل بیشتری روی امنیت و کش کردن دادهها ارائه میده و نیازی به میدلویرهای اضافی برای بسیاری از وظایف معمول نداره.
بله، Hapi انعطافپذیره و میتونی ازش برای پروژههای کوچک هم استفاده کنی. هرچند اگه پروژه خیلی سادهای داری، ممکنه Express انتخاب راحتتری باشه، چون سبکتره. اما اگه بهدنبال ساختار منظمتر و مدیریت بهتر امنیت و کش هستی، Hapi انتخاب عالیایه.
Hapi از همون ابتدا با قابلیت کش داخلی ارائه میشه. میتونی از متد server.cache() یا server.method() برای کش کردن دادهها استفاده کنی. همچنین، با پلاگینهایی مثل CatBoxMemory یا Redis، میتونی دادهها رو بهصورت پیشرفتهتر کش کنی.
بله، نسخههای جدید Hapi از Node.js نسخه 14 و بالاتر، بهویژه نسخههای LTS مثل Node.js 18، کاملاً پشتیبانی میکنن. Hapi همیشه بهروزرسانی میشه تا با نسخههای جدیدتر Node.js سازگار بمونه و بهترین عملکرد رو ارائه بده.
Hapi واقعاً اون چیزی که از یه API سروری انتظار داری رو بهت میده: ساده، قابل گسترش و بدون نیاز به وابستگیهای اضافی. پلاگینها و سیستم کش Hapi خیلی تمیز و هدفمند طراحی شده تا نیازی به سروکله زدن با پیچیدگیهای میدلویر نداشته باشی. همین ویژگیهاش باعث میشه که Hapi یه گزینه امن و قابل اعتماد برای پروژههات باشه.
دوره الفبای برنامه نویسی با هدف انتخاب زبان برنامه نویسی مناسب برای شما و پاسخگویی به سوالات متداول در شروع یادگیری موقتا رایگان شد: