Back to portfolio
TypeScriptNext.jsPrismaPostgreSQLFlutterDocker

Maaleto - Life Management Platform

A backend-first life-management platform — finance, tasks, habits and a Jalali calendar behind one API that the web app, an Android client and AI agents all consume unchanged

Overview

Maaleto is a backend-first life-management platform — finance, tasks, habits, a Jalali calendar, notes, reports and AI — sitting behind a single API. In this architecture the website is only "Client #1": an Android app, desktop, a Telegram bot, AI agents and MCP clients all have to consume the same backend without a single backend change.

The Problem

Most personal-management apps start with one client and put the business logic inside it. The second client then means rewriting half the product — and worse, maintaining two implementations of the same rule that quietly drift apart.

  • Balance calculation, habit streaks and kanban ordering give different answers per client the moment they live in the client.

  • A Persian calendar isn't a display concern. Recurrence, Iranian public holidays and reporting windows all have to be correct server-side.

  • Without enforced layer boundaries, importing the database "just this once" inside a UI component quietly becomes the permanent architecture.

The Solution

An npm-workspaces monorepo where the architectural boundaries are enforced mechanically rather than by convention:

  • packages/contracts — Zod schemas as the formal contract between the server and every client.

  • packages/core — the only place business logic exists, and the only place allowed to reach the database. Zero Next.js imports.

  • packages/db — Prisma schema and repository classes, each behind an interface so services stay testable.

  • apps/web — Next.js 16 App Router UI plus /api/v1 route handlers that are thin controllers only.

  • apps/worker — BullMQ consumers and cron jobs.

  • mobile — a Flutter Android client consuming exactly the same API.

Those boundaries are declared with eslint-plugin-boundaries at the repo root, so importing @lifeos/db from a UI component fails lint — it isn't merely discouraged.

Modules

  • Auth: OTP login, short-lived JWT access tokens paired with opaque rotating refresh tokens, and session/device management.

  • Finance: wallets, categories, transactions and budgets, with a balance that is always derived and never stored, plus an idempotent create/update path via an Idempotency-Key header.

  • Tasks: tasks, subtasks, projects and labels, with server-owned manual kanban ordering.

  • Calendar: Jalali and Gregorian events with rrule recurrence, Iranian holiday lookup, an agenda view and a Saturday-start week grid.

  • Habits: daily and weekly check-ins with a streak computed on read from check-in history rather than stored.

  • Reports & Notifications: in-app notifications, a budget-exceeded trigger fired from Finance, and a composed dashboard report endpoint.

The Outcome

  • All six modules ship real UI — Tailwind v4, shadcn/ui and TanStack Query, Farsi and RTL throughout.

  • The Flutter Android client builds for release and consumes the same API with no backend changes.

  • The full production stack (Docker Compose, migrations, standalone app) was smoke-deployed against a real Ubuntu 26.04 VM: all five migrations applied to a genuinely empty Postgres and a complete request-OTP → verify-OTP round trip succeeded.

  • Decisions that would be expensive to reverse are recorded as ADRs in docs/decisions/, including the alternatives that were rejected and why.

Current status: the deployment pipeline is verified end to end, but no internet-facing server is provisioned yet, so there is no public live demo. The source is available at github.com/Nikosonz/lifeos.

Tech Stack

  • Language & contracts: TypeScript, Zod

  • Backend: Next.js 16 (App Router), Prisma, PostgreSQL, Redis, BullMQ

  • Frontend: React, Tailwind CSS v4, shadcn/ui, TanStack Query

  • Mobile: Flutter (Android)

  • Infrastructure: Docker, Docker Compose, GitHub Actions

Maaleto - Life Management Platform | Pouya Karimi