DEV Community

Varesh Tapadia
Varesh Tapadia

Posted on • Updated on

Designing Search API in REST

Context

If we start looking at any REST style architecture/guidelines, it always talk about resource and GET,PUT,POST,DELETE operations around it. However, search for a resource is always debatable. The most common notation is to use the GET with /<resource>?<search params> approach. This approach is easy to implement, satisfies the desinging architects 👔 and reasonably flexible.
In this article I will try to mention some of the issues and possible approaches to how this can be implemented in a different and extensible way.

Issues

Let's start by listing some of the major issues with this approach.

Leaking Personal Information

GDPR 😉, I don't need to say anything. Most of the corporate systems have access logging enabled and they log the requested URL (with query params). Now if the URL start containing sensitive or personal information, the solution is not good enough.

Limitation of filter

With query params, there is a limit to the number of filters that can be added. Now if your data set has lots of numeric values and requirement is to support multiple search options, you would end up with fields like amount_less_than, amount_greater_than, amount_equals for all the fields. What if the requirement is to suddenly add "less than and equal to" option also 😁.

Approaches

One of the thing that came to the mind is to provide a search on POST request. This is all good, but how would the body look like 😕.

No Brain approach

The No Brain approach is the easiest one. All the query filters, can be made part of the Search Request Object. Example

{
  "amount_greater_than": 10,
  "amount_less_than": 20,
  "active": true
}
Enter fullscreen mode Exit fullscreen mode

You already know my opinion on this one, so let's move to the next one.

Generic Filter

The simplest alternative, just accept a bunch of generic filters. Something like

{
  "filters": [
    {
      "field": "AMOUNT",
      "operation": "LT",
      "value": "20"
    },
    {
      "field": "ACTIVE",
      "operation": "EQ",
      "value": "true"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

✔️ This is quite extensive and can be used for new filters without making any change to the consumers.
❌ One drawback is that the field type information is lost. Everything is String.
❌ Another drawback is that the unsupported filter could also be requested. So informing what all are supported for each field type is tricky.
❌ Another one is that multiple similar filters can be used, which makes API responses confusing. e.g. LT 10 (amount less than 10) and LT 20 could be requested. No harm, but confusing.

Filters per field

The slight variation of the above approach but addresses the major drawback is to have filters per field type. This way the type information is preserved. A sample API request could be like

{
  "active": {
    "operation": "EQ",
    "value": true
  },
  "amount": [
    {
      "operation": "LT",
      "value": 20
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

✔️ Field type information is preserved and communicated properly.
✔️ More flexibility in choosing the operations supported per field type. Also, the API will communicate to the consumer, if the field supports multiple filters (like amount) or only one (like active).
❌ Still there is a possibility that the caller might use the same filter for a field twice.

DSL type filter per field

Going a step further, what if we define the operations per field as possible fields in json. A sample request for such a filter could be modelled as follows

{
  "active": {
    "eq": true
  },
  "amount": {
    "eq": 0,
    "ge": 0,
    "gt": 0,
    "le": 0,
    "lt": 0
  }
}
Enter fullscreen mode Exit fullscreen mode

✔️ Clean/well documented API with listing all the capabilities possible.
✔️ Extensible, if ne (not equal) filter is required for a field, it can just be added.
✔️ Consistency and easy to follow request.

Java example

In Java, this can be modelled with the help of Generics as follows

@Data
class FilterEQ<T> { //Class for EQ filter
    private T eq;
}
@Data
class Filter<T> extends FilterEQ<T> { //Filter extending EQ 
    private T lt;
    private T le;
    private T gt;
    private T ge;
}
@Data
public class Filters { //Request Object
    private Filter<Long> amount;
    private FilterEQ<Boolean> active;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Providing Search API in REST design is not easy and for really extensible use cases, GraphQL is the best approach. However, it is not always possible to get the best desk in the office 😊. We have to choose the best seat from the available ones.

Here, I have given some ideas on how to design and improve a Search API. According to me, the DSL type Filter per field is very close to how one would develop a search API in GraphQL. But maybe there is a better approach. Do let me know your thoughts on the same. Please like and share with friends if you see some merits in the process and arguments I have listed.

Top comments (0)