The builder pattern is one of the Creational pattern groups. The responsibility is to build a complex object with basic objects step by step.
The builder pattern should be used when a developer wants to:
For example, we will create an object to validate API schema including body, param, and query.
base.validation.ts
export class BaseValidation {
protected readonly _httpRequest: { params?: any; query?: any; body?: any };
constructor() {
this._httpRequest = {};
}
findOneBuilder() {
return this;
}
deleteOneBuilder() {
this._httpRequest.params = Joi.object().keys({
id: Joi.string().custom(objectId),
});
return this;
}
updateOneBuilder() {
this._httpRequest.params = Joi.object().keys({
id: Joi.string().custom(objectId),
});
return this;
}
insertOneBuilder() {
return this;
}
searchBuilder() {
this._httpRequest.query = {};
return this;
}
withBody(body) {
this._httpRequest.body = body;
return this;
}
withFilter(filter) {
this._httpRequest.query = {
...this._httpRequest.query,
...filter,
isAll: Joi.string(),
includeId: Joi.string(),
};
return this;
}
withPopulate() {
this._httpRequest.query = {
...this._httpRequest.query,
populate: Joi.string(),
};
return this;
}
withSelect(select) {
this._httpRequest.query = {
...this._httpRequest.query,
select: select,
};
return this;
}
withSortBy() {
this._httpRequest.query = {
...this._httpRequest.query,
sort_fields: Joi.string(),
sort: Joi.string(),
};
return this;
}
withLimit() {
this._httpRequest.query = {
...this._httpRequest.query,
limit: Joi.number().integer(),
};
return this;
}
withPage() {
this._httpRequest.query = {
...this._httpRequest.query,
page: Joi.number().integer(),
};
return this;
}
build() {
return {
...(this._httpRequest.body && { body: this._httpRequest.body }),
...(this._httpRequest.query && { query: this._httpRequest.query }),
...(this._httpRequest.params && { query: this._httpRequest.params }),
};
}
}
tag.validation.ts
export class TagValidation extends BaseValidation {
constructor() {
super();
}
searchBuilder() {
return super
.searchBuilder()
.withSelect(Joi.custom(this.select))
.withPage()
.withPopulate()
.withLimit()
.withSortBy()
.withFilter({
name: Joi.string(),
})
.build();
}
private select(value: string, helpers: CustomHelpers) {
const selectFields = ['name', 'description', 'slug'];
if (value) {
const valueList = value.split(',');
const difference = valueList.filter((x) => !selectFields.includes(x));
if (difference.length > 0) {
return helpers.message({ custom: `"{{#label}}" must be in ${selectFields}` });
}
}
return value;
}
}
tag.route.ts
validateSchemaMiddleware(new TagValidation().searchBuilder());
Execution result
/tags?limit=1&select=name
{
"statusCode": "10000",
"status": 200,
"message": "",
"data": {
"record": [
{
"id": 6,
"name": "React JS"
}
],
"limit": 1,
"total": 6,
"page": 1
}
}
/tags?limit=1&select=title
{
"statusCode": "10001",
"status": 400,
"message": "Invalid request",
"data": {
"query": [
"\"\"select\"\" must be in name,description,slug"
]
}
}
Pros:
Cons:
Thank you for reading, and happy coding!
I hope this article will help make the concepts of the Builder Pattern