Skip to content

Repository files navigation

Despensa

Status: Experimental License: MIT Go backend React frontend TypeScript PostgreSQL Docker

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.

Overview

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.

Problem

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.

Solution

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.

Features

  • 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

Architecture

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"]
Loading

Despensa is a modular monolith. There is one backend, one frontend, and one database—not a set of premature microservices. See Architecture.

Technology Stack

  • 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

Household Model

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
Loading

Inventory Model

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.

Shopping Lists

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 Tracking

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.

Expiration Management

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.

Waste Tracking

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.

Realtime Collaboration

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.

Screens / User Experience

  • 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

Getting Started

Requirements: Go 1.24+, Node.js 22+, npm, and PostgreSQL 16+.

cp .env.example .env
cd backend
go run ./cmd/migrate
go run ./cmd/api

In another terminal:

cd frontend
npm install
npm run dev

Replace SESSION_SECRET before any exposed deployment. These commands are documentation only; they were not executed as validation for v0.1.

First Access

  1. Open http://localhost:5173/register after the database, API, and frontend are available.
  2. Enter your name, email, password, and the name of the first household.
  3. Despensa creates the user, the household, owner membership, and initial categories in one transaction, then opens an HttpOnly session.
  4. In Products, create the first product and define its unit and minimum stock.
  5. In Pantry, add a batch with quantity and optional expiration, price, lot, and store.
  6. In Shopping, create a list and add catalog products or free-text items.
  7. 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.

Configuration

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

API

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.

Database

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.

Docker

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 --build

Docker 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.

Project Structure

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

Security

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.

Design Decisions

Limitations

  • 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.

Roadmap

v0.2

  • barcode camera scanning
  • receipt import
  • improved PWA/offline support
  • notification delivery
  • recurring shopping suggestions

v0.3

  • optional household spending budgets
  • price comparison improvements
  • recipe/meal planning integration
  • intelligent shopping suggestions

v0.4

  • native mobile experiments
  • optional public price sharing
  • external retailer integrations

No milestone has a promised date.

Contributing

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.

License

Despensa is licensed under the MIT License. Copyright 2026 Thiago Montozo.

About

Smart household inventory, grocery planning and price tracking platform.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages