---
title: "Recipe Box"
description: "Recipe box API with ingredient counts and quick-meal search as computed columns and queries"
url: "https://www.pixeltable.com/recipes/recipe-box"
source: "https://github.com/pierrebrunelle/pixeltable-recipe-box"
---

# Recipe Box

Recipe box API with ingredient counts and quick-meal search as computed columns and queries

One `app.py` with 1 table, 3 computed columns, 4 HTTP routes. Needs `pixeltable[serve]>=0.7.14`.

## Tables

### recipes (table)

| Column | Kind | Type or expression |
| --- | --- | --- |
| id | primary key | `pxtf.uuid.uuid7()` |
| title | stored | String |
| ingredients | stored | String |
| minutes | stored | Int (optional) |
| notes | stored | String (optional) |
| n_ingredients | computed on insert | `ingredient_count(ingredients)` |
| title_upper | computed on insert | `pxtf.string.upper(title)` |
| title_blurb | computed on insert | `teaser(title, minutes)` |

## Routes (service `recipes_api`)

- `POST /recipes`: inserts into `recipes` (takes title, ingredients, minutes, notes; returns id, n_ingredients, title_upper, title_blurb)
- `POST /recipes/update`: updates `recipes` (takes title, ingredients, minutes, notes; returns id, n_ingredients, title_upper, title_blurb)
- `POST /ingredient-count`: computes `recipes` columns without storing (takes ingredients; returns n_ingredients)
- `GET /recipes/quick`: runs the query `quick_recipes` (takes max_minutes)

## Run it locally

```sh
git clone https://github.com/pierrebrunelle/pixeltable-recipe-box
cd pixeltable-recipe-box
uv sync
uv run pxt schema update app.py recipes
uv run python seed.py recipes
uv run pxt service run app.py recipes --port 8000
```

## Run it on Pixeltable Cloud

```sh
uv run pxt new    # a free database, no account
# put the pxt:// address it prints in pixeltable.toml
uv run pxt db update pxt://<org>:main -f
uv run pxt schema update app.py pxt://<org>:main -f
uv run pxt service update app.py pxt://<org>:main -f
```

## app.py

At commit `a71c1bec` (https://github.com/pierrebrunelle/pixeltable-recipe-box/blob/a71c1beccb19f13e563d4b6f9b58f130c3ac56d4/app.py):

```python
"""Recipe Box API built with Pixeltable.

    pxt schema update app.py recipes
    pxt service run app.py recipes
"""
import pixeltable as pxt
import pixeltable.functions as pxtf
from pixeltable.serving import FastAPIRouter

from udfs import ingredient_count, teaser

# ---- tables ----
TableModel = pxt.model_base()


class Recipes(TableModel, name='recipes'):
    id = pxt.Column(value=pxtf.uuid.uuid7(), primary_key=True)
    title: pxt.String
    ingredients: pxt.String
    minutes: pxt.Int | None
    notes: pxt.String | None

    n_ingredients = ingredient_count(ingredients)
    title_upper = pxtf.string.upper(title)
    title_blurb = teaser(title, minutes)


# ---- queries ----
@pxt.query
def quick_recipes(max_minutes: int):
    """Recipes that fit a time budget, fastest first."""
    return Recipes.where(Recipes.minutes <= max_minutes).select(
        Recipes.id, Recipes.title_blurb, Recipes.n_ingredients
    ).order_by(Recipes.minutes)


# ---- routes ----
recipes_api = FastAPIRouter(name='recipes_api')
recipes_api.add_insert_route(
    Recipes, path='/recipes',
    inputs=[Recipes.title, Recipes.ingredients, Recipes.minutes, Recipes.notes],
    outputs=[Recipes.id, Recipes.n_ingredients, Recipes.title_upper, Recipes.title_blurb],
)
recipes_api.add_update_route(
    Recipes, path='/recipes/update',
    inputs=[Recipes.title, Recipes.ingredients, Recipes.minutes, Recipes.notes],
    outputs=[Recipes.id, Recipes.n_ingredients, Recipes.title_upper, Recipes.title_blurb],
)
recipes_api.add_compute_route(Recipes, path='/ingredient-count', inputs=[Recipes.ingredients],
                              outputs=[Recipes.n_ingredients])
recipes_api.add_query_route(path='/recipes/quick', query=quick_recipes, method='get')
```
