Loopar Introduction
loopar-webpage

Concepts Overview

Loopar is a meta-framework β€” a framework that can build itself. At its core lies a powerful concept: Entity, the master builder that creates other builders.


The Philosophy

Traditional frameworks require you to write code for every model, view, and controller. Loopar inverts this: you define what you want, and the framework generates everything else.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    THE LOOPAR WAY                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   Traditional:  Code β†’ Application                          β”‚
β”‚                                                             β”‚
β”‚   Loopar:       Definition β†’ Code β†’ Application             β”‚
β”‚                      ↑                                      β”‚
β”‚                 (drag & drop)                               β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Concepts

ConceptDescription
EntityThe master builder β€” defines data models and generates everything
BuilderSpecialized entity types (Page, View, Form, Controller)
AppA self-contained application package
ModuleGroups related entities within an app
DocumentA data-centric entity (stored in database)
PageA presentation-centric entity (web pages)
ComponentUI elements used in drag-and-drop design

The Auto-Recursive Magic

What makes Loopar unique is that Entity is built using Entity. The system bootstraps itself:

  1. Entity defines the structure of all models
  2. Entity creates Builders (specialized entity types)
  3. Builders create specific model types (Pages, Forms, Views)
  4. You use Builders to create your application

This means you can extend Loopar using Loopar itself.

Entity

Entity is the foundational concept in Loopar. It's the master builder that defines data models and automatically generates all the code needed to work with them.


What Entity Creates

When you define an Entity, Loopar automatically generates:

GeneratedDescription
Database TableWith proper schema and migrations
Sequelize ModelORM model with validations
REST APIFull CRUD endpoints
React FormWith all field types rendered
List ViewWith search, filters, and pagination
ControllerWith lifecycle hooks

Entity Structure

Every Entity consists of three optional files:

/modules/[module-name]/[entity-name]/
β”œβ”€β”€ entity-name.json      # Field definitions & metadata
β”œβ”€β”€ entity-name.js        # Server-side controller (optional)
└── entity-name.jsx       # Client-side component (optional)

The JSON File (Required)

Defines the entity's fields, metadata, and structure:

{
  "name": "Customer",
  "module": "CRM",
  "is_single": false,
  "fields": [
    { "name": "name", "type": "Data", "required": true },
    { "name": "email", "type": "Email", "unique": true },
    { "name": "status", "type": "Select", 
      "options": ["Lead", "Active", "Inactive"] }
  ]
}

The JS File (Optional)

Server-side controller for custom business logic:

import { BaseDocument } from "loopar";

export default class Customer extends BaseDocument {
  constructor(props) {
    super(props);
  }

  async beforeInsert() {
    // Runs before saving a new record
    this.created_at = new Date();
  }

  async afterInsert() {
    // Runs after saving a new record
    await this.sendWelcomeEmail();
  }

  async validate() {
    if (!this.email.includes('@')) {
      throw new Error('Invalid email format');
    }
  }
}

The JSX File (Optional)

Client-side React component for custom UI:

import { BaseForm } from "@loopar/components";

export default function CustomerForm(props) {
  return (
    <BaseForm {...props}>
      {/* Custom UI elements */}
    </BaseForm>
  );
}

Entity Types

Typeis_singleDescription
DocumentfalseMultiple records (customers, products)
SingletrueOne record only (settings, config)

Document vs Single

Document entities store multiple records:

  • Customer, Product, Order, Invoice
  • Have list views with pagination
  • CRUD operations on individual records

Single entities store only one record:

  • System Settings, Company Info, App Config
  • No list view, direct access to the form
  • Perfect for configuration data

Builders

Builders are specialized entities designed to create specific types of models. While Entity is the general-purpose builder, each Builder is optimized for a particular use case.


Why Builders?

Entity is powerful but generic. Builders provide:

  • Focused metadata β€” Only relevant fields for the model type
  • Optimized UI β€” Tailored design experience
  • Specialized behavior β€” Logic specific to the model type

Builder Types

Page Builder

Creates complete web pages: homepages, landing pages, blogs, documentation.

