Crafting Query Strings with the Builder Pattern in Typescript
Refactoring Query String Generation with the Builder Pattern
In web development, the process of generating query strings is a common task, especially when working with APIs. Query strings are crucial for passing data between different pages or between the client and server.
In this post, we’ll explore a flexible approach to constructing query strings using the Builder pattern in Typescript. By leveraging this design pattern, we can create query strings dynamically with ease, enhancing the maintainability and readability of our codebase. Let’s dive into the details of how the Builder pattern can streamline the process of query string generation.
Analyzing the following example code
Recently, I stumbled upon the following code snippet. GenerateQueryString has successfully done its job, configuring a query string dynamically with the parameters.
const generateQueryString = ({ filterKey, filterValue, startTime, endTime}: Partial<{
filterKey: string;
filterValue: string;
startTime: number;
endTime: number;
}>): string => {
return `filterKey=${filterKey ? filterKey : 'hash'}${filterValue ? `&filterValue=${encodeURI(filterValue)}` : ''}
${startTime ? `&stime=${startTime}` : ''}${endTime ? `&etime=${endTime}` : ''}`;
};
const queryStr = generateQueryString({ ilterKey, filterValue, startTime, endTime });
...
However, the implementation lacks both reusability and readability.
Conditional Logic decreases the readability
The code uses conditional ternary operators to construct the query string. This approach makes the code harder to read and maintain, especially as the number of parameters increases.
${startTime ? `&stime=${startTime}` : ''}
Lack of Separation of Concerns
The function is responsible for both constructing the query string and encoding values. This violates the principle of single responsibility and can lead to less modular and harder-to-maintain code.
${filterValue ? `&filterValue=${encodeURI(filterValue)}` : ''}
Limited Flexibility
The function signature expects specific parameters, limiting its flexibility and making it less reusable in scenarios where different sets of query parameters are needed.
Refactor the code using the Builder pattern
What is a Builder pattern?
The Builder pattern is a creational design pattern that separates the construction of a complex object from its representation. It allows the construction process to be step-by-step, enabling the creation of different variations of an object using the same construction code. This pattern promotes code reusability, readability, and flexibility by providing a clear, fluent interface for building objects with varying configurations.
Create a QueryStringBuilder Class
Implement a class that serves as a builder for constructing query strings. QueryStringBuilder class is created by applying the Builder pattern so that provides methods for setting individual query parameters.
class QueryStringBuilder {
private baseUrl: string;
private queryString: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
this.queryString = '';
}
addQueryString(key: string, value: string): void {
if (this.queryString !== '') {
this.queryString += '&';
}
this.queryString += `${key}=${encodeURIComponent(value)}`;
}
build(): string {
return this.baseUrl + '?' + this.queryString;
}
}
Implementation
Utilizing the Builder pattern enables the dynamic addition of query parameters while also effectively separating concerns.
const urlBuilder = new QueryStringBuilder('https://example.com');
if(filterKey) urlBuilder.addQueryString('filterKey', filterKey ?? 'hash');
if(filterValue) urlBuilder.addQueryString('filterValue', encodeURI(filterValue));
if(startTime) urlBuilder.addQueryString('startTime', startTime);
if(endTime) urlBuilder.addQueryString('startTime', endTime);
const resultUrl = urlBuilder.build();
return resultUrl;
Conclusion
Implementing the Builder pattern for query string construction in Web development significantly improves code quality by enhancing reusability, readability, and flexibility.
If you are interested in other design patterns, I recommend reading Design Patterns: Elements of Reusable Object-Oriented Software.
Happy coding!