# Filter a product catalog

Bound input, server-side filtering, and updated results in one Astro component.

Source: https://withastro-utils.github.io/docs/examples/products/

Query on the server. Render in the same component. This example uses a small local catalog so you can run it without a database; replace the filter callback with your own database query when connecting real products.

## Try the interaction

Set a maximum price and apply the filter. Try **10** for no results, **50** for two products, or **100** for the full catalog. Negative and empty prices are rejected.

### src/layouts/Layout.astro

```astro title="src/layouts/Layout.astro"
---
import { WebForms } from '@astro-utils/forms/forms.js';
---
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width" />
        <title><slot name="title" /></title>
    </head>
    <body>
        <WebForms>
            <slot />
        </WebForms>
    </body>
</html>
```

### src/pages/products.astro

```astro title="src/pages/products.astro"
---
import { BButton, BInput, Bind, BindForm } from '@astro-utils/forms/forms.js';
import Layout from '../layouts/Layout.astro';
import { products } from '../data/products';

const bind = Bind({ maxPrice: 50 });
let matches = products.filter(product => product.price <= bind.maxPrice);

function filterProducts() {
    matches = products.filter(product => product.price <= bind.maxPrice);
}
---
<Layout>
    <span slot="title">Filter products</span>
    <BindForm {bind}>
        <label for="maxPrice">Maximum price ($)</label>
        <BInput id="maxPrice" name="maxPrice" type="number" min={0} required />

        <BButton onClick={filterProducts} whenFormOK>Apply filter</BButton>
        
        <p role="status">{matches.length} products found</p>
        <ul>{matches.map(product => <li>{product.name} — ${product.price}</li>)}</ul>
    </BindForm>
</Layout>
```

### src/middleware.ts

```ts title="src/middleware.ts"
import forms from '@astro-utils/forms';

export const onRequest = forms();
```

### astro.config.mjs

```js title="astro.config.mjs"
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import forms from '@astro-utils/forms/dist/integration.js';

export default defineConfig({
    output: 'server',
    adapter: node({ mode: 'standalone' }),
    integrations: [forms]
});
```

### src/data/products.ts

```ts title="src/data/products.ts"
// A local catalog keeps this example runnable without a database.
// Replace the filter in your server callback with your application's query.
export const products = [
    { name: 'Notebook', price: 12 },
    { name: 'Desk lamp', price: 35 },
    { name: 'Headphones', price: 79 }
];
```

## How it works

`Bind` restores the submitted budget, `BInput` validates the field, and `BButton` runs `filterProducts` on the server when the form is valid. Astro renders the result count and matching rows together. The embedded preview simulates this interaction locally.

## Run it yourself

Follow the [Forms setup](https://withastro-utils.github.io/docs/guides/forms/getting-started/index.md) or copy the configuration, middleware, and shared layout from the file sidebar. Add both the page and catalog files at the paths shown.
