Search Filters: From SQL to Web Applications

Understanding Search Filters

Think of search filters like sifting through a library. Instead of looking through every book, you might want to narrow down your search by:

  • Genre (Fiction, Non-fiction, etc.)
  • Publication Year
  • Author
  • Language

In web applications, these filters help users find exactly what they're looking for without manually scanning through all available items.

Basic Filter Implementation

Let's start with a simple e-commerce product filter system:


// models/Product.js
const Product = sequelize.define('Product', {
    name: DataTypes.STRING,
    price: DataTypes.DECIMAL,
    category: DataTypes.STRING,
    color: DataTypes.STRING,
    size: DataTypes.STRING
});

// Basic filter implementation
const filterProducts = async (filters) => {
    const whereClause = {};
    
    if (filters.category) {
        whereClause.category = filters.category;
    }
    
    if (filters.color) {
        whereClause.color = filters.color;
    }
    
    if (filters.size) {
        whereClause.size = filters.size;
    }
    
    if (filters.minPrice) {
        whereClause.price = {
            [Op.gte]: filters.minPrice
        };
    }
    
    return await Product.findAll({
        where: whereClause
    });
};

// Express route implementation
app.get('/api/products', async (req, res) => {
    try {
        const products = await filterProducts(req.query);
        res.json(products);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});
                    

Advanced Search and Filtering

Let's build a more sophisticated search system with multiple filter types:


// Advanced product search with multiple filter types
const searchProducts = async ({
    search = '',
    category = null,
    minPrice = null,
    maxPrice = null,
    colors = [],
    sizes = [],
    brands = [],
    inStock = null,
    sortBy = 'name',
    sortOrder = 'ASC',
    page = 1,
    limit = 10
}) => {
    // Build where clause
    const whereClause = {};
    
    // Text search
    if (search) {
        whereClause[Op.or] = [
            { name: { [Op.iLike]: `%${search}%` } },
            { description: { [Op.iLike]: `%${search}%` } }
        ];
    }
    
    // Category filter
    if (category) {
        whereClause.category = category;
    }
    
    // Price range
    if (minPrice || maxPrice) {
        whereClause.price = {};
        if (minPrice) whereClause.price[Op.gte] = minPrice;
        if (maxPrice) whereClause.price[Op.lte] = maxPrice;
    }
    
    // Multiple color selection
    if (colors.length > 0) {
        whereClause.color = {
            [Op.in]: colors
        };
    }
    
    // Multiple size selection
    if (sizes.length > 0) {
        whereClause.size = {
            [Op.in]: sizes
        };
    }
    
    // Brand selection
    if (brands.length > 0) {
        whereClause.brand = {
            [Op.in]: brands
        };
    }
    
    // Stock status
    if (inStock !== null) {
        whereClause.stockQuantity = {
            [Op.gt]: 0
        };
    }
    
    // Calculate pagination
    const offset = (page - 1) * limit;
    
    // Execute query
    const { rows, count } = await Product.findAndCountAll({
        where: whereClause,
        order: [[sortBy, sortOrder]],
        limit,
        offset,
        include: [
            {
                model: Category,
                attributes: ['name']
            },
            {
                model: Brand,
                attributes: ['name']
            }
        ]
    });
    
    return {
        products: rows,
        totalItems: count,
        totalPages: Math.ceil(count / limit),
        currentPage: page
    };
};
                    

Building a Real Estate Search System

Let's create a comprehensive property search system:


// models/Property.js
const Property = sequelize.define('Property', {
    title: DataTypes.STRING,
    price: DataTypes.DECIMAL,
    bedrooms: DataTypes.INTEGER,
    bathrooms: DataTypes.DECIMAL,
    squareFeet: DataTypes.INTEGER,
    propertyType: DataTypes.STRING,
    location: DataTypes.STRING,
    yearBuilt: DataTypes.INTEGER
});

// Property search service
const PropertyService = {
    async searchProperties({
        location,
        minPrice,
        maxPrice,
        minBeds,
        maxBeds,
        minBaths,
        maxBaths,
        propertyTypes,
        minSquareFeet,
        maxSquareFeet,
        minYearBuilt,
        amenities,
        page = 1,
        limit = 12
    }) {
        const whereClause = {};
        
        // Location search (could be expanded to use geocoding)
        if (location) {
            whereClause.location = {
                [Op.iLike]: `%${location}%`
            };
        }
        
        // Price range
        if (minPrice || maxPrice) {
            whereClause.price = {};
            if (minPrice) whereClause.price[Op.gte] = minPrice;
            if (maxPrice) whereClause.price[Op.lte] = maxPrice;
        }
        
        // Bedroom range
        if (minBeds || maxBeds) {
            whereClause.bedrooms = {};
            if (minBeds) whereClause.bedrooms[Op.gte] = minBeds;
            if (maxBeds) whereClause.bedrooms[Op.lte] = maxBeds;
        }
        
        // Bathroom range
        if (minBaths || maxBaths) {
            whereClause.bathrooms = {};
            if (minBaths) whereClause.bathrooms[Op.gte] = minBaths;
            if (maxBaths) whereClause.bathrooms[Op.lte] = maxBaths;
        }
        
        // Property types
        if (propertyTypes && propertyTypes.length > 0) {
            whereClause.propertyType = {
                [Op.in]: propertyTypes
            };
        }
        
        // Square footage range
        if (minSquareFeet || maxSquareFeet) {
            whereClause.squareFeet = {};
            if (minSquareFeet) whereClause.squareFeet[Op.gte] = minSquareFeet;
            if (maxSquareFeet) whereClause.squareFeet[Op.lte] = maxSquareFeet;
        }
        
        // Year built
        if (minYearBuilt) {
            whereClause.yearBuilt = {
                [Op.gte]: minYearBuilt
            };
        }
        
        const offset = (page - 1) * limit;
        
        const { rows, count } = await Property.findAndCountAll({
            where: whereClause,
            include: [
                {
                    model: Amenity,
                    where: amenities ? { id: { [Op.in]: amenities } } : {},
                    required: amenities ? true : false
                }
            ],
            limit,
            offset,
            distinct: true
        });
        
        return {
            properties: rows,
            totalProperties: count,
            totalPages: Math.ceil(count / limit),
            currentPage: page
        };
    }
};
                    

Best Practices for Search Filters

  • Validate and sanitize all user inputs
  • Use appropriate indexes for frequently filtered columns
  • Implement caching for common filter combinations
  • Consider performance impact of complex filter combinations
  • Provide meaningful error messages for invalid filters

Common Pitfalls to Avoid

  • Not handling special characters in search terms
  • Forgetting to escape user input
  • Over-complicating filter logic
  • Not considering query performance with multiple filters

Advanced Filter Techniques

  • Full-text search implementation
  • Geospatial filtering
  • Fuzzy matching
  • Filter suggestions based on available data