ساخت وب اپلیکیشن RESTful با استفاده از Express ،Node.js و MongoDB – از صفر تا صد


در این مقاله با مفاهیم مقدماتی RESTful آشنا میشویم و یک وب اپلیکیشن سریع، ساده و تکصفحهای با آن میسازیم. دقت کنید که این مطلب بر اساس نسخههای Node.js v8.11.x ،MongoDB v3.6.x و Express v4.16 ساخته شده است و ممکن است روی برخی سیستمها با نسخههای قبلی این نرمافزارها کار نکند.
در راهنمای قبلی «آموزش راهاندازی و اجرای Express،Node.js و MongoDB» دیدیم که چگونه میتوان از حالت بدون نصب هیچ چیز به یک وب اپلیکیشن ساده Node.js با استفاده از فریمورک اکسپرس رسید که دادههایی را از پایگاه داده MongoDB خوانده و در آن مینویسد. آن نوشته شروع مناسبی بود و اگر با این فناوریها آشنایی ندارید، بهتر است ابتدا به آن مقاله مراجعه کنید، چون در این بخش قصد داریم مفاهیم عمیقتر را بررسی کنیم. شما باید با شیوه راهاندازی یک وبسرور با Express و روش استفاده از app.get و app.post برای ارتباط با سرور و پایگاه داده آشنا باشید. همه این مباحث در بخش قبلی راهنما ارائه شده است و اگر توسعهدهندهای هستید که با جاوا اسکریپت آشنا هستید نباید برای شما دشوار باشد.
در هر صورت اگر آن مقاله را مطالعه کردهاید و یا از قبل با این مفاهیم آشنا هستید، اینک نوبت آن رسیده است که به تکمیل مهارتهای خود بپردازیم و یک اپلیکیشن ساده و کوچک بسازیم که بدون نیاز به تازهسازی یا رفرش صفحه نیز کار کند. اهداف این نوشته شامل موارد زیر هستند:
- یادگیری معنای REST به زبان ساده
- ذخیرهسازی و بازیابی دادههای JSON در یک مجموعه MongoDB با استفاده از متدهای HTTP GET و HTTP POST.
- حذف دادهها از مجموعه با استفاده از HTTP DELETE
- استفاده از AJAX برای همه عملیات داده
- بهروزرسانی DOM با JQuery
ما در بخش قبلی این راهنما یک اپلیکیشن ساده فرانتاند بر مبنای بکاند Router/View ساختیم. در این راهنما قصد داریم نیاز به رفرش کردن صفحه یا مراجعه به URI های دیگر را به طور کامل حذف کنیم. اما پیش از آغاز باید کمی با REST آشنا شویم.
بخش 1 – REST چیست؟
تعریف ویکیپدیا از REST یا «انتقال حالت بازنمایانه» (Representational State Transfer) به صورت زیر است:
یک سبک معماری که عناصر مربوط به معماری در درون یک سیستم هایپرمدیای توزیع یافته را انتزاع میکند.
شاید تعریف فوق چندان روشن نباشد. بسیاری از ما نمیدانیم که سیستم هایپرمدیای توزیع یافته چیست. اگر بخواهیم REST را به زبان ساده توضیح دهیم، باید به چهار مفهوم که در وبسایت IBM (+) آمده است اشاره کنیم:
- استفاده صرف از متدهای HTTP
- بیحالت (stateless) بودن
- ارائه URI-هایی با ساختار شبیه دایرکتوری
- انتقال XML ،JSON یا هر دوی آنها.
اینک به توضیح هر یک از مفاهیم فوق میپردازیم.
استفاده صرف از متدهای HTTP
این مفهوم کاملاً سرراست است. برای بازیابی داده از GET استفاده میکنیم. برای ایجاد داده نیز میتوانیم از POST استفاده کنیم. برای بهروزرسانی یا تغییر دادهها باید از PUT استفاده کنیم و برای حذف دادهها از DELETE استفاده میشود. بدین ترتیب مثال زیر نمونه مناسبی از استفاده صرف از HTTP نیست:
http://www.domain.com/myservice/newuser.php?newuser=bob
چون این یک HTTP GET است که میخواهد وانمود کند POST است. ما در آدرس فوق یک صفحه وب را میگیریم و همزمان دادههای آن را برای ذخیره شدن در پایگاه داده ارسال میکنیم. در حالی که باید ابتدا یک سرویس NewUser ایجاد کنیم و به آن POST کنیم.
بیحالت بودن
این مفهوم تا حدودی پیچیده است؛ اما خلاصه آن این است که نباید اطلاعات «باحالت» (statful) را در سرور ذخیره کنیم. اگر مجبور باشیم حالتی را ذخیره کنیم باید آن را در سمت کلاینت از طریق کوکی یا متدهای دیگر ذخیره کنیم. یک فریمورک فرانتاند مانند «انگولار» (Angular) در این مورد کاملاً مفید خواهد بود، چون کل تنظیمات سمت کلاینت MVC را ایجاد میکند و میتوانید به این ترتیب حالت عناصر را بدون درگیر کردن سرور ایجاد و دستکاری کنید.
IBM یک نمونه از صفحهبندی ارائه کرده است که کاملاً مناسب است. یک طراحی باحالت باید یک سرویس deliverPage داشته باشد که صفحههایی را که بازدید میکنید ردگیری کند و هر بار که دکمه Next را میزنید، صفحههای بعدی را تحویل دهد. یک طراحی بیحالت صرفاً صفحهای که هم اینک بازدید میکنید را به صورت currPage و دادههای nextPage را به صورت «نشانهگذاری» (Markup) نگهداری میکند و سپس با استفاده از HTTP GET در یک سرویس newPage به کمک پارامتر nextPage از markup درخواست صفحه بعدی را میکند.
همه مفاهیمی که در این بخش مطرح شدند را میتوانید در این صفحه (+) به صورت عملی ملاحظه کنید. با بررسی این کد متوجه میشوید که هرگز نباید دادههای مربوط به صفحه را در سمت سرور ذخیره کنید. ما صرفاً صفحه جاری را از DOM میگیریم و سپس صفحه بعدی را ارسال کرده و DOM را بهروزرسانی میکنیم. این همان معنی برنامهنویسی ساده بیحالت است.
ارائه URI-هایی با ساختارهای شبیه دایرکتوری
توضیح این مفهوم سادهتر است. به صورت خلاصه باید گفت که ما باید به جای ساختارهایی مانند:
http://app.com/getfile.php?type=video&game=skyrim&pid=68
ساختارهایی به این صورت داشته باشیم:
http://app.com/files/video/skyrim/68
انتقال XML یا JSON یا هر دوی آنها
این مفهوم نیز کاملاً ساده است. باید مطمئن شد که بکاند ما، دادهها را در قالبهای XML یا JSON ارسال میکند. این دادهها در «لایه ارائه» (presentation layer) بدون نیاز به تحمیل فشار روی سرور به جز در موارد نیاز به دادههای جدید قابل عرضه هستند.
بدین ترتیب با مفاهیم مقدماتی REST آشنا شدیم. همان طور که دیدید این مفاهیم کاملاً سرراست هستند و احتمالاً در سیستمهای خود مدتها است که با آنها کار میکنید.
بخش 2 – راهاندازی
اینک که ایدهای از معنای REST داریم، میتوانیم با ساخت یک وب اپلیکیشن تکصفحهای که آن را عملاً تست کنیم. البته ما قصد نداریم یک وب اپلیکیشن To-do که معادل برنامه «Hello-world» وب اپلیکیشنها است بسازیم. ما قصد داریم مجموعه سادهای از نامهای کاربری و ایمیلها بسازیم که تا حدود زیادی شبیه راهنمای قبلی ما است. ابتدا باید اطمینان حاصل کنید که جدیدترین نسخه پایدار Node روی سیستم شما نصب شده است و سپس پنجره کنسول را باز کنید و به جایی که قرار است این پروژه وب ذخیره شود بروید. با توجه به مقاصد این راهنما، مسیر انتخابی ما C:\node است. اگر شما مسیر دیگری را انتخاب کردهاید، باید دستورهایی که در ادامه میآید را بر همین مبنا اصلاح کنید.
نخستین کاری که باید انجام داد، بهروزرسانی Express و سازنده داربست Express به صورت سراسری مانند زیر است:
npm update -g express
زمانی که دستور فوق انجام شد، دستور زیر را اجرا کنید:
npm update -g express-generator
در انتها این دستور را وارد کنید:
express nodetest2
همان طور که در بخش قبلی این راهنما مشاهده کردید، دستورهای فوق ساختار یک وبسایت را به صورت خودکار در یک دایرکتوری جدید به نام nodetest2 ایجاد میکنند. منتظر بمانید تا این کار صورت بگیرد و زمانی که انجام شد، فایل جدیداً ایجاد شده package.json را در پوشه جدیداً ایجاد شده nodetest2 در ویرایشگر متنی انتخابی خود باز کنید و آن را به صورت زیر تغییر دهید:
{ "name": "nodetest2", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "dependencies": { "cookie-parser": "~1.4.3", "debug": "~2.6.9", "express": "~4.16.0", "http-errors": "~1.6.2", "jade": "~1.11.0", "mongodb": "^3.0.5", "monk": "^6.0.5", "morgan": "~1.9.0" } }
به طور معمول این نسخهها بهروز و مانند بخش ابتدایی این مقاله هستند. این همان نسخههایی هستند که در این راهنما به صورت ثابت شدهای کار میکنند. اگر میخواهید از نسخههای جدید استفاده کنید باید مسئولیت آن را خودتان قبول کنید. دقت کنید که ما بستههای MongoDB و Monk را طوری اضافه کردهایم که به پایگاه داده ما دسترسی و کنترل داشته باشند.
اکنون به پنجره اعلان فرمان بازگردید، به دایرکتوری nodetest2 بروید و دستور زیر را وارد کنید:
npm install
بدین ترتیب همه وابستگیهای مورد نیاز نصب میشوند. این کار ممکن است مدتی طول بکشد. پس از آن یک کار کوچک نیز هست که باید انجام داد. در همان خط اعلان فرمان دستور زیر را وارد کنید:
mkdir data
این همان جایی است که در زمان مناسب فایلهای پایگاه داده خود را ذخیره خواهیم کرد. اگر میخواهید فایلها را در جای دیگری ذخیره کنید، هیچ اشکالی ندارد فقط باید آن دایرکتوری را پیش از اجرای MongoDB آماده کنید. در ادامه این مقاله پایگاه داده را راهاندازی خواهیم کرد و از این رو تا به اینجا کار ما با آن پایان یافته است. اینک زمان آغاز به کار با صفحههای وب رسیده است.
نکته دیگری که باید اشاره کنیم این است که در این مقاله، همه جا از تورفتگیهای دو فاصلهای استفاده شده است، چون این حالت در جامعه توسعهدهندههای جاوا اسکریپت کاملاً رایج است. البته این مسئله موضوع نزاع بزرگی است؛ اما در همه ویرایشگرهای جدید امکان تبدیل فاصله به tab و برعکس به راحتی وجود دارد و بنابراین جای هیچ نگرانی نیست.
بخش 3 – آغاز کار با HTML
اگر بخواهیم یک وب اپلیکیشن تکصفحهای داشته باشیم، نخستین کاری که باید انجام دهیم، ساخت همان صفحه منفرد است. بنابراین پوشه view-ها را باز کنید و شروع به کار با فایل layout.jade بکنید. این فایل یک قالب است و ما تنها چند تغییر ساده در آن ایجاد میکنیم.
بخش ابتدایی این فایل به صورت زیر است:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body block content
ما قصد داریم بتوانیم دو کار را انجام دهیم. یکی این که JQuery را include کنیم و دوم این که فایل اصلی جاوا اسکریپت خود را include کنیم. بنابراین فایل را ویرایش میکنیم تا به صورت زیر درآید:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body block content script(src='http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js') script(src='/javascripts/global.js')
خوانندگان دقیق ممکن است متوجه شده باشند که global.js هنوز وجود ندارد. این نکته درستی است و این فایل را کمی بعدتر ایجاد خواهیم کرد. در حال حاضر در زمان لود شدن صفحه index با خطای 404 مواجه میشویم. اگر از این نکته ناخرسند هستید، میتوانید هم اینک یک فایل خالی در مسیر /public/javascripts/ ایجاد کنید.
در واقع index.jade تنها فایل HTML است که در ادامه مراحل ساخت وب اپلیکیشن خود نیاز خواهیم داشت. ما در این صفحه موارد زیادی را خواهیم گنجاند. در دنیای واقعی به مقادیر زیادی از CSS هم نیاز خواهیم داشت؛ اما به منظور صرفهجویی در وقت میتوانید صرفاً این فایل (+) را دانلود کنید و آن را در مسیر public/stylesheets/style.css/ قرار دهید. بدین ترتیب یک طرحبندی ساده ارائه میشود که البته میتوانید آن را بنا به سلیقه خود تغییر دهید.
فایل index.jade را باز کنید. یک ساختار بسیار ساده به صورت زیر را شاهد خواهید بود:
extends layout block content h1= title p Welcome to #{title}
این وضعیت چندان هیجانانگیز نیست. بنابراین باید آن را تغییر دهیم. خط h1= title و پاراگراف پس از آن هر دو متغیر title را که در فایل routes/index.js/ تعیین شده است دریافت میکنند و صرفاً پیام «Express» را بیان میکنند. این وضعیت را طوری تغییر میدهیم که در این پاراگراف جمله «Welcome to our test» نمایش یابد. همچنین یک پوشش و یک ساختار جدول برای نمایش لیست کاربران اضافه میکنیم.
extends layout block content h1= title p Welcome to our test // Wrapper #wrapper // USER LIST h2 User List #userList table thead th UserName th Email th Delete? tbody // /USER LIST // /WRAPPER
شاید متوجه شده باشید که هیچ دادهای در جدول وجود ندارد. دلیل این مسئله آن است که قصد داریم این دادهها را از طریق Ajax و از پایگاه داده MongoDB خود دریافت کنیم. بنابراین فعلاً همین طور تنظیم میکنیم و سپس با کمی کدنویسی جاوا اسکریپت میتوانیم دادهها را دریافت کنیم. اگر میخواهید ببینید که صفحه در حال حاضر چگونه به نظر میرسد، میتوانید دستور زیر را در ترمینال وارد کنید:
npm start
سپس به آدرس http://localhost:3000 بروید تا یک صفحه بسیار ابتدایی و ملالآور با یک جدول خالی در آن ببینید. اگر فایل CSS را که قبلاً اشاره کردیم، دانلود کردهاید و stylesheet پیشفرض را بازنویسی کرده باشید، در این صورت صفحه به صورت زیر در آمده است:
در ادامه برخی تغییرات را روی آن اعمال خواهیم کرد.
بخش 4 – پایگاه داده
ما در بخش قبلی این راهنما با روش راهاندازی و اجرای MongoDB آشنا شدهایم، بنابراین اینک سراغ کارهای اصلی میرویم.
در یک ترمینال یا پنجره اعلان فرمان جدید به جایی که MongoDB را نصب کردهاید رفته و دستور زیر را وارد کید:
mongod --dbpath c:\node\nodetest2\data
بدیهی است که اگر تصمیم دارید دادهها را در جای دیگر ذخیره کنید، باید از آن مسیر استفاده کنید. در ادامه میبینید که daemon مربوط به MongoDB راهاندازی میشود و گزارش میکند که منتظر اتصالها است. بدین ترتیب میتوانیم با باز کردن یک پنجره ترمینال یا اعلان فرمان و رفتن به دایرکتوری Mongo و وارد کردن دستور زیر به آن وصل شویم:
Mongo
اینک یک رابط فرمان برای پایگاه داده خود داریم. این مبحث نیز در بخش قبلی مورد بررسی قرار گرفته است و از این رو بیدرنگ سر موضوع اصلی میرویم. ابتدا با وارد کردن دستور زیر به یک پایگاه داده جدید (برای پروژه جدیدمان) سوئیچ کنید:
use nodetest2
اینک باید مجموعه userlist را درون پایگاه داده nodetest2 ایجاد کنیم. این کار در یک مرحله و از طریق درج مقادیر زیر در مجموعه خالی ممکن است:
db.userlist.insert({'username': 'test1','email': 'test1@test.com','fullname': 'Bob Smith','age': 27,'location': 'San Francisco','gender': 'Male'})
دقت کنید که بهتر است ابتدا مقادیری مانند فوق را در یک ویرایشگر متنی با استفاده از tab وارد کنید تا به درستی ببینید که چه کار میکنید و سپس آن را در یک خط جمع کرده و در کنسول MongoDB وارد کنید.
این همه دادههایی است که به آن نیاز داشتیم. البته اجرای این دستور هم چندان ضرورتی نداشت، زیرا قصد داریم یک رویه برای افزودن کاربر بنویسیم، اما وارد کردن دستور فوق برای دیدن این که اتصال به پایگاه داده کار میکند مناسب است.
اینک میتوانید از کنسول MongoDB خارج شده و ترمینال را ببندید؛ اما مطمئن شوید که daemon مربوط به MongoDB در حال اجرا باشد، چون در صورت عدم اجرا، وبسایت ما دیگر نمیتواند به پایگاه داده وصل شود و این مشکل بزرگی محسوب میشود.
بخش 5 – لیست کردن کاربران
اینک زمان آن رسیده است که تغییراتی در app.js که قلب و روح اپلیکیشن ما محسوب میشود، ایجاد کنیم. Express به طور پیشفرض آن را به خوبی آمادهسازی کرده است؛ اما باید برخی hook ها برای Monk اضافه کنیم. این فایل در ابتدا به صورت زیر است:
var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); // catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404)); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app;
ما قصد داریم چند چیز به ابتدای این فایل اضافه کنیم تا به صورت زیر در بیاید:
var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); // Database var mongo = require('mongodb'); var monk = require('monk'); var db = monk('localhost:27017/nodetest2');
در این کد ما ماژول Monk را فراخوانی میکنیم و سپس برخی پارامترهای ابتدایی پیکربندی به آن میدهیم که شامل محل قرارگیری پایگاه داده و نام پایگاه داده مورد استفاده یعنی nodetest2 است.
همچنین باید کاری کنیم که پایگاه داده از سوی درخواستهای HTTP مختلف قابل دسترسی باشد. این کار در بخش قبلی این راهنما صورت گرفته است. به این منظور باید بخش زیر را در فایل پیدا کنیم:
app.use('/', indexRouter); app.use('/users', usersRouter);
این وضعیت کاملاً مهم است، چون ترتیب درج کدهای میانافزار Express بسیار مهم است. اگر کد DB خود را زیر روترها قرار دهیم اپلیکیشن از کار میافتد، چون شیء پایگاه داده به آنها ارسال نمیشود. بنابراین دقیقاً بالاتر از دو خط فوق کد زیر را درج کنید:
// Make our db accessible to our router app.use(function(req,res,next){ req.db = db; next(); });
اینک زمان آن رسیده است که به «مسیریابی» (routing) بپردازیم. Express به طور خودکار یک فایل مسیر /users ایجاد کرده است. ما از این فایل استفاده میکنیم اما در حال حاضر هیچ view ایجاد نمیکنیم. چون این اپلیکیشن، تکصفحهای است و از مسیر و ویوی index برای آن میتوانیم استفاده کنیم. ما از مسیر user برای راهاندازی ورودی خروجی دادهها استفاده میکنیم. این سرویسهایی هستند که برای نمایش، افزودن و حذف کاربران در پایگاه داده مورد استفاده قرار میگیرد. این کار از طریق جاوا اسکریپت صورت میگیرد و نیازی برای مراجعه به آدرسهای مختلف در مرورگر نیست، چون همه دادههای گردآوریشده در صفحه index نمایش مییابند.
بنابراین در انتها مقداری پاکسازی انجام میدهیم. فایل /nodetest2/routes/users.js را در ویرایشگر متنی خود باز کنید. این فایل باید به صورت زیر باشد:
var express = require('express'); var router = express.Router(); /* GET users listing. */ router.get('/', function(req, res) { res.send('respond with a resource'); }); module.exports = router;
اینک بخش کامنت و سه خطر زیر آن را حذف کنید. ما کد خودمان برای /users/userlist را در این بخش قرار خواهیم داد. به این ترتیب کد به صورت زیر درمیآید:
var express = require('express'); var router = express.Router(); /* GET userlist. */ router.get('/userlist', function(req, res) { var db = req.db; var collection = db.get('userlist'); collection.find({},{},function(e,docs){ res.json(docs); }); }); module.exports = router;
هدف از این کد آن است که اگر یک HTTP GET به مسیر /users/userlist اجرا شود، سرور ما یک JSON بازگشت دهد که همه کاربران موجود در پایگاه داده را لیست میکند. بدیهی است که در پروژههای بزرگ باید برای میزان دادههایی که هر بار ارسال میشوند محدودیتی تعیین کرد. این کار برای مثال میتواند از طریق افزودن صفحهبندی در فرانتاند صورت گیرد؛ اما فعلاً این کد برای مقاصد ما کافی است.
فایل users.js را ذخیره کنید. وهلهای از Node که اینک در حال اجرا است را متوقف کنید و آن را با وارد کردن دستور زیر در ترمینال یا پنجره اعلان فرمان، ریاستارت کنید:
npm start
سپس مرورگر خود را رفرش کنید. اینک دقیقاً با همان صفحهای که قبلاً دیدیم مواجه میشوید. دلیل این امر آن است که هنوز هیچ چیزی را متصل کردهایم. اگر علاقهمند هستید، میتوانید به آدرس http://localhost:3000/users/userlist بروید تا خروجی JSON که در ادامه دستکاری خواهیم کرد را ببینید. فعلاً تنها یک کاربر وجود دارد و این همان کاربری است که به صورت دستی از طریق کنسول MongoDB وارد کردهایم.
اینک قصد داریم فایل global.js را ایجاد کنیم. بدین منظور یک سند متنی جدید ایجاد کرده و آن را در مسیر nodetest2/public/javascripts/global.js/ ذخیره میکنیم.
در این بخش به چند نکته در مورد سبک کدنویسی اشاره میکنیم. بهتر است همواره حتی در گزارههای تکخطی و موارد مشابه از آکولاد استفاده کنید. همچنین نامهای متغیر را طوری انتخاب کنید که معنادار باشند و از نامهایی که صرفاً کوتاه هستند اجتناب کنید. همچنین استفاده از گیومههای تکی (‘ ‘) به گیومههای جفتی (“”) ارجحیت دارد. بهتر است توضیحات کد در بالاتر از آن؛ و نه در سمت راست درج شود. همچنین بهتر است در مورد حجم کامنتهای خود نگرانی نداشته باشید، چون در اغلب موارد کدهای جاوا اسکریپت minify میشوند. به همین دلیل استفاده از فاصله خالی نیز اشکالی ایجاد نمیکند. همچنین بهتر است آکولادهای باز را در همان خط کد قرار دهید و نه در یک خط جدید.
البته هر کس سلیقه کدنویسی خاص خود را دارد و ممکن است با برخی از موارد فوق موافق یا مخالف باشید و اجباری نیز در این زمینه وجود ندارد. آنچه مهم است این است که کد خوانا باشد و البته کار کند.
در ابتدا یک تابع برای گرفتن مقادیر دادههای جدول HTML خود مینویسیم. بهتر است بخشهای مختلف جاوا اسکریپت را با عناوین بزرگ و گویا برچسبگذاری کنیم. بنابراین کد به صورت زیر خواهد بود:
// Userlist data array for filling in info box var userListData = []; // DOM Ready ============================================================= $(document).ready(function() { // Populate the user table on initial page load populateTable(); }); // Functions ============================================================= // Fill table with data function populateTable() { // Empty content string var tableContent = ''; // jQuery AJAX call for JSON $.getJSON( '/users/userlist', function( data ) { // For each item in our JSON, add a table row and cells to the content string $.each(data, function(){ tableContent += '<tr>'; tableContent += '<td><a href="#" class="linkshowuser" rel="' + this.username + '">' + this.username + '</a></td>'; tableContent += '<td>' + this.email + '</td>'; tableContent += '<td><a href="#" class="linkdeleteuser" rel="' + this._id + '">delete</a></td>'; tableContent += '</tr>'; }); // Inject the whole content string into our existing HTML table $('#userList table tbody').html(tableContent); }); };
همان طور که میبینید ما از یک متغیر سراسری استفاده کردهایم و تابعهای خود را در سطح بالا تعریف کردهایم. این ایده چندان عالی نیست و آن را صرفاً برای سرعت و سادگی انجام میدهیم. در یک اپلیکیشن واقعی باید یک متغیر منفرد سراسری منفرد تعریف کنیم که پس از آن بتوان مشخصات، متدها و موارد دیگر را بسته به نیاز دریافت کرد و از این رو از ایجاد تداخل یا ایجاد آلودگی در فضای نام اجتناب کرد.
با این حال در حال حاضر از متغیر سراسری خاص خود استفاده میکنیم و به بخش DOM ready میرویم تا در آنجا متد پرکننده جدول به نام ()populateTable را در هنگام آماده بودن اجرا کنیم. پس از آن باید متد پرکننده جدول را تعریف کنیم. این همان جایی است که کد ما جالبتر میشود؛ اما پیچیدگی چندانی به آن اضافه نخواهد شد. یک فراخوانی ساده AJAX از طریق JQuery ایجاد میکنیم، روی JSON بازگشتی حلقهای تعریف میکنیم و یک رشته محتوایی بزرگ ایجاد میکنیم که همه کد HTML ما در آن قرار میگیرد و سپس آن را در جدول موجود تزریق میکنیم.
وقتی مواد فوق به جاوا اسکریپت اضافه شدند میتوانیم مجدداً به مرورگر خود سر بزنیم و آن را رفرش کنیم یا به آدرس http://localhost:3000/ برویم. اگر همه چیز به درستی کار کند، جدول خود را میبینیم که با دادهها پر شده است.
این شروع خوبی محسوب میشود؛ اما هنوز همه آن چه در پایگاه داده قرار دارد نمایش نمییابد. در ادامه یک کادر اطلاعات طراحی میکنیم که مجموعه کامل اطلاعات کاربر را هنگام کلیک روی نام کاربری نمایش دهد.
بخش 6 – دریافت اطلاعات کاربر
ما همچنان روی فایل global.js کارمی کنیم و باید یک خط کوتاه به تابع خود اضافه کنیم. این خط همه دادههای کاربر را درون آرایهای که قبلاً ایجاد کردیم قرار میدهد. در این مورد نیز توصیه میکنیم که در صورتی که هزاران کاربر دارید از این مسیر استفاده نکنید، چون این رویکرد در مورد وجود دادههای با حجم بالا، عملکرد مناسبی نخواهد داشت.
خط زیر را پیدا کنید:
// jQuery AJAX call for JSON $.getJSON('/users/userlist', function(data) {
درست زیر آن کد زیر را مشاهده میکنید:
// For each item in our JSON, add a table row and cells to the content string $.each(data, function(){
بین دو خط فوق کد زیر را وارد کنید:
C:\node\nodetest2\public\javascripts\global.js // Stick our user data array into a userlist variable in the global object userListData = data;
کاری که این کد انجام میدهد، این است که همه دادههای بازگشتی کاربر از پایگاه داده را در یک متغیر سراسری جمع میکند به طوری که میتوانیم بدون نیاز به مراجعه مکرر به پایگاه داده هر بار که فردی روی نام کاربری کلیک میکند، اطلاعات وی را نمایش دهیم. اگر بخواهیم روشنتر توضیح دهیم در عملیات بزرگمقیاس این ایده خوبی محسوب نمیشود. در صورتی که میخواهید اطلاعات هزاران کاربر را به یکباره لود کنید، نباید از این رویکرد استفاده کنید. در این موارد باید از رویکرد صفحهبندی استفاده کرد که در آن صرفاً دادههایی که لازم هستند بارگذاری میشوند.
اما با توجه به مقاصد این راهنما این رویکرد مناسب است. بنابراین به انتهای فایل و بخش تابعها میرویم و کد زیرا را در یک خط جدید در انتها وارد میکنیم:
// Show User Info function showUserInfo(event) { // Prevent Link from Firing event.preventDefault(); // Retrieve username from link rel attribute var thisUserName = $(this).attr('rel'); // Get Index of object based on id value var arrayPosition = userListData.map(function(arrayItem) { return arrayItem.username; }).indexOf(thisUserName);
شاید این کد در ابتدا پیچیده به نظر برسد. در این کد ما ابتدا از یک map. استفاده میکنیم تا یک تابع را به هر شیء در آرایه userListData خودمان اعمال کنیم. به این ترتیب یک آرایه کاملاً جدید ایجاد میشود که شامل هر آن چیزی که است که تابع بازگشت خواهد داد. این تابع یک تابع ناشناس callback است که از پارامتر userObj استفاده میکند و صرفاً نام کاربری را بازگشت میدهد. بنابراین اساساً اگر آرایه دادههای اصلی ما شامل شیءهای کامل کاربر باشند در این صورت آرایه بازگشتی با استفاده از map. تنها شامل نامهای کاربری یعنی چیزی شبیه ['Bob', 'Sue'] خواهد بود.
بنابراین زمانی که آن آرایه ارائه شده از سوی map. را به دست آوریم، میتوانیم با زنجیرهسازی indexOf در ترکیب با نام کاربری انتخابی، اندیس آرایه آن نام کاربری را به دست آوریم. بنابراین Bob اندیس 0 و Sue اندیس 1 خواهد بود. سپس میتوانیم از آن عدد استفاده کنیم که به صورت arrayPosition مرتبسازی شده است تا آرایه دادههای کاربر اصلی خودمان را به دست آورده و دادهها را با استفاده از کد زیر واکشی کنیم.
اگر میخواهید نسخهای از کد که از زنجیرهسازی استفاده نمیکند و اندکی طول بیشتری دارد را ببینید، میتوانید به این صفحه (+) مراجعه کنید. در این زمان باید در تابع showUserInfo بقیه کد را نیز وارد کنید:
// Get our User Object var thisUserObject = userListData[arrayPosition]; //Populate Info Box $('#userInfoName').text(thisUserObject.fullname); $('#userInfoAge').text(thisUserObject.age); $('#userInfoGender').text(thisUserObject.gender); $('#userInfoLocation').text(thisUserObject.location); };
اکنون این تابع باید به صورت زیر در آمده باشد:
// Show User Info function showUserInfo(event) { // Prevent Link from Firing event.preventDefault(); // Retrieve username from link rel attribute var thisUserName = $(this).attr('rel'); // Get Index of object based on id value var arrayPosition = userListData.map(function(arrayItem) { return arrayItem.username; }).indexOf(thisUserName); // Get our User Object var thisUserObject = userListData[arrayPosition]; //Populate Info Box $('#userInfoName').text(thisUserObject.fullname); $('#userInfoAge').text(thisUserObject.age); $('#userInfoGender').text(thisUserObject.gender); $('#userInfoLocation').text(thisUserObject.location); };
اکنون باید یک تابع را در زمان کلیک شدن نام کاربری اجرا کنیم، بنابراین به بخش DOM ready بازمیگردیم و زیر فراخوانی اولیه دریافت دادههای جدول کد زیر را اضافه میکنیم:
// Username link click $('#userList table tbody').on('click', 'td a.linkshowuser', showUserInfo);
این کد کاملاً سرراست است. ما شیء کاربر خود را یافتهایم و span ها را با دادهها پر میکنیم. اما نکته این جاست که هنوز خود span ها وجود ندارند! ابتدا فایل global.js را ذخیره کنید و سپس فایل /views/index.jade را باز کنید. باید کد زیر را درست پس از خط #wrapper و بالاتر از جدول لیست کاربران اضافه کنید:
// USER INFO #userInfo h2 User Info p strong Name: | <span id='userInfoName'></span> br strong Age: | <span id='userInfoAge'></span> br strong Gender: | <span id='userInfoGender'></span> br strong Location: | <span id='userInfoLocation'></span> // /USER INFO
به خاطر داشته باشید که تورفتگیها در این کد بسیار مهم هستند. اگر ترتیب این تورفتگیها به هم بخورد، کد از کار میافتد. اکنون کل فایل index.jade باید به صورت زیر باشد:
extends layout block content h1= title p Welcome to our test // Wrapper #wrapper // USER INFO #userInfo h2 User Info p strong Name: | <span id='userInfoName'></span> br strong Age: | <span id='userInfoAge'></span> br strong Gender: | <span id='userInfoGender'></span> br strong Location: | <span id='userInfoLocation'></span> // /USER INFO // USER LIST h2 User List #userList table thead th UserName th Email th Delete? tbody // /USER LIST // /WRAPPER
فایل را ذخیره کنید و به مرورگر مراجعه کنید تا نگاهی به اپلیکیشن خود بیندازیم. آدرس http://localhost:3000 را رفرش کنید تا یک کادر اطلاعات کاربر را که در سمت چپ قرار گرفته است ببینید. اگر روی نام کاربری «test1» در جدول کلیک کنید، اطلاعات مربوط به کاربر Bob را مشاهده میکنید.
اینک وقت آن رسیده است که برخی کاربرهای دیگر را از طریق POST کردن با AJAX به پایگاه داده خود اضافه کنیم.
بخش 7 – افزودن کاربران
نخستین چیزی که نیاز داریم یک مجموعه از فیلدهای فرم است که به وسیله آن بتوانیم کاربرها را اضافه کنیم. بنابراین همچنان به کار روی فایل views/index.jade/ ادامه داده و کدهای مربوط به آن را اضافه میکنیم.
دقیقاً زیر لیست کاربران و بالاتر از محل بسته شدن کامنت wrapper کد زیر را وارد کنید:
// ADD USER h2 Add User #addUser fieldset input#inputUserName(type='text', placeholder='Username') input#inputUserEmail(type='text', placeholder='Email') br input#inputUserFullname(type='text', placeholder='Full Name') input#inputUserAge(type='text', placeholder='Age') br input#inputUserLocation(type='text', placeholder='Location') input#inputUserGender(type='text', placeholder='gender') br button#btnAddUser Add User // /ADD USER
این کد کاملاً سرراست است. ورودیهای متنی برای هر بخش از اطلاعات که برای ایجاد یک کاربر جدید نیاز داریم در این کد تعریف شدهاند. یک دکمه برای کلیک کردن قرار دادهایم. در حال حاضر این دکمه هیچ کاری انجام نمیدهد؛ اما قصد داریم این رویه را اصلاح کنیم.
این آخرین تغییراتی است که روی فایل ویوی خود ایجاد میکنیم. اطمینان حاصل کنید که کد موجود در ویرایشگر فایل متنی به صورت زیر است:
extends layout block content h1= title p Welcome to our test // Wrapper #wrapper // USER INFO #userInfo h2 User Info p strong Name: | <span id='userInfoName'></span> br strong Age: | <span id='userInfoAge'></span> br strong Gender: | <span id='userInfoGender'></span> br strong Location: | <span id='userInfoLocation'></span> // /USER INFO // USER LIST h2 User List #userList table thead th UserName th Email th Delete? tbody // /USER LIST // ADD USER h2 Add User #addUser fieldset input#inputUserName(type='text', placeholder='Username') input#inputUserEmail(type='text', placeholder='Email') br input#inputUserFullname(type='text', placeholder='Full Name') input#inputUserAge(type='text', placeholder='Age') br input#inputUserLocation(type='text', placeholder='Location') input#inputUserGender(type='text', placeholder='gender') br button#btnAddUser Add User // /ADD USER // /WRAPPER
و در مرورگر نیز ظاهر وب اپلیکیشن ما باید به صورت زیر باشد:
نکته آخر این است که Jade در مورد استفاده از tab یا Space کاملاً حساس است. اگر با خطایی مواجه شوید باید همه تورفتگیها را بررسی کنید و سپس app.js را ریاستارت کنید. اگر همه چیز درست باشد میتوانید فایل index.jade را ببندید و فایل routes/users.js/ را باز کنید. چون اینک زمان POST کردن رسیده است.
افزودن یک کاربر کار چندان دشواری محسوب نمیشود. روند این کار کاملاً شبیه به بازیابی اطلاعات کاربر است؛ به جز این که به جای متد ()find از متد ()insert روی پایگاه داده استفاده میکنیم. در زیر کد لیست بندی کاربر و بالاتر از module.exports باید کد زیر را وارد کنید:
/* POST to adduser. */ router.post('/adduser', function(req, res) { var db = req.db; var collection = db.get('userlist'); collection.insert(req.body, function(err, result){ res.send( (err === null) ? { msg: '' } : { msg: err } ); }); });
این کد به منظور post کردن برخی دادهها و درج آن در مجموعه userlist در پایگاه داده استفاده میشود. اگر همه چیز به خوبی پیش برود یک رشته خالی بازگشت مییابد. اگر اجرا با مشکلی مواجه شود، پیام خطایی که از سوی پایگاه داده صادر شده، بازگشت مییابد.
از نظر مقاصد آموزشی در این راهنما، کد فوق تا به این حد کفایت میکند. اینک میتوانید فایل routes/users.js/ را ذخیره کنید. این همه آن چیزی است که نیاز داریم. اکنون همه کاری که باید انجام دهیم این است که کاری کنیم دکمه و فرم مربوطه به اجرای کد بپردازند. بدین منظور به فایل global.js بروید.
ابتدا شنونده رویداد خود را در بخش DOM ready و درست زیر بخش کلیک روی نام لیست کاربران مینویسیم. این کد تنها یک خط است:
// Add User button click $('#btnAddUser').on('click', addUser);
شاید متوجه شده باشید که مشغول فراخوانی یک تابع addUser هستیم. بدیهی است که باید این تابع را بسازیم. این تابع بزرگترین تابع ما در این راهنما خواهد بود. این کد باید برخی کارهای تکراری در مورد اعتبارسنجی انجام دهد و سپس اگر همه فیلدهای فرم شده باشد، دادهها را کامپایل کرده و از طریق AJAX به سرویس adduser سرور POST کند.
این تابع بزرگی محسوب میشد. ما این کد را به صورت کامل توضیح گذاری کردهایم تا نشان دهیم که در هر مرحله چه اتفاقی میافتد. این کد را به انتهای فایل global.js اضافه کنید:
// Add User function addUser(event) { event.preventDefault(); // Super basic validation - increase errorCount variable if any fields are blank var errorCount = 0; $('#addUser input').each(function(index, val) { if($(this).val() === '') { errorCount++; } }); // Check and make sure errorCount's still at zero if(errorCount === 0) { // If it is, compile all user info into one object var newUser = { 'username': $('#addUser fieldset input#inputUserName').val(), 'email': $('#addUser fieldset input#inputUserEmail').val(), 'fullname': $('#addUser fieldset input#inputUserFullname').val(), 'age': $('#addUser fieldset input#inputUserAge').val(), 'location': $('#addUser fieldset input#inputUserLocation').val(), 'gender': $('#addUser fieldset input#inputUserGender').val() } // Use AJAX to post the object to our adduser service $.ajax({ type: 'POST', data: newUser, url: '/users/adduser', dataType: 'JSON' }).done(function( response ) { // Check for successful (blank) response if (response.msg === '') { // Clear the form inputs $('#addUser fieldset input').val(''); // Update the table populateTable(); } else { // If something goes wrong, alert the error message that our service returned alert('Error: ' + response.msg); } }); } else { // If errorCount is more than 0, error out alert('Please fill in all fields'); return false; } };
توصیه ما این است که این کد را به صورت کامل تایپ کنید و صرفاً به کپی/چسباندن اکتفا نکنید. چون به این ترتیب بهتر میتوانید درک کنید که کد چه کاری انجام میدهد و این روش مانند نگاه کردن صرف به کد نیست. در هر حال این اطلاعات را در فایل جاوا اسکریپت وارد کرده و فایل را خیره کنید. اینک باید اپلیکیشن node را ریاستارت کنید و سپس http://localhost:3000 را رفرش کنید. اینک همان چیزی را که قبلاً دیدید، مشاهده میکنید؛ به جز این که در حال حاضر فرم به طور کامل کار میکند و میتوانید اطلاعاتی را در آن وارد کنید.
روی add کلیک کنید و فرم را تحویل دهید تا دادهها به پایگاه داده ارسال شوند. برای مشاهده عملی این دادهها باید جدول را رفرش کنید. روی نام کاربری جدید در جدول کلیک کنید تا اطلاعات کاربر جدید را در کادر اطلاعات مشاهده کنید. ممکن است متوجه شوید که این کار بدون نیاز به هیچ رفرش صفحه رخ میدهد و این همان کارکردی است که به دنبال آن بودیم.
اینک میتوانید چندین کاربر دیگر نیز در پایگاه داده وارد کنید. در مورد تعداد کاربران نگران نباشید، چون در بخش بعدی روش حذف کاربران را نیز آموزش خواهیم داد.
بخش 8 – حذف کاربران
حذف کاربران آسانتر از افزودن آنها است؛ اما بخش بزرگی از این فرایند یکسان است. در واقع ما تنها باید فایلهای route و global.js را بهروزرسانی کنیم و لازم نیست به فایل index.jade دست بزنیم، زیرا لینکهای مربوط به delete را قبلاً در آنجا قرار دادهایم.
کار خود را ابتدا با فایل route آغاز میکنیم. به این منظور فایل routes/users.js/ را از کنید و کد زیر را در انتهای آن و درست بالاتر از module.exports وارد کنید:
/* DELETE to deleteuser. */ router.delete('/deleteuser/:id', function(req, res) { var db = req.db; var collection = db.get('userlist'); var userToDelete = req.params.id; collection.remove({ '_id' : userToDelete }, function(err) { res.send((err === null) ? { msg: '' } : { msg:'error: ' + err }); }); });
این کد کاملاً سرراست است و ما یک پارامتر ID به آن ارسال میکنیم. بدین ترتیب URI که ارجاع میدهیم به عنوان مثال به صورت /deleteuser/12345 خواهد بود و ما آن را در کد خود با req.params.id مورد ارجاع قرار میدهیم و MongoDB با فیلد id_ که برای هر مدخل در مجموعه ایجاد میکند با آن تطبیق مییابد. در این مورد نیز همانند روبه افزودن کاربر اگر همه چیز به درستی پیش رود، یک رشته خالی بازگشت مییابد و اگر اشکالی رخ دهد پیام خطای صادر شده از سوی پایگاه داده MongoDB را باز میگرداند.
فایل routes/users.js/ را ذخیره کنید. در ادامه این بخش را به کد جاوا اسکریپت خود وصل میکنیم. اینک به فایل global.js بازمیگردیم تا همه چیز را کامل کنیم. ما قبلاً لینک delete را به یک خصوصیت که محتوی id است پر کردهایم. این کار در این خط فایل global.js صورت میگیرد:
tableContent += '<td><a href="#" class="linkdeleteuser" rel="' + this._id + '">delete</a></td>';
اینک میخواهیم یک رویه کوتاه delete در بخش DOM ready اضافه کنیم:
// Delete User link click $('#userList table tbody').on('click', 'td a.linkdeleteuser', deleteUser);
به ساختاری که هنگام کار با متد «on» جی کوئری برای دریافت لینکهای درج شده به صورت دینامیک استفاده میکنیم توجه کنید. شما باید یک عنصر استاتیک را در صفحه نخست ارجاع دهید. به همین دلیل است که سلکتور ما عنصر tbody جدول است که صرف نظر از افزودن یا حذف کاربران همیشه ثابت میماند. سپس لینکهای خاصی را که میخواهیم در پارامترهای on. دریافت کنیم را تعیین میکنیم. تابع deleteUser خود را در انتهای فایل درج میکنیم:
// Delete User function deleteUser(event) { event.preventDefault(); // Pop up a confirmation dialog var confirmation = confirm('Are you sure you want to delete this user?'); // Check and make sure the user confirmed if (confirmation === true) { // If they did, do our delete $.ajax({ type: 'DELETE', url: '/users/deleteuser/' + $(this).attr('rel') }).done(function( response ) { // Check for a successful (blank) response if (response.msg === '') { } else { alert('Error: ' + response.msg); } // Update the table populateTable(); }); } else { // If they said no to the confirm, do nothing return false; } };
به خاطر داشته باشید که سرور node باید ریاستارت شود و سپس به آدرس http://localhost:3000 بروید تا آن را تست کنید. این وضعیت سادهتر از تابع addUser است، چون نیازمند یک تأیید ساده جاوا اسکریپت است:
اگر کاربر از حذف آیتم مطمئن باشد، درخواست مربوطه به سرویس delete سرور میرود و ID مربوطه ارسال میشود و در نهایت جدول بهروزرسانی میشود تا نشان دهد که کاربر حذف شده است.
بدین ترتیب کارکرد حذف نیز ایجاد شده است. اکنون میتوانید هر تعداد کاربر که میخواهید به پایگاه داده وب اپلیکیشن اضافه، مشاهده، یا حذف کنید. از آنجا که این راهنما تا به همین جا نیز به قدر کافی طولانی شده است قصد داریم مراحل بهروزرسانی کاربر را دیگر مطرح نکنیم؛ اما امیدواریم با مواردی که در این دو مقاله مطالعه کردید، ایده خوبی از روش انجام این کار کسب کرده باشید. ابتدا باید اطلاعات را از پایگاه داده GET کنید و بر همین اساس یک فرم بسازید و سپس تغییراتی را که کاربر روی آن فرم اعمال کرده است را در پایگاه داده PUT میکنید و در نهایت با رفرش جدول بهروزرسانی شده است.
در واقع این یک تمرین خوب برای شما است تا معلوماتی را که از این راهنما کسب کردهاید را عملاً تست کنید. شما باید فایلهای views/index.jade ،/routes/users.js ،/app.js/ و public/javascripts/global.js/ را مانند روشهایی که در مورد افزودن و حذف کاربران انجام دادیم، ویرایش کنید.
سخن پایانی
به این ترتیب به پایان این راهنمای طولانی در مورد ساخت وب اپلیکیشن Restful با Node.js رسیدیم. بدیهی است که وب اپلیکیشن Restful ما کاملاً ابتدایی محسوب میشود. این اپلیکیشن از هیچ نظر عملکرد بهینهای ندارد. همچنین مباحث انعطافپذیری، نگهداری کد یا افزودن ویژگیهای جدید در آن رعایت نشدهاند. هدف از این پروژه صرفاً معرفی مقدمات و مبانی یک وب اپلیکیشن تکصفحهای مبتنی بر AJAX برای دستکاری دادهها به وسیله Node / Express / MongoDB بوده است.
اگر این مطلب برایتان مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش جاوا اسکریپت (JavaScript)
- مجموعه آموزشهای مهندسی نرم افزار
- Node.js چیست و چه نقشی در توسعه وب دارد؟ — به زبان ساده
- Node.js و ابزارها و تکنیکها برای ساخت سرورهای قدرتمند و سریع
- رویدادها (Events) در Node.js — راهنمای استفاده صحیح
==