AI-powered coffee & matcha drink generator. Tell us your mood or flavour preference, and we'll craft something you'd see on a specialty café menu.
Disclaimer: Recipes are AI-generated. The codebase was developed with AI assistance. Always use your best judgment when preparing drinks and review the code before deploying.
- Conda — install Miniconda
- Node.js ≥ 18
- Anthropic API key — get one here
git clone https://github.com/timotius-devin/brew-lab.git
cd brew-lab./run-be.shThis script will:
- Create a conda environment named
brew-lab(Python 3.12) if it doesn't exist - Install all Python dependencies (
fastapi,anthropic,uvicorn,slowapi, etc.) - Start the FastAPI server on http://localhost:8002
On first run, it copies .env.example → .env — edit the .env file with your Anthropic API key and re-run.
./run-fe.shThis script will:
- Run
npm installifnode_modulesis missing - Start the Vite dev server on http://localhost:5173
Go to http://localhost:5173, type your mood or flavour craving, and hit Brew it.
cd backend
conda activate brew-lab
pytestTests cover: health endpoint, valid prompts, off-topic rejection, empty/long prompts, bad JSON from Claude, and CORS.
cd frontend
npm run test # run once
npm run test:watch # watch modeTests cover: PromptInput component (rendering, disabled state, character count) and LoadingState component.
Brew Lab is strictly scoped to coffee and matcha. Topic drift is prevented at three levels:
-
System prompt — Claude is explicitly instructed to only respond to coffee, matcha, drinks, flavours, and café culture. Off-topic inputs trigger a hard-coded rejection:
{"error": "I only know coffee and matcha..."}. -
Backend validation — The API validates every response against a strict schema. If Claude returns anything outside the expected structure (including error keys), it's caught and returned as a 400/422 error.
-
Input sanitisation — User input is trimmed, length-limited to 300 characters, and HTML-escaped before being sent to Claude.
The API key is stored server-side only and never exposed to the browser. The Vite dev server proxies /api requests to the FastAPI backend.
brew-lab/
├── .gitignore
├── README.md
├── run-be.sh # Start script — backend (conda + uvicorn)
├── run-fe.sh # Start script — frontend (npm + vite)
├── backend/
│ ├── main.py # FastAPI app + Claude integration + rate limiting
│ ├── requirements.txt # Python dependencies (pinned versions)
│ ├── pytest.ini # Pytest configuration
│ ├── .env.example
│ └── tests/
│ ├── conftest.py # Pytest fixtures + mocks
│ └── test_drinks.py # API endpoint tests
└── frontend/
├── index.html
├── package.json
├── package-lock.json
├── tsconfig.json
├── vite.config.ts # Proxies /api → localhost:8002
├── vitest.config.ts # Vitest configuration
└── src/
├── main.tsx
├── App.tsx
├── types.ts # Shared TypeScript interfaces
├── index.css # Specialty café aesthetic
├── components/
│ ├── PromptInput.tsx
│ ├── DrinkCard.tsx
│ └── LoadingState.tsx
└── tests/
├── setup.ts
├── PromptInput.test.tsx
└── LoadingState.test.tsx
| Layer | Technology |
|---|---|
| Frontend | React 18 + TypeScript + Vite |
| Backend | Python 3.12 + FastAPI |
| AI | Claude (claude-sonnet-4-20250514) |
| Rate limiting | slowapi (10 req/min per IP) |
- Fork the repo
- Create a branch:
git checkout -b feature/your-feature - Make your changes
- Test both servers
- Open a pull request
- Dark/light theme toggle
- Save favourite drinks to localStorage
- Share drink via URL
- Seasonal drink suggestions
- Caffeine level indicator
MIT