Smart household inventory, grocery planning and price tracking platform.
Current status: Experimental (v0.1.0). Despensa is a portfolio-grade full-stack project. Automated tests and runtime validation are intentionally outside this first delivery.
Despensa gives a household one place to understand what is at home, what expires soon, what needs to be purchased, what was consumed or discarded, and how prices changed across its own shopping history. It is designed as a responsive web application that feels comfortable on a phone while remaining useful on desktop.
Domestic inventory is rarely just a number. The same product can be present in several lots with different expiration dates, people add and check shopping items at the same time, and a purchase creates both stock and price history. Generic task lists do not model those relationships, while spreadsheets are difficult to use in the supermarket.
Despensa models households, products, batches, stock movements, shopping lists, stores, and price records as connected concepts. PostgreSQL is the source of truth. A compact Go API owns validation, authorization, transactions, and realtime list events. The React PWA provides task-oriented screens for home and supermarket use.
- Multiple households per user, with owner and member roles
- Secure registration, login, logout, and current-session endpoints
- Household-scoped products, custom categories, brands, units, and optional barcodes
- Batch-aware inventory with quantity, lot, source store, price, and optional expiration
- IN, OUT, ADJUSTMENT, EXPIRED, and DISCARDED movement history
- Low-stock detection without automatic or surprising list changes
- Shared shopping lists and Server-Sent Events updates
- Mobile-first Modo Compra with quantity, price, store, brand, and expiration capture
- Transactional purchase completion that can create inventory and price records
- Personal price history, summary statistics, and simple charts
- Expiration windows for expired, today, 3, 7, and 30 days
- Waste summaries based on explicit expired/discarded movements
- Structured logs, request IDs, bounded JSON bodies, and sanitized errors
- Health, PostgreSQL readiness, SQL migrations, graceful shutdown, and containers
- Responsive React interface with desktop sidebar and mobile bottom navigation
- PWA manifest, installable icon, and a minimal app-shell fallback service worker
flowchart LR
Browser["React PWA"] -->|"HTTP + HttpOnly cookie"| API["Go API / Chi"]
Browser -->|"SSE shopping events"| API
API --> Auth["Session and household authorization"]
API --> Services["Application handlers"]
Services --> Repositories["Repository layer"]
Repositories --> PostgreSQL["PostgreSQL / source of truth"]
Services --> Hub["In-process SSE hub"]
Despensa is a modular monolith. There is one backend, one frontend, and one database—not a set of premature microservices. See Architecture.
- Backend: Go,
net/http, Chi, pgx, bcrypt,log/slog - Frontend: TypeScript strict, React, Vite, React Router, Tailwind CSS, Lucide, Recharts
- Database: PostgreSQL with versioned SQL migrations
- Realtime: Server-Sent Events from an in-process, lifecycle-aware hub
- Deployment: multi-stage Dockerfiles and Docker Compose
A user may belong to multiple households. Every domain read or mutation after authentication requires X-Household-ID, and the backend verifies membership rather than trusting the header. Owners can add an existing Despensa user by email. Household switching never merges inventory or shopping history.
erDiagram
USER ||--o{ HOUSEHOLD_MEMBER : joins
HOUSEHOLD ||--o{ HOUSEHOLD_MEMBER : contains
USER ||--o{ HOUSEHOLD : creates
HOUSEHOLD ||--o{ PRODUCT : owns
HOUSEHOLD ||--o{ SHOPPING_LIST : owns
Product describes identity and replenishment policy. InventoryItem is a physical batch, so four cartons expiring next week do not hide two cartons expiring tomorrow. Each batch can hold source store, purchase price, date received, lot, and expiration. Mutations append InventoryMovement records so consumption and waste remain explainable.
Lists accept catalog products and free-text items. A catalog item can flow from planned quantity to purchased quantity, price, store, brand, and expiration. Completing a list in one database transaction marks the list complete, creates batches for checked catalog items, appends IN movements, and records supplied prices.
Price records belong to the household and product and may reference a store. The product view shows the latest, previous, minimum, maximum, average, percentage change, lowest historical store, and a timeline. These are user-supplied observations—not an official inflation index.
The API derives expiration state against the current database date: expired, today, within 3 days, within 7 days, within 30 days, later, or no expiration. Expired batches remain visible until the user consumes, adjusts, or marks them expired/discarded.
EXPIRED and DISCARDED movements feed the monthly dashboard and category summary. Despensa presents operational data without moral judgment. Because products can use different units, the aggregate quantity is explicitly presented as a directional indicator.
The browser opens one household-scoped SSE stream. Adding, changing, checking, or removing an item and completing a list publishes a compact event. Other open list screens reload authoritative state. Events carry no passwords or session tokens and are ephemeral; PostgreSQL remains canonical.
- Login and registration
- Household dashboard with seven high-signal cards
- Pantry with expiry filters and batch actions
- Product catalog and price detail charts
- Shopping list overview and collaborative item list
- Modo Compra optimized for checking items in a store
- Price history, stores, waste insights, household members, and settings
- Loading, error, empty, confirmation, status badge, and toast states
Requirements: Go 1.24+, Node.js 22+, npm, and PostgreSQL 16+.
cp .env.example .env
cd backend
go run ./cmd/migrate
go run ./cmd/apiIn another terminal:
cd frontend
npm install
npm run devReplace SESSION_SECRET before any exposed deployment. These commands are documentation only; they were not executed as validation for v0.1.
- Open
http://localhost:5173/registerafter the database, API, and frontend are available. - Enter your name, email, password, and the name of the first household.
- Despensa creates the user, the household, owner membership, and initial categories in one transaction, then opens an HttpOnly session.
- In Products, create the first product and define its unit and minimum stock.
- In Pantry, add a batch with quantity and optional expiration, price, lot, and store.
- In Shopping, create a list and add catalog products or free-text items.
- In Household, the owner can add another already registered user by email.
Returning users access http://localhost:5173/login. No token needs to be copied into the browser, and no sensitive credential is stored in localStorage. For a guided walkthrough of registration, inventory, purchasing, price history, expiration, and household sharing, see First Access and User Flows.
| Variable | Example/default | Purpose |
|---|---|---|
APP_ENV |
development |
Runtime security policy |
API_PORT |
8080 |
Backend listening port |
DATABASE_URL |
PostgreSQL URL | Required database connection |
WEB_ORIGIN |
http://localhost:5173 |
Exact credentialed CORS origin |
SESSION_SECRET |
change-me |
HMAC key; production requires 32+ characters |
SESSION_TTL_HOURS |
720 |
Opaque session lifetime |
MIGRATIONS_DIR |
migrations |
Migration directory used by the migration command |
LOG_LEVEL |
info |
Structured slog level |
VITE_API_URL |
http://localhost:8080 |
Browser API base URL |
All application endpoints use /api/v1. Authenticated household routes require the HttpOnly session cookie and X-Household-ID. Errors include a stable code, friendly message, and request ID. See API.
The initial migration creates users, sessions, households, memberships, categories, stores, products, inventory batches, movements, shopping lists/items, and price records. It includes UUID primary keys, tenant indexes, filtered expiry/barcode indexes, foreign keys, unique constraints, checks, and enums. See Database.
The backend and frontend Dockerfiles are multi-stage and run as non-root users. Compose starts PostgreSQL, applies migrations, starts the API, and serves the SPA through unprivileged Nginx.
SESSION_SECRET="replace-with-at-least-32-characters" docker compose up --buildDocker configuration was created but intentionally not executed in this delivery.
The Compose default uses development cookie policy so first access works over local HTTP. Set APP_ENV=production only behind HTTPS; production enables the Secure cookie requirement.
backend/
cmd/api/ API entrypoint and graceful shutdown
cmd/migrate/ versioned SQL migration runner
internal/auth/ password and session cryptography
internal/database/ pgx pool and migration lifecycle
internal/domain/ API/domain models
internal/http/ handlers, middleware, and routing
internal/realtime/ in-process SSE hub
internal/repository/ centralized PostgreSQL queries
migrations/ forward and rollback SQL
frontend/
public/ PWA manifest, icon, service worker
src/app/ route tree
src/components/ shell and reusable UI
src/hooks/ household API and realtime hooks
src/pages/ task-oriented screens
src/services/ credentialed HTTP client
src/store/ session/household UI state
docs/ architecture, domain, API, security, flows, ADRs
Passwords use bcrypt. Sessions are 256-bit opaque tokens; only an HMAC-SHA-256 digest is stored. The cookie is HttpOnly, SameSite=Lax, and Secure in production. Membership is checked on every household route. CORS is exact-origin and credentialed, request bodies and fields are bounded, SQL is parameterized, and API errors omit stack traces. See Security.
- ADR 0001 — Go backend
- ADR 0002 — React and TypeScript
- ADR 0003 — PostgreSQL source of truth
- ADR 0004 — SSE for shopping lists
- ADR 0005 — Batch-aware inventory
- No password reset, email verification, invitation token flow, or account deletion yet.
- Adding a member requires that person to have already registered.
- SSE is in-process; multi-instance fan-out would require a shared event backbone.
- The basic service worker is not a full offline data-sync solution.
- Price and waste totals may combine different package sizes/units and should be interpreted accordingly.
- No barcode camera scanning, receipt import, notifications, or automatic suggestions.
- No automated tests, CI, runtime benchmark, or production-readiness claim in v0.1.
- barcode camera scanning
- receipt import
- improved PWA/offline support
- notification delivery
- recurring shopping suggestions
- optional household spending budgets
- price comparison improvements
- recipe/meal planning integration
- intelligent shopping suggestions
- native mobile experiments
- optional public price sharing
- external retailer integrations
No milestone has a promised date.
Focused issues and pull requests are welcome. Keep household isolation explicit, keep SQL out of HTTP handlers, avoid logging credentials or arbitrary request bodies, and document domain-semantic changes.
Despensa is licensed under the MIT License. Copyright 2026 Thiago Montozo.