Filtering and HATEOAS (The Buzzword)

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Similar to pagination is filtering. Let's say that I want to be able to search in the programmers collection by nickname. So I'll add a &nickname=2. Why 2? Well, my programmer nicknames aren't very interesting, but if I use 2, I'll want it to return Programmer2 and Programmer12. Of course right now, this doesn't have any affect yet.

Filtering: Use Query Parameters

Filtering is actually really easy. But the reason I wanted to cover it is that sometimes people wonder if they should get clever with their URLs when they're filtering and maybe come up with URLs like /api/programmers/filter/nickname2. Don't do that. If you're filtering, use a query parameter, end of story.

Coding up a Simple Filter

So how do we get this to work? It couldn't be simpler. At the top of listAction, I'll look for the nickname query parameter. And if it's present, we'll query in a special way. And if it's not, we'll do the normal findAll. I have a shortcut setup to query using MySQL LIKE. I'll pass it the value surrounded by the percent signs:

... lines 1 - 89
public function listAction(Request $request)
$nicknameFilter = $request->query->get('nickname');
if ($nicknameFilter) {
$programmers = $this->getProgrammerRepository()
->findAllLike(array('nickname' => '%'.$nicknameFilter.'%'));
} else {
$programmers = $this->getProgrammerRepository()->findAll();
... lines 99 - 123
... lines 125 - 214

And that should be it!

If we go back to the Hal Browser and hit go, we get nothing back! But we're actually on page 3, so click to go back to page 1. Hmm, now we have too many results! That's because we lost the &nickname query parameter. That's because I was lazy - if I have extra filters I should pass those to the 3rd argument of PaginatedRepresentation. If I do that, it'll show up in our pagination links.

I'll re-add &nickname=2 manually and this time, we see it's only returning Programmer2 and Programmer12, and it knows that there's only going to be one page of them. So that's it for filtering: use query parameters, do whatever logic you need for filtering, and pass the filter to PaginatedRepresentation, even though I didn't do it here. It's that easy.

Hypermedia as the Engine of Application State (HATEOAS)

One quick thing to notice is that even though we now support this nickname filter, there isn't any way for the API client to know this just by looking at the links. We have links to help them go through the pages, but no link that says: "Hey, if you want, you could pass a nickname query parameter to filter this collection". There's not really a way with HAL to do that. There are other formats that give you more options and ways to say "Hey, you can add the following 4 query parameters to this URI, and here's what each will do".

And this is one of those spots where REST can get frustrating. The purpose of adding links is to help make your API client's life easier. They are not intended to replace human-readable documentation. So even if you did find a clever way to include filtering information in your response, this would just be as a "nice feature" for your API. You should still document which endpoints have which filters and what they mean.

The term HATEOAS means: hypermedia as the engine of application state. And as cool as it is, it's at the heart of this confusion. In its most pure form, HATEOAS is an idea that seems to suggest that if your API has really good links, it doesn't need human-readable technical documentation. Instead, a client can follow the links in your response and other details you return to figure everything out.

But this is a dream, not a reality. As cool as our API is, it lacks a ton of details. For example, it doesn't have any information about filtering. Second, it doesn't tell you which HTTP methods each URL supports, nor what fields you should POST to /api/programmers in order to create a new resource. The links we have are nice, but they're nowhere near giving the API client everything it needs.

So think of links as a nice addition to your API, but not something that'll replace really nice human documentation.

Leave a comment!

  • 2020-03-06 Victor Bocharsky

    Hey Claire,

    I suppose you already get all your key/value filters as array/arrays before writing the query. Then, the best way to escape the value - use setParameter() method. And to avoid problems on users who want to guess or hack your URL, I'd recommend you to do simple "if" checks instead of dynamic things, something like:

    if (isset($filters['name']) && $filter['name']) {
    $qb->andWhere('name LIKE :filter_name');
    $qb->setParameter('filter_name', $filter['name']);

    // and so on for all your possible filters you want to support in that URL

    This way you specify columns and Doctrine properly escape values. Moreover, because in theory you have finite number of filters, i.e. fields. Otherwise, use may change it to something like "?notexistentcolumn=test" and it will fail.

    But if you really want to make it dynamic, here's an example on how you can probably escape values in doctrine to avoid SQL injection: https://www.doctrine-projec... - though this example only shows how to escape values, not column names.


  • 2020-03-04 claire

    Hi, I was wondering for a use case were there will be 10+ filters a user will be able to search by. The url could be something
    Instead of writing out 10+ andWhere() statements i was wondering it there a way to pass a variable into the andWhere() statement. Something like andWhere('$filters LIKE :filter') however i have trouble as the single quotes are needed as part of the query syntax and ive tried concatenating the varible to the query but that didn't work either.
    Wondering if you have any suggestions ?


  • 2018-10-22 Victor Bocharsky

    Hey Coder,

    I think it's OK to have some logic in repos, and we do use it on practice. Well, you can test them with integration tests, or sometimes even functional test are enough, it depends.


  • 2018-10-21 Coder

    So it is ok to have logic in repositories? Like if ($filter) { add some filtering } I was thinking about it long time and did not know the answer. From the testability perspective - we do not unit test repositories, and so less logic would be better. But we can still test them with the tests like you write - which call whole api endpoint.

  • 2016-07-29 Thierno Diop

    Hi I wanted to let you know that adding the filter prameter in the created links like you did cause two problems :
    1 - if you have the prameter page in the link sent by the client you'll have two page prameter in the link you generate in your code
    2 - this add in the generated link the filter prameter with the syntax : %5Bfilter%5D=ilterValue
    The solution i found is to do it like this :

    $createLink = function ($pageumber) use ($routeName, $routeParams,$request) {

    return $this->router->generate($routeName, array_merge(



    array("page" => $pageumber)


    And don't forhget to write the array("page" => $pageumber) after $request->query->all() otherwise the page prameter in the query will override the $pageNumber in the merged array!!
    PS : You do great work really great

  • 2016-07-04 weaverryan

    Everything is possible of course :). But, there's no simple way to do this that I can think of - you would probably build some custom functionality to do this yourself. So, it depends on how badly you need this!

  • 2016-07-04 bblue

    Would it be possible to somehow dynamically add the parameters exposed by the serializer as (the only) valid search fields?