node.jsmongodbexpresssearchfilter

how to make api to filter multiple colors. mongodb and expresss


I have built a clothing store backend based on Express and Mongodb, In which there is one api which is used to filter and search. In this API we cannot filter based on multiple colors or size. the endpoint would be /products?color=green,blue&page=1&size=26,28.... How to achieve this functionality?

const getAllProducts = async (req: Request, res: Response) => {
  const { page, limit, search, color, size, category, brand } = req?.query;

  let currentPage = Number(page) || 0;
  let docLimit = Number(limit) || 10;
  let searchDoc = search || '';

  try {
    const products = await Product.aggregate([
      {
        $lookup: {
          from: 'stores',
          localField: 'store',
          foreignField: '_id',
          as: 'store',
        },
      },
      {
        $match: {
          $or: [
            {
              title: { $regex: searchDoc, $options: 'i' },
            },
            {
              'store.name': { $regex: searchDoc, $options: 'i' },
            },
          ],
          ...(color ? { color: { $regex: color, $options: 'i' } } : {}),
          ...(brand ? { 'store.name': { $regex: brand, $options: 'i' } } : {}),
          ...(size
            ? {
                size: {
                  $in: [Number(size)],
                },
              }
            : {}),
          ...(category ? { $expr: { $eq: ['$category', category] } } : {}),
        },
      },
      {
        $skip: currentPage * docLimit,
      },
      {
        $limit: docLimit,
      },
    ]);

    const total = await Product.find().countDocuments();

    const data = {
      products,
      total,
    };

    res.status(200).json({
      statusCode: 200,
      success: true,
      message: 'fetch successfully',
      data,
    });
  } catch (error) {
    if (error instanceof Error) {
      res.status(500).json({
        statusCode: 500,
        success: false,
        message: error.message,
        error,
      });
    }
  }
};

Product Schema

const productSchema = new mongoose.Schema<IProduct>(
  {
    title: {
      type: String,
      required: true,
    },
    description: {
      type: String,
    },
    color: {
      type: [String],
      required: true,
      enum: [
        'black',
        'white',
        'blue',
        'brown',
        'copper',
        'gold',
        'green',
        'grey',
        'navy',
        'pink',
        'orange',
      ],
    },
    price: {
      type: Number,
      required: true,
    },
    discount: {
      type: Number,
      default: 0,
    },
    rating: {
      type: Number,
      default: 0,
    },
    category: {
      type: String,
      enum: [
        'shirts',
        'jeans',
        'jacket',
        'top',
        'skirts',
        'pants',
        'dresses',
        't-shirts',
        'hats',
        'socks',
      ],
    },
    store: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'stores',
    },
    size: {
      type: [Number],
    },
    quantity: {
      type: Number,
      default:5
    },
    productImg: [
      {
        src: {
          type: String,
        },
      },
    ],
  },
  { timestamps: true }
);

I am expecting to get products based on the provided colors or sizez.


Solution

  • I think you can .split() the color and size and use $in operator to match colors or sizes.

    const getAllProducts = async (req, res) => {
      const { page, limit, search, color, size, category, brand } = req.query;
    
      let currentPage = Number(page) || 0;
      let docLimit = Number(limit) || 10;
      let searchDoc = search || '';
    
      //if color and size exist, convert them to array
      const colorsArray = color ? color.split(',') : [];
      const sizesArray = size ? size.split(',').map(Number) : [];
    
      try {
        const products = await Product.aggregate([
          {
            $lookup: {
              from: 'stores',
              localField: 'store',
              foreignField: '_id',
              as: 'store',
            },
          },
          {
            $match: {
              $or: [
                {
                  title: { $regex: searchDoc, $options: 'i' },
                },
                {
                  'store.name': { $regex: searchDoc, $options: 'i' },
                },
              ],
             //add $in 
              ...(colorsArray.length ? { color: { $in: colorsArray } } : {}),
              ...(sizesArray.length ? { size: { $in: sizesArray } } : {}),
              ...(brand ? { 'store.name': { $regex: brand, $options: 'i' } } : {}),
              ...(category ? { $expr: { $eq: ['$category', category] } } : {}),
            },
          },
          {
            $skip: currentPage * docLimit,
          },
          {
            $limit: docLimit,
          },
        ]);
    
        const total = await Product.find().countDocuments();
    
        const data = {
          products,
          total,
        };
    
        res.status(200).json({
          statusCode: 200,
          success: true,
          message: 'fetch successfully',
          data,
        });
      } catch (error) {
        res.status(500).json({
          statusCode: 500,
          success: false,
          message: error.message,
          error,
        });
      }
    };

    This code might not work if you copy and paste cuz I haven't run it. But looking at it, it should imo.

    Hope this helps you