<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های محمدحسن تبریزی</title>
        <link>https://virgool.io/feed/@m_72351797</link>
        <description></description>
        <language>fa</language>
        <pubDate>2026-04-15 10:33:05</pubDate>
        <image>
            <url>https://static.virgool.io/images/default-avatar.jpg</url>
            <title>محمدحسن تبریزی</title>
            <link>https://virgool.io/@m_72351797</link>
        </image>

                    <item>
                <title>یک کلاس برای ایجاد query که می تونه sort,paginate,limit, limitFields انجام بده در  expressJS</title>
                <link>https://virgool.io/@m_72351797/%DB%8C%DA%A9-%DA%A9%D9%84%D8%A7%D8%B3-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-query-%DA%A9%D9%87-%D9%85%DB%8C-%D8%AA%D9%88%D9%86%D9%87-sortpaginatelimit-limitfields-%D8%A7%D9%86%D8%AC%D8%A7%D9%85-%D8%A8%D8%AF%D9%87-%D8%AF%D8%B1-expressjs-nqd4bm5qkkss</link>
                <description>یکی از چالش هایی که تو برنامه نویسی بک اند بهش بر می خوریم  query ها و نحوه ی مدیریت اونا هست. توی این پست می خوام  خیلی شیک و تمیز و با استفاده از یک کلاس، این چالش رو برای همیشه، ساده کنم.فرض کنیم یه پایگاه داده ای از خانه ها داریم که هر خونه یکسری ویژگی ها داره و می خواهیم بر اساس این ویژگی ها کل خونه هامون رو سرچ کنیم یا به عبارتی تخصصی تر با استفاده از query string خونه هارو فیلتر کنیم. حالا queryString چیه؟فرض کنید ما با استفاده از مسیر api/houses می تونیم همه ی خونه ها رو get  کنیم ولی می خواهیم که کاربر با استفاده از یه سری اهرم ها تو قسمت فرانتمون بجای اینکه همه ی خونه ها رو  get  کنه بتونه بر اساس یه سری ویژگی ها این خونه ها رو get  کنه.پس کاربر با استفاده از اون اهرم ها یک queryString می سازه و به سرور ارسال می کنه. پس مسیری که کاربر میخاد بهش دسترسی پیدا کنه همونه ولی با یه سری ویژگی های جدید مثلا کاربر می خواد با استفاده از: api/houses?city=tabriz&amp;price=500 به همه خونه هایی که تو شهر تبریز هستند و قیمتشون دقیقا 500 است دسترسی پیدا کنه.به این قسمتی که بعد از علامت سوال هست می گیم queryString.چطوری توی expressJs  پیادش کنیم؟برای مسیر api/houses  یک کنترلر می نویسیم که اگه این مسیر از طرف کاربر خواسته شد اون کنترلر خوانده بشه و همه ی خونه ها رو تحویل کاربر بده.Const getAllHouse = async(req,res,next) =&gt; {         Const houses = await HouseModel.find();          Res.status(200).json({                  Status:”success”,                  houses              })}حالا ما می خواییم بر اساس queryString  همه ی خانه ها رو فیلتر کنیم و به کاربر بدیم پس توی این پست، فقط با این کنترلر و داخل این کنترلر فقط با const houses = await HouseModel.find سر و کار خواهیم داشت.دو راه واسه هندل کردن این موضوع داریم.راهکار اول:داخل .find() یک آبجکت قرار بدیم تا براساس اون آبجکت  find بشه:Const houses = await HouseModel.find({City:”tabriz”,Price=500});راهکار دوم:بعد از  find()یکسری از متدهای مخصوص mongooseرو زنجیر کنیم.Const houses = await HouseModel.find().where(“city”).equals(“tabriz”).where(“price”).equals(500);هر دو راهکار رو می تونیم برای فیلتر کردن استفاده کنیم که من از روش اول استفاده خواهم کرد.مورد دیگه ای که باید بهش اشاره کنم اینه که یه سری از فیلتر ها هم داریم که به خود خونه ها ربط ندارن و هر جای دنیا که خواستیم یه بکی بنویسیم باید اونارو مدنظر بگیریم مثلا page  واسه pagination  هست sort واسه مرتب کردن اطلاعاتی هست که پیدا شده limitواسه ماکسیمم تعدادی هست که بر می گردونه fields هم برای پیدا کردن یه سری فیلدهای خاص است که در ادامه در موردشون می نویسم.گام اول: در گام اول می خواهیم که اینارو یعنی همین مشهور معروفارو از queryStringامون حذف کنیم و بریم سراغ فیلدهایی که داخل اطلاعات خودمون هستند.برای مثال یک  queryStringداریم api/houses?page=2&amp;sort=date&amp;limit=6&amp;city=tabriz&amp;price=500Const queryObj = {…req.query} // querystringرو از آبجکت ریکویست بگیر  =&gt; {page:2,sort:date,city:tabriz}Const excludedFields = [“page”,”sort”,”limit”,”fields”]; //  مشهورارو حذف کنexcludedFields.forEach(el =&gt; delete queryObj[el]); // queryObj =&gt; {city:tabriz,price=500}const query = HouseModel.find(queryObj); // حالا با فراخوانی کردن query، همه ی خونه هایی که در تبریز هستند و قیمتشون هم 500 هست رو بدست میاریم. با کد زیرConst houses = await query;گام دوم: پیاده سازی بزرگتر مساوی یا بزرگتر از یا کوچکتر از و کوچکتر مساوی Api/houses?price[gte]=300با این query می خواهیم که خونه هایی با قیمت بزرگتر از 300 رو پیدا کنیم. اینجا یک مشکل کوچولو داریم و اونم اینه که ما موقعی که می خواهیم با استفاده از  mongoose  از قابلیت gte,gt,lte,ltاستفاده کنیم از $استفاده می کنیم ولی موقعی که این مسیر رو به کنترلرمون می فرستیم و از اونجا query  رو console.logمی کنیم اون علامت $ رو نمی تونیم پیدا کنیم یعنی چی?{price:{ $gte:300}} // این همونیه که ما لازم داریم و باید بدیمش به  mongoose{price:{gte:300} // این اونیه که ما از  req.query تحویل می گیریمراهکار چیه؟می آییم با استفاده از یه reqex قبل از  gt,gtr,lt,lte یک $اضافه می کنیم.let queryStr = JSON.stringify(queryObj); // آبجکت رو به استرینگ تبدیل کنqueryStr.replace(“/\b(gt|gte|lt|lte)\b/g, match =&gt; &#x60;$${match}&#x60;); //بعد توش بگرد دنیال اون 4تا کلمه و قبلش یه علامت دلار بزار با استفاده از  template stringconst newQueryObj = JSON.parse(queryStr)); // استرینگ رو که بهش علامت دلارو اضافه کردی برگردون به حالت قبلشlet query = HouseModel.find(newQueryObj);گام سوم: بعد از اینکه فیلدهای داخل اطلاعاتمون رو سروسامان دادیم برگردیم به فیلدهایی که حذفشون کردیم.مرتب کردن:فرض کنیم اطلاعاتمون رو می خواهیم بر اساس  priceمرتب کنیم.Api/houses?sort=priceIf(req.query.sort){ // اگه تو ریکویستمون sort دیدیquery = query.sort(req.query.sort) //.sort رو به کویری که ساختیم زنجیر کن}حالا که قابلیت مرتب کردن رو هم بهش اضافه کردیم یه سوال پیش میاد. شاید کاربر می خواهد اطلاعات رو بر اساس چندتا فیلد مرتب کنه?Api/houses?sort=price,area,…در این حالت میریم سراغ  JSو req.query.sort رو بر اساس ویرگول از هم جدا می کنیم و سپس با فاصله خالی به همدیگه می چسبونیم.If(req.query.sort){Const sortBy = req.query.sort.split(“,”).join(“ “);query = query.sort(sortBy)}قدم بعدی اینه که یه حالت پیش فرض به  sort  اضافه کنیم که اگه کاربر sort رو خالی گذاشت با توجه به اون  sortکنه.If(req.query.sort){Const sortBy = req.query.sort.split(“,”).join(“ “);query = query.sort(sortBy) //chaining new method SORT to the query}else{    ///این قسمت رو بهش اضافه می کنیمquery = query.sort(“-createdAt”);}پروجکشن:با استفاده از fieldLimitting می خواهیم به کاربر این قابلیت رو بدیم که خودش انتخاب کنه که چه فیلدهایی رو می خواهد دریافت کنه. مثلا انتخاب می کنه که فقط قیمت، منطقه و طبقات خونه هایی رو که می خواهد ببینه رو براش نشون بده.Api/houses?fields=area,price,floorsIf(req.query.fields){Const fields =  req.query.fields.split(“,”).join(“ “);query = query.select(fields);}else{ // اگه کاربر چیزی وارد نکرد همه چی رو بهش نشون بده غیر از ورژنQuery = query.select(-__V); // don’t show version}پجینیشن:با استفاده از این ویژگی این قابلیت رو ایجاد می کنیم که کاربر در هر صفحه به تعداد مشخصی از اطلاعات دسترسی داشته باشه.Api/houses?page=2&amp;limit=10 //مثلا صفحه ی دوم رو نشون بدهکه هر صفحه 10 تا داده داشته باشهConst page = req.query.page * 1 || 1; Const limit = req.query.limit * 1 || 100; //تعدا داده هایی که تو هر صفحه نشون داده میشهConst skip = (page - 1) * limit; // این تعداد از داده هارو بپپپرquery  = query.skip(skip).limit(limit); //  skip و limit رو به کویری مون زنجیر می کنیمچالشی که اینجا داریم اینه که وقتی کاربر صفحه ای رو خواست که تعداد اطلاعاتمون کم بود و تو اون صفحه اطلاعات نداشتیم چیکار کنیم؟If(req.query.page){Const maxHouseCount = await HouseModel.countDocument(); // با استفغاده از یه متد  mongoose تعداد کل خونه هامون رو ازش می گیریمIf(skip&gt;= maxHouseCount) Throw new Error(); }کارمون تقریبا تمومه.می تونیم یکسری مسیرها هم برای ویژگی های خاص یا عمومی ایجاد کنیم مثلا ارزان ترین ها گران ترین ها و ....Api/houses/cheapestدر این حالت یک کنترلر جدا واسه این مسیر می نویسیم که داخل اون کنترلر باید به reqآبجکت، query اضافه کنیم و سپس با استفاده از  next اون  req  رو به کنترلر اصلی بفرستیم.Router(‘/houses/cheapest”,cheapestControlle,getAllHouseController);// اول برو تو کنترلر مخصوص ارزانترین ها اونجا  req رو دستکاری کن بعد برو تو کنترلر اصلیConst cheapestController = (req,res,next) =&gt; {Req.query.sort(“-price);Next();}به این ترتیب می تونیم انواع مسیرهای مشهور برای سرچ کردن اضافه کنیم.ریفکتورینگ کدمون:این کد ها رو داخل کنترلر اصلیمون نوشتیم و کاملا صحیح داره عملیاتش رو انجام می ده. حالا واسه اینکه از این کد واسه مدل های دیگه مون هم استفاده کنیم چیکار کنیم؟خیلی شیک و مجلسی یه کلاس درست می کنیم که دوتا ورودی می گیره که اولی مدلمون هست و دومی آبجکت  queryاست.Class APIQueryFeatures{constructor(query,queryString){//اولی همون HouseModel امون بود که حالا می تونیم انواع مدل ها رو بهش بدیمthis.query = query;//دومی هم همون req.query مون بودthis.queryString = queryString;}//  فیلتر کن اون چهارتا فیلد مشهور معروفی که همه جا داریمشfilter(){const queryObj = { ...this.queryString};const excludedFields = [&#x27;page&#x27;, &#x27;sort&#x27;, &#x27;limit&#x27;, &#x27;fields&#x27;];// این چهارتارو می گمexcludedFields.forEach((el) =&gt; delete queryObj[el]);//add $ to query objectlet queryStr = JSON.stringify(queryObj);queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g,(match) =&gt; &#x60;$${match}&#x60;);this.query = this.query.find(JSON.parse(queryStr));return this;}sort(){if (this.queryString.sort) {const sortBy = this.queryString.sort.toString().split(&#x27;,&#x27;).join(&#x27; &#x27;);this.query = this.query.sort(sortBy);} else {this.query = this.query.sort(&#x27;-createdAt&#x27;);}return this;}limitFields(){if (this.queryString.fields) {const fields = this.queryString.fields.toString().split(&#x27;,&#x27;).join(&#x27; &#x27;);this.query = this.query.select(fields);}return this;}paginate(){const page = this.queryString.page * 1 || 1;const limit = this.queryString.limit * 1 || 10;const skip = (page - 1) * limit;this.query = this.query.skip(skip).limit(limit);return this;}}Module.exports = APIQueryFeatures;خوب حالا یه کلاس داریم که می تونه کلی کارای خفن انجام بده.واسه فراخوانیش هم بصورت زیر عمل می کنیم.const features = new APIQueryFeatures(Model.find(),   req.query).filter().sort().limitFields().paginate();//این چهارتا متدی که واسه کلاسمون نوشتیم رو زنجیر می کنیم. و بعد فراخوانیش می کنیم. البته مواقعی که نمی خواهیم از یکی از این چهارتا استفاده کنیم می تونیم اونو زنجیر نکنیم.const   document = await features.query;</description>
                <category>محمدحسن تبریزی</category>
                <author>محمدحسن تبریزی</author>
                <pubDate>Fri, 28 May 2021 21:20:40 +0430</pubDate>
            </item>
            </channel>
</rss>