FeatureDescription
PurposeDesign full web pages visually
OutputStandalone page with route
ComponentsSections, rows, columns, text, images, etc.
Use CasesHomepage, About, Contact, Blog posts
/pages/homepage/
β”œβ”€β”€ homepage.json       # Page structure & components
β”œβ”€β”€ homepage.js         # Page controller (optional)
└── homepage.jsx        # Custom React component (optional)

View Builder

Creates data display components that connect to a model.

FeatureDescription
PurposeVisualize data from an entity
OutputConnected view component
ComponentsTables, cards, charts, grids
Use CasesDashboards, reports, data displays

Views connect to a source entity and render its data with custom layouts.


Form Builder

Creates forms for data collection without requiring a full entity.

FeatureDescription
PurposeCollect and submit data
OutputForm that submits to a controller
ComponentsInputs, selects, checkboxes, buttons
Use CasesLogin, registration, contact forms
/forms/login-form/
β”œβ”€β”€ login-form.json     # Form fields & layout
└── login-form.js       # Form submission handler

Controller Builder

Creates server-side controllers for custom actions without a model.

FeatureDescription
PurposeHandle specific backend actions
OutputAPI routes and actions
ComponentsN/A (server-side only)
Use CasesAuth endpoints, webhooks, integrations
// login-controller.js
export default class LoginController extends BaseController {
  async actionLogin() {
    const { email, password } = this.request.body;
    // Authentication logic
  }

  async actionLogout() {
    // Logout logic
  }
}

Choosing the Right Builder

NeedBuilder
Database model with CRUDEntity
Web page designPage Builder
Display existing dataView Builder
Standalone formForm Builder
Custom API endpointsController Builder

App

App is Loopar's packaging system. An App is a self-contained application that bundles modules, entities, pages, and assets into an installable unit.


What is an App?

Think of an App as an independent program within Loopar:

  • Contains all its modules and entities
  • Can be installed/uninstalled on any tenant
  • Can be shared, distributed, or sold
  • Has its own version and dependencies

App Structure

/apps/
└── my-crm/                      # App directory
    β”œβ”€β”€ app.json                 # App metadata
    β”œβ”€β”€ modules/                 # App modules
    β”‚   β”œβ”€β”€ contacts/            # Module: Contacts
    β”‚   β”‚   β”œβ”€β”€ customer/        # Entity: Customer
    β”‚   β”‚   β”‚   β”œβ”€β”€ customer.json
    β”‚   β”‚   β”‚   β”œβ”€β”€ customer.js
    β”‚   β”‚   β”‚   └── customer.jsx
    β”‚   β”‚   └── lead/            # Entity: Lead
    β”‚   β”‚       └── lead.json
    β”‚   └── sales/               # Module: Sales
    β”‚       β”œβ”€β”€ quote/
    β”‚       └── invoice/
    └── public/                  # Static assets
        β”œβ”€β”€ images/
        └── styles/

App Metadata (app.json)

{
  "name": "My CRM",
  "version": "1.0.0",
  "description": "Customer relationship management app",
  "author": "Your Name",
  "dependencies": ["loopar"],
  "modules": ["contacts", "sales"]
}

App Lifecycle

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    Create    β”‚ β†’   β”‚   Develop    β”‚ β†’   β”‚   Install    β”‚
β”‚   (in dev)   β”‚     β”‚  (add models)β”‚     β”‚ (on tenant)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                 ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Publish    β”‚ ←   β”‚    Export    β”‚ ←   β”‚    Test      β”‚
β”‚ (marketplace)β”‚     β”‚   (package)  β”‚     β”‚  (staging)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

App Manager

Access the App Manager at desk/App Manager/view to:

ActionDescription
CreateStart a new app
InstallInstall app on current tenant
UninstallRemove app from tenant
UpdateUpdate to newer version
ExportPackage app for distribution
ImportInstall from repository or file

Default App Creation

When you create an App, Loopar automatically:

  1. Creates the app directory structure
  2. Generates a module with the same name
  3. Sets up the app.json metadata
  4. Registers the app in the system

All entities you create are linked to the app through their module.

Module

