Project · 2025
Pantry
A self-hosted household inventory and meal-planning app with AI-assisted imports, shared list collaboration, and integrations for Notion and Discord.
- TypeScript
- React
- Express
- Prisma
- SQLite
- Vite
- TanStack Query
- Tailwind
- Radix UI
- Nx
Project snapshot
- Status
- Live self-hosted household inventory system.
- Import
- AI-assisted receipt image import with prepare, review, confirm, confidence scoring, and CSV history.
- Planning
- Recipe library, weekly meal planner, ingredient shortfall calculation, and shopping-list staging.
- Integrations
- Optional planning and notification integrations.
Technical proof
- Nx monorepo with Express, React, Prisma, SQLite, and shared validation schemas.
- HMAC-signed integration keys support scoped API access with IP allowlists and nonce replay protection.
- Admin tooling covers security logs, backup/restore, catalog moderation, and AI provider selection.
What it is
Pantry is a full-stack household inventory manager built as an Nx monorepo with three packages: packages/api (Express 5 + Prisma), packages/web-app (React 18 + Vite), and packages/shared (types and validation schemas). The data layer supports both local and hosted deployments.
The core data model is a pantry list that multiple users can join. Each list owns inventory items, shopping lists, a recipe library, and a weekly meal planner. Members track items by name, brand, category, location, fullness level, purchase date, and expiry date. A dashboard surfaces expiring-soon counts, stock-level breakdowns, and top categories with 30-second auto-refresh.
Architecture tour
Layering and data flow
packages/api/src/routes holds around 80 route files, split between list-scoped endpoints (inventory, recipes, shopping lists, meal planning) and a separate integration-* family (integration-inventory.ts, integration-recipes.ts, integration-shopping.ts, and others) that exists purely for automation clients, not the web app. Routes call into services, which call repositories, which are the only layer touching Prisma. packages/shared sits underneath both packages/api and packages/web-app and holds the Zod validation schemas and TypeScript types both sides import, so a shape change to, say, an inventory item is a single edit instead of two schemas kept in sync by hand. The Prisma schema has grown to 54 models across 30 migrations, SQLite on disk either way.
Nx wires the three packages together and tracks what’s affected by a change (nx affected), which matters more than it sounds: lint, build, and test all default to running only against packages a diff actually touched.
A couple of decisions and why
The integration-* route family is a deliberate split, not an accident of naming. Automation clients (the AI import pipeline, Discord and Notion integrations, bots) authenticate with scoped, HMAC-signed API keys instead of the session cookies the web app uses, and they get their own IP allowlist and nonce-based replay protection (integration-signing-golden-vector.test.ts locks the signing scheme down with a fixed test vector, so a signature-format change fails loudly). Keeping that surface in its own route namespace means the web app’s auth path never has to reason about bot credentials, and the bot path never has to reason about session cookies.
The receipt-import flow is a prepare → review → confirm state machine rather than a single “parse and save” call, because AI-extracted item data is wrong often enough that a review step earns its keep: confidence scores flag the extractions worth double-checking before anything hits inventory.
Testing
Tests run under Vitest per package (490 .test.ts/.test.tsx files across packages, plus 54 Playwright e2e specs), including a dedicated integration layer (integration-auth.middleware.test.ts, integration-inventory-upsert.test.ts, integration-bot-routes.test.ts, integration-ai.test.ts, and others) that exercises routes against a real test database rather than mocked repositories, which is where the HMAC signing and rate-limit behavior actually get checked end to end.
Key features
Import is a first-class workflow. Users can drop grocery receipt images (up to ten at once) and the API extracts structured item data via OpenAI or a configurable Claude backend. Each import goes through a prepare → review → confirm state machine, with confidence scoring and a CSV history log that supports re-import and download. CSV import is also available directly into shopping lists.
The recipe library supports manual entry, URL scrape, and bookmark HTML batch import with deduplication. Recipes carry cover and step images, tags, ratings, comments, and shareable public read-only links. The meal planner slots recipes by breakfast, lunch, and dinner across a week, computes ingredient shortfalls against pantry stock, and stages them into a shopping list.
Integrations and security
Optional integrations support owner-scoped planning and household notifications. Automation access is protected with scoped credentials, allowlists, and replay protection.
Auth is JWT bearer tokens with httpOnly refresh cookies and tokenVersion-based revocation. Admin tooling covers user management, security event logs, catalog moderation (stores, brands, ingredients), backup/restore, and configurable app settings including AI provider selection.