DEV Community

Cover image for Repository Pattern With Javascript
Sakis bal
Sakis bal

Posted on

Repository Pattern With Javascript

Photo by Cleyder Duque from Pexels

Explain it simply

The repository, in simple words, is a pattern used to keep a loose connection between the client and the server data storing procedures hiding all complex implementation. This means that the client will not have to be concerned as to how to access the database, add or remove items from a collection of items, pointers, etc.

Why Would I Want To Use It

Let’s make a scenario where you just call API endpoints from your client and use it in a certain way. In a real-world scenario, you would probably call the same endpoint on different points in your files depending on where you need to use the data. For example:

const todoItemsElContainer = document.getElementById("todo-items");
fetch('http://example.com/todoItems.json')
      .then(response => response.json())
      .then(data => {
           const todoItemDiv = document.createElement('div');
               todoItemDiv.id = data.ID;

           const innerText = document.createElement('p');
               innerText.textContent = `${data.DESCR} duration: ${todoItemDiv.duration}`;

           todoItemDiv.appendChild(innerText);
           todoItemsElContainer.appendChild(todoItemDiv);
       });

function addTodoItem(description, duration) {

    const data = {
        DESCR: description,
        DURATION: duration
    }

    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
    });
    return response.json();
}

Enter fullscreen mode Exit fullscreen mode

Issues with the above approach

There are several issues with the above code example. For one in the first fetch call we are mixing the presentation with fetching of the data. This makes the fetch non-reusable.

Furthermore, what happens now if we want to change a certain attribute of the requested object now?

Let’s suppose the API changes and we need to change an attribute name from todoList.DESCR to todoList.DESCRIPTION. We will have to go to every single instance and change that attribute to the new attribute name.

This is quite unproductive and might lead to accumulating mistakes and errors over time. (There is a chance we forget some instance unchanged and find that out later, which would be very frustrating).

Another possible scenario is that there will be some business logic involved in the procedure. By scattering around functions that are related to that object you are creating more violations to SOLID. But enough talking about the problem. What is the solution?

Repository Pattern to the Rescue

With the repository pattern, you manage to map attributes that come directly from the database to the repository model which gives us flexibility. If the attributes of the item change we can easily change them at the ONE spot where they are used: The Repository. No need to go to every file the object is retrieved, respecting the SOLID principle.

The power of abstraction and inheritance gives us the power to create a default use for simple API object management to minimize the boilerplate. Then with inheritance, the concrete implementations can overwrite the default behavior.

Additionally, business logic is encapsulated in functions inside the Repository. If the implementation ever changes you have it all in one place to change it however you like.

Below is an example of the TodoItemRepository.


class TodoItem {
  /**
   * Naming attributes the same as in the database
   * helps when adding the items back to the database.
   */
  constructor(id, description, duration) {
      this.DESCR = description;
      this.DURATION = duration;
      this.ID = id;
  }

  getDescription() {
      return this.DESCR;
  }

  getDuration() {
      return this.DURATION;
  }

  getId() {
      return this.ID;
  }
}

class TodoItemRepository {

    constructor() {
         this.todoItems = [];

         this.todoItemDataContext = new TodoItemDataContext();
    }

    getAll() {
        return this.todoItemDataContext.getAll().then(response => {
            if (Array.isArray(response)) {
                response.map( todoItem => {
                    this.todoItems.push(new TodoItem(todoItem.ID, todoItem.DESCRIPTION, todoItem.DURATION));
                })
            }
            return this.todoItems;
        });
    }

    add(todoItem) {
        this.todoItemDataContext.add(todoItem).then((newTodoItem) => {

            this.todoItems.push(todoItem);
        }).catch((error) => {
            console.error('Error:', error);
        });
    }

}
Enter fullscreen mode Exit fullscreen mode

First of all above, we decoupled the fetching of the data with the presentation of it. Now we can reuse it.

If now the database changes the DESCR attribute to DESCRIPTION all we need to do is change our Repository class to accept this change. In short, the design became more SOLID. Note here that you need to use getters, or setters (make the attributes private) within your application so you are not dealing directly with the attribute names.

In Conclusion

The repository pattern:

  • Helps us keep the code SOLID
  • Is an abstraction that hides the complexity of communicating with the data persistence layer
  • Makes our code more robust

Learn More About The Repository Pattern

Latest comments (3)

Collapse
 
fdbatista profile image
Felix Daniel

Hi, good article.
But in this line this.todoItemDataContext = new TodoItemDataContext();
Where does the TodoItemDataContext class come from?

Collapse
 
iamsheppherd profile image
Sheppherd

Looking over it, I would say TodoItemDataContext() is the data source. In the example for both getAll() and add(), the same methods are called on the object in a promise.

Collapse
 
robertsan96 profile image
Robert Sandru

Well, good article but instead of using those getters to maintain key consistency between entities, why not adopting the Adapter pattern?