Module is Loopar's organizational unit. Modules group related entities within an app, providing logical separation and structure.


What is a Module?

A Module is like a folder that contains related functionality:

  • Groups entities by domain (sales, inventory, HR)
  • Provides namespace isolation
  • Enables selective installation
  • Organizes the Desk sidebar

Module Structure

/apps/my-app/modules/
β”œβ”€β”€ core/                    # Core functionality
β”‚   β”œβ”€β”€ user/
β”‚   β”œβ”€β”€ role/
β”‚   └── settings/
β”œβ”€β”€ sales/                   # Sales module
β”‚   β”œβ”€β”€ customer/
β”‚   β”œβ”€β”€ quote/
β”‚   └── invoice/
└── inventory/               # Inventory module
    β”œβ”€β”€ product/
    β”œβ”€β”€ warehouse/
    └── stock-entry/

Module in the Desk

Modules appear in the Desk sidebar, grouping their entities:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  DESK                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β–Ό Core                     β”‚
β”‚      Users                  β”‚
β”‚      Roles                  β”‚
β”‚      Settings               β”‚
β”‚  β–Ό Sales                    β”‚
β”‚      Customers              β”‚
β”‚      Quotes                 β”‚
β”‚      Invoices               β”‚
β”‚  β–Ό Inventory                β”‚
β”‚      Products               β”‚
β”‚      Warehouses             β”‚
β”‚      Stock Entries          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Creating a Module

  1. Go to Desk β†’ Module β†’ New
  2. Enter module details:
    • Name: Module identifier (e.g., sales)
    • Label: Display name (e.g., Sales)
    • App: Parent app
    • Icon: Module icon for the sidebar
  3. Save the module

Module vs App

AspectAppModule
ScopeComplete applicationFunctional group
ContainsModules, assets, configEntities only
InstallableYesNo (part of app)
ExampleCRM AppSales Module

Fields & Components

Loopar provides a rich set of field types and UI components for building your entities and pages.


Data Fields

Fields that store data in the database:

Field TypeDescriptionDatabase Type
DataSingle line textVARCHAR(255)
TextMulti-line textTEXT
IntInteger numberINT
FloatDecimal numberFLOAT
CurrencyMoney valuesDECIMAL(18,6)
DateDate onlyDATE
DateTimeDate and timeDATETIME
TimeTime onlyTIME
CheckBoolean (yes/no)TINYINT(1)
SelectDropdown optionsVARCHAR(255)
LinkReference to another entityVARCHAR(255)
TableChild table (one-to-many)β€”
EmailEmail with validationVARCHAR(255)
PhonePhone numberVARCHAR(50)
PasswordEncrypted passwordVARCHAR(255)
ColorColor pickerVARCHAR(20)
ImageImage uploadTEXT (path)
FileFile uploadTEXT (path)
MarkdownRich markdown textLONGTEXT
CodeCode editorLONGTEXT
JSONJSON dataLONGTEXT

Layout Components

Components for structuring pages (no database storage):

ComponentDescription
SectionFull-width container
RowHorizontal container with columns
ColColumn within a row
PanelCard-like container with styling
DivGeneric container
TabTabbed content container
CollapseCollapsible content

Display Components

ComponentDescription
TitleHeading text (h1-h6)
SubtitleSecondary heading
ParagraphBody text
ImageDisplay image
IconLucide icon
ButtonClickable button
LinkNavigation link
MarkdownRendered markdown
Code BlockSyntax-highlighted code

Interactive Components

ComponentDescription
CarouselImage/content slider
ParticlesAnimated particle background
Banner ImageHero banner with overlay
Feature CardFeature highlight card
Pricing CardPricing table card
TestimonialCustomer testimonial

Field Properties

Common properties available on most fields:

PropertyDescription
nameField identifier (snake_case)
labelDisplay label
typeField type
requiredMust have a value
uniqueMust be unique in database
defaultDefault value
hiddenHide from UI
readonlyCannot be edited
optionsFor Select fields
descriptionHelp text
classCSS classes

Lifecycle Hooks

Loopar provides hooks at key points in an entity's lifecycle. Use these to add custom business logic.


