Skip to content

Architecture

Saiful Alam Rakib edited this page Apr 22, 2026 · 1 revision

Architecture

Bill Organizer is a full-stack web application using an Inertia.js monolith — a single Laravel application serving a Vue.js SPA without a separate API layer for the frontend, while also exposing a versioned REST API for external clients.


Tech Stack

Backend

Layer Technology
Framework Laravel 12 (PHP 8.3+)
Database SQLite (default) · MySQL 8.0+
Authentication Laravel Sanctum (API tokens) + session auth (web)
API Inertia.js (SPA bridge) + REST API v1
Queue System Database-backed queue
Mail Configurable mail drivers (SMTP, log, etc.)
Testing Pest PHP
Code Style Laravel Pint (PSR-12)

Frontend

Layer Technology
Framework Vue.js 3 with TypeScript
Build Tool Vite
UI Tailwind CSS v4
Components Reka UI, Lucide icons
Charts ApexCharts
Forms VeeValidate + Zod validation
State Vue 3 Composition API

DevOps

Layer Technology
Containerization Docker + Laravel Sail
CI/CD GitHub Actions
Package Manager Yarn (frontend) · Composer (backend)
Linting ESLint, Prettier (frontend) · Pint (backend)

Multi-Tenant Design

The application uses a team-based multi-tenancy approach:

User ──┬──► Team A (owner)
       ├──► Team B (member)
       └──► Team C (member)

Active Team = single context for all data operations
  • Teams are the central organizational unit.
  • Users can belong to multiple teams and switch between them freely.
  • Active Team — each user session operates within one team at a time.
  • Data Isolation — Eloquent global scopes (TeamScope) automatically filter all queries to the current team, preventing cross-tenant data leaks.

Directory Structure

Backend (app/)

app/
├── Enums/               # PHP enums (BillStatus, RecurrencePeriod, etc.)
├── Helpers/             # Helper functions and utility classes
├── Http/
│   ├── Controllers/
│   │   ├── Api/V1/      # REST API controllers (AuthController, BillController, …)
│   │   └── (web)        # Inertia web controllers
│   ├── Middleware/       # Custom middleware (team middleware, etc.)
│   └── Resources/
│       └── Api/V1/      # API resource transformers (BillResource, etc.)
├── Jobs/                # Queued jobs
├── Mail/                # Mailable classes
├── Models/
│   ├── Pivots/          # Custom pivot models (e.g. TeamUser)
│   └── Scopes/          # Eloquent global scopes (TeamScope)
├── Notifications/        # Laravel notification classes
├── Providers/            # Service providers
└── Traits/              # Reusable traits (HasTeam, HasMetaData)

Frontend (resources/js/)

resources/js/
├── Components/           # Shared Vue components
│   └── notes/            # Note-specific components (NoteCard, NoteViewDialog)
├── Layouts/              # Page layout wrappers
├── Pages/                # Inertia page components (Bills, Categories, Notes, …)
├── Types/                # TypeScript type definitions
├── Utils/                # Utility helpers
└── composables/          # Vue composables (useNoteExport, etc.)

Routes

routes/
├── web.php               # Inertia web routes
├── api.php               # API entry point (loads versioned files)
└── api/
    └── v1.php            # All /api/v1/* route definitions

Key Models & Relationships

User

User
 ├── belongsToMany(Team)           → teams (via team_user pivot)
 ├── belongsTo(Team, active_team)  → currently active team
 ├── hasMany(Bill)
 ├── hasMany(Category)
 └── HasApiTokens (Sanctum)

Team

Team
 ├── belongsTo(User, owner)        → team owner
 ├── belongsToMany(User)           → members
 └── Global scope for data isolation

Bill

Bill
 ├── belongsTo(User)
 ├── belongsTo(Team)
 ├── belongsTo(Category)
 ├── hasMany(Transaction)
 └── morphToMany(Note)

Transaction

Transaction
 ├── belongsTo(Bill)
 └── belongsTo(Team)

Note

Note
 ├── belongsTo(User)
 ├── belongsTo(Team)           → null for personal notes
 └── morphedByMany(Bill, …)   → polymorphic relationship

Database Schema (Key Tables)

Table Purpose
users User accounts, active team reference
teams Team workspace definitions
team_user Many-to-many users ↔ teams pivot
bills Bill records with recurrence and trial support
categories Bill categories (with icon and color)
transactions Payment records linked to bills
notes Polymorphic notes (personal or team-scoped)
meta_data Flexible key-value store for preferences and flags
personal_access_tokens Sanctum API tokens

Key Traits

HasTeam

Automatically assigns the current team ID when creating a model:

trait HasTeam
{
    protected static function bootHasTeam()
    {
        static::creating(function ($model) {
            $model->team_id = Team::current()->id;
        });
    }
}

HasMetaData

Flexible key-value metadata storage on any model:

trait HasMetaData
{
    public function setMeta(string $key, $value): void
    public function getMeta(string $key, $default = null)
    public function deleteMeta(string $key): void
}

API Architecture

The REST API follows versioned namespacing:

/api/v1/auth/*          → AuthController
/api/v1/bills/*         → BillController
/api/v1/categories/*    → CategoryController
/api/v1/transactions/*  → TransactionController
/api/v1/teams/*         → TeamController
/api/v1/notes/*         → NoteController
/api/v1/webhooks/*      → WebhookController
/api/v1/users/*         → UserController

All responses follow a standardized envelope:

{
  "success": true,
  "message": "Operation successful",
  "data": { ... }
}

Feature Flags

The application supports runtime feature flags stored in user metadata:

Flag Description
enable_notes Enable / disable the notes module
enable_calendar Enable / disable calendar view
email_notification Email notification preferences
web_notification Web notification preferences

Live Link: bills.msar.me

API Docs: API

Open API Collection: API Collection

Clone this wiki locally