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