Server-Side Hooks

Defined in the entity's .js file:

import { BaseDocument } from "loopar";

export default class Invoice extends BaseDocument {
  
  // ═══════════════════════════════════════
  // VALIDATION
  // ═══════════════════════════════════════
  
  async validate() {
    // Runs before any save operation
    // Throw error to prevent save
    if (this.total < 0) {
      throw new Error("Total cannot be negative");
    }
  }

  // ═══════════════════════════════════════
  // INSERT HOOKS (new records)
  // ═══════════════════════════════════════
  
  async beforeInsert() {
    // Before saving new record
    this.created_by = this.session.user;
    this.status = "Draft";
  }

  async afterInsert() {
    // After saving new record
    await this.notifyAccountant();
  }

  // ═══════════════════════════════════════
  // UPDATE HOOKS (existing records)
  // ═══════════════════════════════════════
  
  async beforeUpdate() {
    // Before updating existing record
    this.modified_by = this.session.user;
  }

  async afterUpdate() {
    // After updating existing record
    if (this.status === "Paid") {
      await this.updateCustomerBalance();
    }
  }

  // ═══════════════════════════════════════
  // SAVE HOOKS (both insert and update)
  // ═══════════════════════════════════════
  
  async beforeSave() {
    // Before any save (insert or update)
    this.calculateTotals();
  }

  async afterSave() {
    // After any save (insert or update)
    await this.updateStockLevels();
  }

  // ═══════════════════════════════════════
  // DELETE HOOKS
  // ═══════════════════════════════════════
  
  async beforeDelete() {
    // Before deleting record
    if (this.status === "Paid") {
      throw new Error("Cannot delete paid invoice");
    }
  }

  async afterDelete() {
    // After deleting record
    await this.reverseStockEntries();
  }
}

Hook Execution Order

On Insert (new record)

1. validate()
2. beforeSave()
3. beforeInsert()
4. β†’ DATABASE INSERT β†’
5. afterInsert()
6. afterSave()

On Update (existing record)

1. validate()
2. beforeSave()
3. beforeUpdate()
4. β†’ DATABASE UPDATE β†’
5. afterUpdate()
6. afterSave()

On Delete

1. beforeDelete()
2. β†’ DATABASE DELETE β†’
3. afterDelete()

Accessing Data in Hooks

async beforeSave() {
  // Access field values
  const customerName = this.customer_name;
  
  // Access session/user info
  const currentUser = this.session.user;
  
  // Access request data
  const requestBody = this.request?.body;
  
  // Query other documents
  const customer = await loopar.getDocument("Customer", this.customer);
  
  // Set field values
  this.total = this.calculateTotal();
}

Client vs Server

Loopar separates client-side and server-side code clearly. Understanding this separation is key to extending the framework.


File Responsibilities

/entity-name/
β”œβ”€β”€ entity-name.json    β†’ Definition (shared)
β”œβ”€β”€ entity-name.js      β†’ Server-side (Node.js)
└── entity-name.jsx     β†’ Client-side (React)
FileRuns OnPurpose
.jsonBothField definitions, metadata
.jsServerBusiness logic, database, API
.jsxClientUI components, interactions

Server-Side (.js)

Runs in Node.js. Has access to:

  • Database operations
  • File system
  • Environment variables
  • External APIs (server-to-server)
  • Session and authentication
  • Business logic hooks
// entity-name.js
import { BaseDocument } from "loopar";

export default class MyEntity extends BaseDocument {
  async beforeSave() {
    // Database access
    const count = await loopar.db.count("Customer");
    
    // File system
    const config = await loopar.readFile("config.json");
    
    // Environment
    const apiKey = process.env.API_KEY;
  }

  // Custom API action
  async actionCustomEndpoint() {
    return { success: true, data: this.getData() };
  }
}

Client-Side (.jsx)

Runs in the browser (React). Has access to:

  • UI rendering
  • User interactions
  • Browser APIs
  • HTTP requests to server
  • Component state
// entity-name.jsx
import { BaseForm, useDocument } from "@loopar/components";
import { useState } from "react";

