Sorting REST API Results
In this article we will look at how to sort results when using a GET request that returns a list. Implementing sorting functionality in a RESTful API is a crucial aspect of designing a robust and user-friendly web service. Sorting REST API results empowers developers and end-users alike by providing a systematic and organized way to retrieve and navigate data.
In our API project we have GET /v1/travels
and GET /v1/tours
endpoints. Below we will describe how to implement sorting for those endpoints. If you would like a deeper dive into the process, please watch our video on this topic.
Add Sort By Query Parameter to Controllers
First, we are going update the code to handle sort_by
query parameter. We update listTravels
method in travels controller to include sortBy
option in options passed to repository.getAll()
method. sortBy
is assigned the value of req.query.sort_by
const travels = TravelResource.collection( await repository.getAll({ sortBy: req.query.sort_by, }) );
We update listTours
method in tours controller to include the same option sortBy
.
const tours = TourResource.collection( await repository.getAll({ sortBy: req.query.sort_by, }) );
Now, let’s update BaseRepository
class to handle sortBy
property in the options
parameter.
Handle Sorting REST API Query Parameter in Repository
Now, let’s update BaseRepository
class to handle sortBy
property in the options
parameter. First, in BaseRepository.ts
file add protected class property called allowedSortByFields
protected allowedSortByFields: Array<string> = ["created_at"];
This property is assigned a default value of an array with “created_at” as a string member. When a user attempts to sort results, the API can’t allow them to use any field they want for 2 reasons. The first one is obvious: a user may try to sort results by a field that doesn’t exist in the database. The second reason is that even if a user attempts to sort by a field that exists in the database, this field may not be indexed in the database and the operation will take a long time: for example sorting by description
field.
Let’s take a look at getAll
method in BaseRepository.
getAll(options: Record<string, any> = {}): Promise<Array<A>> { const orderBy = this.getOrderBy(options.sortBy); delete options.sortBy; options.order = orderBy; return this.modelClass.findAll(options); }
We get orderBy
from options.sortBy
using BaseRepositor
y’s getOrderBy
method. Then we remove sortBy
from the options and add orderBy
as order
property on the options object. Sequelize require this property to order the returned results. Finally, we pass options to Sequelize’s findAll
method.
Now let’s take a look at BaseRepository
‘s getOrderBy
method. It is a protected method that generates order property for Sequelize options object.
protected getOrderBy(sortBy: string | undefined): Array<[string, string]> { const orderBy: Array<[string, string]> = [["created_at", "DESC"]]; if (!sortBy) { return orderBy; } const parts = sortBy.split("-"); if (!this.allowedSortByFields.includes(parts[0])) { return orderBy; } if (!parts[1] || !["asc", "desc"].includes(parts[1].toLowerCase())) { return orderBy; } return [[parts[0], parts[1].toLowerCase()]]; }
It takes sortBy
parameter that can be either string or undefined and returns an array of tuples. First a default value of array with tuple created_at
, desc
is assigned to orderBy
constant. The default value is returned in 3 cases. First – if there’s no sortBy
(sortBy
is undefined). Second – if the first part of the sortBy
is not in the allowedSortByFields
. Third – if the second part of sortBy
is either missing or not “asc” or “desc”. If none of the previous conditions match, the array of tuples is returned where the first part of the tuple is the field parsed from sortBy
and the second part is a direction (ascending or descending).
Update Code in Travel and Tour Repositories
Finally, let’s add protected property allowedSortByFields
to TravelRepository
.
protected allowedSortByFields = [ "name", "number_of_days", "created_at", "updated_at", ];
As you can see, we can sort Travels by name, number of days, created_at and updated_at.
Let’s add the same protected property to TourRepository
.
protected allowedSortByFields = [ "name", "price", "starting_date", "ending_date", ];
We can sort tours by name, price, starting_date and ending_date.
That’s it for the code. Inheritance pattern enables us to “push” all the logic into parent class. Children classes only need to have their own custom properties or logic.
Conclusion
In conclusion, incorporating sorting functionality into your RESTful API design is not just a feature — it’s a strategic decision that significantly impacts the usability and efficiency of your web service. By providing users and developers with the tools to organize and navigate data effectively, you create a more powerful and user-centric API experience.