Design RESTful query API with a long list of query parameters

RestHttp Parameters

Rest Problem Overview


I need to design a RESTful query API, that returns a set of objects based on a few filters. The usual HTTP method for this is GET. The only problem is, it can have at least a dozen filters, and if we pass all of them as query parameters, the URL can get quite long (long enough to be blocked by some firewall).

Reducing the numbers of parameters is not an option.

One alternative I could think of is to make use of the POST method on the URI and send the filters as part of the POST body. Is this against being RESTfull (Making a POST call to query data).

Anyone have any better design suggestions?

Rest Solutions


Solution 1 - Rest

Remember that with a REST API, it's all a question of your point of view.

The two key concepts in a REST API are the endpoints and the resources (entities). Loosely put, an endpoint either returns resources via GET or accepts resources via POST and PUT and so on (or a combination of the above).

It is accepted that with POST, the data you send may or may not result in the creation of a new resource and its associated endpoint(s), which will most likely not "live" under the POSTed url. In other words when you POST you send data somewhere for handling. The POST endpoint is not where the resource might normally be found.

Quoting from RFC 2616 (with irrelevant parts omitted, and relevant parts highlighted):

> 9.5 POST > ======== > > The POST method is used to request that the origin server accept the > entity enclosed in the request as a new subordinate of the resource > identified by the Request-URI in the Request-Line. POST is designed to > allow a uniform method to cover the following functions: > > - ... > - Providing a block of data, such as the result of submitting a form, to a data-handling process; > - ... > > ... > > The action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result. > > If a resource has been created on the origin server, the response SHOULD be 201 (Created)...

We have grown used to endpoints and resources representing 'things' or 'data', be it a user, a message, a book - whatever the problem domain dictates. However, an endpoint can also expose a different resource - for example search results.

Consider the following example:

GET    /books?author=AUTHOR
POST   /books
PUT    /books/ID
DELETE /books/ID

This is a typical REST CRUD. However what if we added:

POST /books/search

    {
        "keywords": "...",
        "yearRange": {"from": 1945, "to": 2003},
        "genre": "..."
    }

There is nothing un-RESTful about this endpoint. It accepts data (entity) in the form of the request body. That data is the Search Criteria - a DTO like any other. This endpoint produces a resource (entity) in response to the request: Search Results. The search results resource is a temporary one, served immediately to the client, without a redirect, and without being exposed from some other canonical url.

It's still REST, except the entities aren't books - the request entity is book search criteria, and the response entity is book search results.

Solution 2 - Rest

A lot of people have accepted the practice that a GET with too long or too complex a query string (e.g. query strings don't handle nested data easily) can be sent as a POST instead, with the complex/long data represented in the body of the request.

Look up the spec for POST in the HTTP spec. It's incredibly broad. (If you want to sail a battleship through a loophole in REST... use POST.)

You lose some of the benefits of the GET semantics ... like automatic retries because GET is idempotent, but if you can live with that, it might be easier to just accept processing really long or complicated queries with POST.

(lol long digression... I recently discovered that by the HTTP spec, GET can contain a document body. There's one section that says, paraphrasing, "Any request can have a document body except the ones listed in this section"... and the section it refers to doesn't list any. I searched and found a thread where the HTTP authors were talking about that, and it was intentional, so that routers and such wouldn't have to differentiate between different messages. However, in practice a lot of infrastructure pieces do drop the body of a GET. So you could GET with with filters represented in the body, like POST, but you'd be rolling the dice.)

Solution 3 - Rest

In a nutshell: Make a POST but override HTTP method using X-HTTP-Method-Override header.

Real request

POST /books

Entity body

{ "title": "Ipsum", "year": 2017 }

Headers

X-HTTP-Method-Override: GET

On the server side, check if header X-HTTP-Method-Override exists then take its value as the method to build the route to the final endpoint in the backend. Also, take the entity body as the query string. From a backend point of view, the request became just a simple GET.

This way you keep the design in harmony with REST principles.

Edit: I know this solution was originally intended to solve PATCH verb problem in some browsers and servers but it also work for me with GET verb in the case of a very long URL which is the problem described in the question.

Solution 4 - Rest

If you are developing in Java and JAX-RS I recommend you use @QueryParam with @GET

I had the same question when I needed to go through a list.

See example:

import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
    
@Path("/poc")
public class UserService {

	@GET
    @Path("/test/")
 	@Produces(MediaType.APPLICATION_JSON)
	public Response test(@QueryParam("code") final List<Integer> code) {
        		Integer int0 = codigo.get(0);
        		Integer int1 = codigo.get(1);
        		
		return Response.ok(new JSONObject().put("int01", int0)).build();
	}
}

URI Pattern: “poc/test?code=1&code=2&code=3

@QueryParam will convert the query parameter “orderBy=age&orderBy=name” into java.util.List automatically.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmissionE46View Question on Stackoverflow
Solution 1 - RestAmir AbiriView Answer on Stackoverflow
Solution 2 - Restsea-robView Answer on Stackoverflow
Solution 3 - RestDelmoView Answer on Stackoverflow
Solution 4 - Restacacio.martinsView Answer on Stackoverflow