export default function MyEntity(props) {
  const { document, setValue } = useDocument();
  const [loading, setLoading] = useState(false);

  const handleCustomAction = async () => {
    setLoading(true);
    const response = await fetch(`/api/my-entity/custom-endpoint`);
    setLoading(false);
  };

  return (
    <BaseForm {...props}>
      <button onClick={handleCustomAction}>
        {loading ? "Loading..." : "Custom Action"}
      </button>
    </BaseForm>
  );
}

Communication Between Client & Server

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     CLIENT      β”‚         β”‚     SERVER      β”‚
β”‚    (Browser)    β”‚         β”‚    (Node.js)    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€         β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                 β”‚  HTTP   β”‚                 β”‚
β”‚  React Componentβ”‚ ──────→ β”‚  Controller     β”‚
β”‚  (.jsx)         β”‚ Request β”‚  (.js)          β”‚
β”‚                 β”‚         β”‚                 β”‚
β”‚                 β”‚ ←────── β”‚                 β”‚
β”‚                 β”‚ Responseβ”‚                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

When to Use Each

Use Server (.js) ForUse Client (.jsx) For
Database queriesUI customization
Data validationUser interactions
Business rulesForm behavior
API integrationsAnimations
File operationsConditional rendering
AuthenticationReal-time updates

Multi-Tenant Architecture

Loopar's multi-tenant system allows you to run multiple isolated sites from a single codebase.


What is Multi-Tenancy?

Each tenant is a completely independent site with:

  • Own database β€” Complete data isolation
  • Own domain β€” Custom URL for each client
  • Own config β€” Independent settings
  • Own users β€” Separate user base
  • Shared code β€” Same apps, different data

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      SINGLE CODEBASE                        β”‚
β”‚                       /apps, /packages                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚   TENANT A    β”‚ β”‚   TENANT B    β”‚ β”‚   TENANT C    β”‚     β”‚
β”‚  β”‚   (dev)       β”‚ β”‚  (client-1)   β”‚ β”‚  (client-2)   β”‚     β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€     β”‚
β”‚  β”‚ Database: dev β”‚ β”‚ Database: c1  β”‚ β”‚ Database: c2  β”‚     β”‚
β”‚  β”‚ Port: 3000    β”‚ β”‚ Port: 3001    β”‚ β”‚ Port: 3002    β”‚     β”‚
β”‚  β”‚ Domain: β€”     β”‚ β”‚ app1.com      β”‚ β”‚ app2.com      β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚         β”‚                 β”‚                 β”‚               β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                      β”‚                                      β”‚
β”‚                    PM2                                      β”‚
β”‚            (Process Manager)                                β”‚
β”‚                      β”‚                                      β”‚
β”‚                   CADDY                                     β”‚
β”‚         (Reverse Proxy + SSL)                               β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Site Directory Structure

/sites/
β”œβ”€β”€ dev/                    # Development tenant
β”‚   β”œβ”€β”€ .env                # Environment config
β”‚   β”œβ”€β”€ installed-apps.json # Installed apps list
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   └── uploads/        # Tenant-specific uploads
β”‚   └── sessions/           # User sessions
β”‚
β”œβ”€β”€ client-1/               # Production tenant
β”‚   β”œβ”€β”€ .env
β”‚   β”œβ”€β”€ installed-apps.json
β”‚   └── ...
β”‚
└── client-2/               # Another tenant
    └── ...

Tenant Configuration (.env)

Each tenant has its own environment file:

# sites/client-1/.env
DB_TYPE=mysql
DB_HOST=localhost
DB_NAME=client1_db
DB_USER=client1
DB_PASSWORD=secret

PORT=3001
NODE_ENV=production

DOMAIN=app1.com

Use Cases

ScenarioSetup
SaaS ProductOne tenant per customer
AgencyOne tenant per client project
EnterpriseSeparate tenants for departments
StagingDev, Staging, Production tenants

Key Benefits

BenefitDescription
Data IsolationEach client's data is completely separate
Custom DomainsProfessional URLs for each client
Independent ScalingScale individual tenants as needed
Easy DeploymentDeploy updates to all tenants at once
Cost EfficientSingle server, multiple clients

