We're building a music database API that needs sophisticated search capabilities. Users should be able to search for musicians based on various criteria including names, band affiliations, and instruments played. Think of it like building a music streaming service's search feature where users can find artists through multiple filters.
The main challenges we need to solve are:
Let's approach this step by step using the query parameter system:
// Base query object that we'll build upon
let query = {
where: {}, // For filtering conditions
include: [], // For related models
attributes: [], // For field selection
order: [], // For sorting
limit: 5, // For pagination
offset: 0 // For pagination
};
// Handle firstName and lastName filters
if (req.query.firstName) {
query.where.firstName = req.query.firstName;
}
if (req.query.lastName) {
query.where.lastName = req.query.lastName;
}
Think of this like a filing cabinet where we're looking for folders matching specific names. The WHERE clause acts as our filtering criteria.
// Handle bandName filter
if (req.query.bandName) {
query.include.push({
model: Band,
where: { name: req.query.bandName }
});
}
This is similar to looking through related files in a filing system - we're checking a musician's band affiliation before including them in results.
// Handle instrumentTypes filter
if (req.query.instrumentTypes) {
query.include.push({
model: Instrument,
where: { type: req.query.instrumentTypes },
through: { attributes: [] } // Hide join table
});
}
Imagine this as checking a musician's skill set - we're filtering based on what instruments they can play.
// Handle musician field selection
if (req.query.musicianFields) {
if (req.query.musicianFields.includes('all')) {
delete query.attributes;
} else if (req.query.musicianFields.includes('none')) {
query.attributes = [];
} else {
query.attributes = req.query.musicianFields;
}
}
This is like choosing which details to show on a musician's profile card - you might want just their name, or all their information.
// Handle result ordering
if (req.query.order) {
query.order = req.query.order.map(orderItem => {
const [field, direction] = orderItem.split(',');
return [field, direction?.toUpperCase() || 'ASC'];
});
} else {
query.order = [['lastName'], ['firstName']];
}
Think of this as arranging your search results like sorting a deck of cards - you can choose which characteristics determine the order.
// Using LIKE for partial matches
if (req.query.bandName) {
query.include.push({
model: Band,
where: {
name: {
[Op.like]: `%${req.query.bandName}%`
}
}
});
}
// Reusable pagination middleware
const paginationMiddleware = (req, res, next) => {
const { page = 1, size = 5 } = req.query;
req.pagination = {
limit: page === '0' ? null : Number(size),
offset: page === '0' ? 0 : (Number(page) - 1) * Number(size)
};
next();
};
Here are some common issues to watch out for when implementing search:
To keep your search feature fast and efficient:
This pattern of search implementation is common in many applications:
Here are some key test cases to consider: