skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER
    import { ParsedQs } from 'qs';
    
    
    const toArray = (
      value: string | string[] | ParsedQs | ParsedQs[] | undefined
    ): string[] => {
      if (typeof value === 'string') {
    return value.split(','); // Split if it's a comma-separated string
      } else if (Array.isArray(value)) {
    return value.filter((v) => typeof v === 'string') as string[]; // Ensure it's an array of strings
      }
      return [];
    };
    
    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 colorArray = toArray(color);
       const sizearray = toArray(size).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,
        });
      }
    };
    

  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search