API & Routes

Loopar automatically generates RESTful API endpoints for every entity. You can also create custom actions.


Auto-Generated Endpoints

For an entity named Customer, Loopar creates:

MethodEndpointAction
GET/api/CustomerList all records
GET/api/Customer/:nameGet single record
POST/api/CustomerCreate new record
PUT/api/Customer/:nameUpdate record
DELETE/api/Customer/:nameDelete record

Query Parameters

The list endpoint supports:

# Pagination
GET /api/Customer?page=1&limit=20

# Search
GET /api/Customer?search=john

# Filters
GET /api/Customer?status=Active&country=USA

# Sorting
GET /api/Customer?order_by=created_at&order=desc

# Field selection
GET /api/Customer?fields=name,email,status

Custom Actions

Add custom API endpoints in your entity's .js file:

export default class Customer extends BaseDocument {
  
  // Creates: POST /api/Customer/send-welcome-email
  async actionSendWelcomeEmail() {
    await this.sendEmail({
      to: this.email,
      subject: "Welcome!",
      template: "welcome"
    });
    
    return {
      success: true,
      message: "Email sent successfully"
    };
  }

  // Creates: GET /api/Customer/stats
  async actionStats() {
    const total = await loopar.db.count("Customer");
    const active = await loopar.db.count("Customer", { status: "Active" });
    
    return {
      total,
      active,
      inactive: total - active
    };
  }
}

Calling Actions from Client

// From React component
const response = await fetch("/api/Customer/send-welcome-email", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "CUST-001" })
});

const data = await response.json();

Page Routes

Pages get their own routes automatically:

Page NameRoute
homepage/homepage
about-us/about-us
contact/contact

Desk Routes

Internal desk routes follow this pattern:

RouteView
/desk/Customer/listList view
/desk/Customer/newNew record form
/desk/Customer/edit/:nameEdit record form
/desk/Customer/view/:nameRead-only view

Database

Loopar uses Sequelize ORM to support multiple database backends. The schema is generated automatically from your entity definitions.


Supported Databases

DatabaseBest For
MySQLProduction, high performance
PostgreSQLAdvanced features, JSON support
MariaDBMySQL alternative
SQLiteDevelopment, small deployments

Automatic Schema Management

When you create or modify an entity:

  1. Loopar reads the field definitions from .json
  2. Compares with current database schema
  3. Generates migration SQL
  4. Applies changes automatically
Entity Definition β†’ Schema Diff β†’ Migration β†’ Database

Field to Column Mapping

Field TypeMySQLPostgreSQL
DataVARCHAR(255)VARCHAR(255)
TextTEXTTEXT
IntINTINTEGER
FloatFLOATREAL
CurrencyDECIMAL(18,6)NUMERIC(18,6)
DateDATEDATE
DateTimeDATETIMETIMESTAMP
CheckTINYINT(1)BOOLEAN
JSONLONGTEXTJSONB

Database API

Access the database in server-side code:

import loopar from "loopar";

// Get a document
const customer = await loopar.getDocument("Customer", "CUST-001");

// Create a document
const newCustomer = await loopar.newDocument("Customer");
newCustomer.name = "John Doe";
newCustomer.email = "john@example.com";
await newCustomer.save();

// Query documents
const customers = await loopar.db.getAll("Customer", {
  filters: { status: "Active" },
  fields: ["name", "email"],
  orderBy: "created_at DESC",
  limit: 10
});

// Count documents
const count = await loopar.db.count("Customer", { status: "Active" });

// Raw SQL (when needed)
const results = await loopar.db.execute(
  "SELECT * FROM Customer WHERE created_at > ?",
  ["2024-01-01"]
);

Relationships

{
  "name": "customer",
  "type": "Link",
  "options": "Customer"
}

Creates a foreign key reference to the Customer entity.

Table Fields (One-to-Many)

{
  "name": "items",
  "type": "Table",
  "options": "Invoice Item"
}

Creates a child table with parent reference.


Database Per Tenant

