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 یه گزینه امن و قابل اعتماد برای پروژه هات باشه.
دوره الفبای برنامه نویسی با هدف انتخاب زبان برنامه نویسی مناسب برای شما و پاسخگویی به سوالات متداول در شروع یادگیری موقتا رایگان شد: