Spring Data REST searching with tanstack router and react query (part 5)
In the previous article
we have enabled paging and sorting in our Spring Boot backend and
extended our React UI to make use of these features.
So the obvious next feature is searching.
Extending the backend
In contrast to paging and sorting which was already automatically
supported by Spring Data REST we need to do a bit of legwork
in order to add the support for search.
We will be using QueryDsl which has a very
nice integration with Spring Data REST.
Adding the QueryDSL dependencies
To get started we need to add the necessary dependencies in build.gradle.kts
First (1), we define a variable queryDslVersion so that we dont need to repeat
the QueryDsl version number in every dependency.
We use the version provided by the
Spring Boot dependency management plugin.
While Spring Boot defines the correct version already, we still need to refer
to the specific version in a few dependencies since we need the
com.querydsl:querydsl-jpa:5.0.0:jakarta variant which supports
Jakarta EE out of the box (see (2)). We also need querydsl-core and
jakarta.persistence:jakarta.persistence-api.
In addition to the implementation dependencies we also need an annotation
processor (see (3)), which will generate the QueryDsl specific types
of our Club entity (QClub) which allows type-safe access to our
entity properties.
Enabling QueryDsl
First we need to mark the Club entity to be processed by the QueryDsl
annotation processor. This is done by adding a QueryEntity annotation.
As a next step we either need to compile our application using gradle or
ensure that annotation processing is enabled in your IDE. For IntelliJ it is
enough to check the checkbox in the Annotation Procesors configuration
(see the IntelliJ documentation).
Now we can extend our ClubRepository.
The only thing we really need to do is to have our interface extend
from QuerydslPredicateExecutor as well as from QuerydslBinderCustomizer
which allows us to customize how filters are interpreted (see (5)).
This alone would already be enough to enable searching within our REST
API, however, we also want to be able to use a case-insensitive substring
search.
Therefore, we need to customize the default QuerydslBindings accordingly (see (6)).
Here, we simply define a case-insensitive search for both clubName and managerEmail.
Note that for the id a substring search would not make any sense, therefore,
we leave it at that.
In order to have enough test data we again add a few rows into our table
Now we are ready to test the searching:
produces
which is exactly what we expected.
Since this is a case insensitive query
also produces club6.
Searching in the managerEmail field works as expected
produces
However, when searching in the ID field we have to provide the exact
string, e.g.
produces an empty result
but an exact search works as expected:
produces
Extending the frontend
With that the backend part is done and we can focus on how to add
this to the React UI.
Update the fetch method
First we need to extend our fetch method to add support for filters in src/api/api.ts
First we define a new zod schema for
the search parameters which we
will use for both the parameter type (see (7) and (8)) as well as for the
query parameter parsing (later).
The method useClubsQuery is basically the same except for the addition of
the new filter parameter which we well also need to pass to the queryKey
(see (9)) in order to automatically re-query whenever a filter parameter changes.
In the fetchClubs method we simply take all filter parameters (see (10)) and
add them (if present) to the URLSearchParams to pass them directly to the server.
Add searching to the UI
Now we can update the UI to be able to support search.
Since PrimeReact Datatable already has a very nice filter feature, there is not a lot we have to do.
Puh, there is a lot to unpack here. First, we extend the ClubPageSearchParams
to include our previously defined ClubFilterSchema (see (11)).
Then, we need converter functions between the filter format of our
ClubFilterSchema and the DataTable filter structure
(in both directions, see (12) and (13)).
Now we can pass the search.filter to our useClubsQuery method (14).
Once all of this is in place we need to activate filtering on the DataTable
by setting filterDisplay to row(15). This makes inline filter boxes appear
underneath the table headers.
Further, we need to pass in the current search.filter as the currently selected
filters(16). This ensures that even if you change the filter in the URL
you will still see the current filter parameters in the Table filters.
Finally, we need to react to onFilter changes (17) and update the
current search params whenever a filter changes. This will trigger an automatic
re-fetching of the data with the new filters.
The only remaining thing to do is to mark the clubName and managerEmail columns
as filterable (18) and we are finally done.
In the screenshot you can see the DataTable with active filtering.
Any change in one of the filter fields will automatically cause a re-fetching
of the data from the server.
The data is filtered, sorted and paged on the server via Spring Data REST.
The UI state is fully kept in the browser URL and the code allows a fully
type-safe handling of paging, sorting and searching aspects.
This concludes part 5 of the tutorial series. Thanks a lot for hanging in there.
You can find the finished source code on
GitHub.
Hi, I'm Rainer. I'm a software engineer based in Salzburg.
You can follow me on
Twitter, see
some of my work on
GitHub, or read
more about me on
my website.