Each tenant has its own database:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Tenant A   β”‚    β”‚  Tenant B   β”‚    β”‚  Tenant C   β”‚
β”‚             β”‚    β”‚             β”‚    β”‚             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ MySQL β”‚  β”‚    β”‚ β”‚ MySQL β”‚  β”‚    β”‚ β”‚SQLite β”‚  β”‚
β”‚  β”‚ db_a  β”‚  β”‚    β”‚  β”‚ db_b  β”‚  β”‚    β”‚  β”‚ db_c  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each tenant can even use a different database type.

Theming

Loopar uses Tailwind CSS with CSS variables for a flexible theming system. Themes can be changed dynamically without rebuilds.


Theme System

Loopar supports:

  • Light/Dark modes β€” Automatic switching
  • Custom color schemes β€” Define your brand colors
  • Per-tenant themes β€” Each tenant can have its own theme
  • Dynamic generation β€” Create themes programmatically

CSS Variables

Themes are defined using CSS custom properties:

:root {
  --background: 0 0% 100%;
  --foreground: 222 84% 5%;
  --primary: 221 83% 53%;
  --primary-foreground: 210 40% 98%;
  --secondary: 210 40% 96%;
  --muted: 210 40% 96%;
  --accent: 210 40% 96%;
  --destructive: 0 84% 60%;
  --border: 214 32% 91%;
  --radius: 0.5rem;
}

.dark {
  --background: 222 84% 5%;
  --foreground: 210 40% 98%;
  /* ... dark mode colors */
}

Using Theme Colors

In your components, use Tailwind classes:

// Background colors
<div className="bg-background" />
<div className="bg-primary" />
<div className="bg-secondary" />

// Text colors
<p className="text-foreground" />
<p className="text-muted-foreground" />
<p className="text-primary" />

// Border colors
<div className="border border-border" />

Built-in Themes

Loopar includes several pre-built themes:

ThemeDescription
DefaultClean, professional blue
SlateNeutral gray tones
RoseWarm pink accent
OrangeEnergetic orange
GreenFresh green accent
VioletPurple tones

Creating Custom Themes

Define a new theme in your app:

// Custom theme definition
const myTheme = {
  name: "corporate",
  colors: {
    primary: { h: 210, s: 100, l: 40 },     // Corporate blue
    secondary: { h: 210, s: 20, l: 95 },
    accent: { h: 45, s: 100, l: 50 },        // Gold accent
    background: { h: 0, s: 0, l: 100 },
    foreground: { h: 210, s: 50, l: 10 },
  }
};

Dark Mode

Dark mode is automatic based on:

  1. User preference in settings
  2. System preference (prefers-color-scheme)
  3. Manual toggle
// Toggle dark mode
import { useTheme } from "@loopar/components";

function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  
  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      Toggle Theme
    </button>
  );
}

Glossary

Quick reference for Loopar terminology.


TermDefinition
EntityThe core model definition that generates database tables, APIs, and UI
BuilderSpecialized entity type for creating specific model types
AppSelf-contained application package with modules and entities
ModuleOrganizational unit that groups related entities
DocumentInstance of an entity (a record in the database)
SingleEntity type that stores only one record (for settings/config)
TenantIndependent site with own database and configuration
DeskThe admin interface for managing entities and data
ComponentUI element used in drag-and-drop design
FieldData attribute defined in an entity
HookLifecycle callback (beforeSave, afterInsert, etc.)
ControllerServer-side class handling business logic
ActionCustom API endpoint defined in a controller
LinkField type referencing another entity (foreign key)
TableField type for child records (one-to-many)
PM2Process manager running all tenant sites
CaddyReverse proxy for SSL and domain routing

Common Patterns

PatternExample
Entity nameCustomer, Sales Invoice, Stock Entry
Field namecustomer_name, total_amount, is_active
Module namecore, sales, inventory
App namemy-crm, loopar, hr-management
Route/desk/Customer/list, /api/Customer

File Extensions

ExtensionPurpose
.jsonEntity/component definitions
.jsServer-side controller
.jsxClient-side React component
.envEnvironment configuration
.mjsES Module JavaScript