Search and Filter

Letting users narrow down the table without leaving the page

Concepts

Query parameters for filtering

Building a conditional query

A search form

Reflecting the active filters

Edge cases

Check for Understanding

Why should a search form use method="get" rather than method="post"?

GET requests encode their parameters in the URL, so the result page can be bookmarked, shared, and reached via browser history. POST requests send data in the request body and are not represented in the URL, so refreshing the page re-submits the form and the URL cannot be shared. For searches (which do not modify server state) GET is always the right choice.

If you build a SQL query by concatenating the user's search term directly into the string, what attack does that enable?

SQL injection. A user who enters ' or '1'='1 as the site name would turn the query into where site = '' OR '1'='1', which matches every row. A more destructive input could add drop table or extract sensitive data. Parameterized queries fix this by passing values separately, so the database treats them as literals rather than SQL.

How do you write a query that filters by site name only when a site name is provided, but returns all rows otherwise?

Build the where clause conditionally. One approach is:

clauses = []
params = []
if site:
    clauses.append("site = ?")
    params.append(site)
sql = "select * from snails"
if clauses:
    sql += " where " + " and ".join(clauses)

This produces a valid query with or without a filter, and always uses parameterized placeholders.

What does "pre-filling" form inputs mean, and how do you retrieve the current filter value from the request in Litestar?

Pre-filling means setting the value attribute of a form input to the currently active filter so the user sees what they searched for and can refine it. In Litestar, the handler receives optional query parameters as function arguments (e.g., site: str | None = None). Those values are passed to the htpy renderer, which sets value=site on the corresponding <input>.

Exercises

Add a date range filter

Add start_date and end_date query parameters that filter results to measurements taken within the specified range. Write out the SQL you need before asking the LLM to generate it. Test the edge cases: what happens when only one of the two dates is provided, or when start_date is later than end_date?

Combine filters

Make the site name filter and the mutation filter work together correctly so that both conditions must be met when both are provided. Write a description of the combined query first, then implement it. Verify with at least three test inputs: site only, mutation only, and both at once.

Show active filters

Display a summary above the table listing each currently active filter with a link that removes it (e.g., "Site: north-beach [x]"). Clicking the link should return the same page with only that filter removed. Think through the URL construction before writing any code.

Test the edge cases

Manually test what the search route does when the filter value is: (a) an empty string, (b) a string containing a single quote, (c) a string of 500 characters. Record what the server returns for each; which cases does the current implementation not handle correctly?