The Problem

Invio targets small businesses and freelancers who need invoicing without SaaS lock-in. The pain point is concrete: hosted invoicing tools charge monthly fees, hold customer data hostage, and force clients through account creation just to view a bill. Invio replaces that with a self-hosted system where invoices are shared via secure, tokenized links—no client login required.

What This Does

Invio is a full-stack invoicing application with a SvelteKit frontend (frontend/src/routes/) and a Bun/TypeScript backend (backend/src/). The backend handles authentication (JWT, OIDC, two-factor), invoice generation (PDF, UBL, Factur-X, FatturaPA), customer/product management, and i18n across four locales. The frontend provides dashboards, invoice editors, and public invoice views.

The system runs as Docker containers (docker-compose.yml) with a supervisord.conf orchestrating processes. A notable feature is the translation pipeline (.github/scripts/translations.py) that keeps locale files in sync.

How It Is Wired

Execution starts at backend/src/app.ts which mounts three route groups: admin.ts, auth.ts, and public.ts. The admin routes (19 imports, instability 0.95) are the most volatile—they touch controllers, models, and utils. The auth flow routes through middleware/auth.ts for JWT verification and rateLimiter.ts for throttling.

The import graph shows 83 internal modules with 100 edges and zero circular dependencies—clean structure. The hubs are backend/src/types/index.ts (17 dependents) and backend/src/database/init.ts (14 dependents, 826 lines). Any change to types/index.ts ripples across a quarter of the codebase; it should stay stable.

The backend hits SQLite (backend/invio-demo.db), the filesystem for PDF/logo storage, and SMTP for email. The frontend proxies API calls through frontend/src/routes/api/[...path]/+server.ts to the backend. Public invoice access flows through frontend/src/routes/public/invoices/[token]/+page.svelte.

How To Use It

git clone https://github.com/moses-y/Invio
cd Invio
docker compose up -d

The .env.example at root defines required environment variables (database paths, JWT secrets, SMTP config). The docker-compose.yml builds both backend/ and frontend/ images with their respective Dockerfiles. For development, docker-compose-dev.yml mounts source for hot reload. The frontend has bun.lock for reproducible installs; the backend uses deno.lock.

Real-World Use

A consultancy runs Invio on a $5 VPS behind Caddy. A project manager creates an invoice, Invio generates a PDF, and the client receives a https://invoices.example.com/public/invoices/<token> link. The client pays via bank transfer without creating an account. The consultant exports Factur-X XML for their accounting software.

Code Health & Issues

Static analysis found 44 findings (3 high, 41 medium) across 6 kinds. The critical ones:

  • High - Oversized files: backend/src/database/init.ts (826 lines), backend/src/controllers/invoices.ts, backend/src/routes/admin.ts—hard to hold in one head, changes ripple widely.
  • High - Duplicated code: 220 repeated 6-line blocks across 52 files, particularly in customers.ts and invoices.ts. Extract shared helpers.
  • Medium - Empty catch block: frontend/src/lib/backend.ts silently discards errors.
  • Medium - High branching: backend/src/controllers/settings.ts has 23 branch points over 65 lines.

The security audit flags: wildcard CORS with credentials (backend/src/app.ts), unpinned Docker base images (oven/bun:1), and unpinned GitHub Actions versions. No test suite exists across 115 source files—regressions ship undetected.

The Bottom Line

Invio is a solid, feature-complete invoicing app with clean module boundaries and no circular dependencies. It's not production-hardened yet: the 826-line init file, missing tests, and wildcard CORS need attention before trusting it with real client data. For a solo developer or small team willing to fix the security gaps, it's a legitimate alternative to paying $20/month for FreshBooks.