Skip to main content
Modules are the building blocks of Loopar applications. They organize your application’s functionality into logical units containing controllers, forms, pages, and business logic.

Module Structure

Loopar follows a convention-based directory structure for organizing modules:
packages/loopar/apps/
└── your-app/
    └── modules/
        └── your-module/
            ├── controllers/
            │   └── your-controller/
            │       ├── your-controller.js
            │       └── your-controller-controller.js
            ├── forms/
            │   └── your-form/
            │       ├── your-form.js
            │       └── client/
            │           └── your-form-form.jsx
            └── pages/
                └── your-page/
                    ├── your-page.js
                    └── your-page-controller.js
Each module component follows kebab-case naming convention. For example, “Customer Order” becomes “customer-order”.

Creating Your First Module

1
Step 1: Create Module Directory
2
Create a new directory under packages/loopar/apps/your-app/modules/:
3
mkdir -p packages/loopar/apps/your-app/modules/customer
4
Step 2: Create a Document Class
5
Document classes extend BaseDocument and contain your business logic:
6
import { loopar, fileManage, BaseDocument } from "loopar";

export default class Customer extends BaseDocument {
  async validateEmail() {
    const email = this.email;
    
    if (!email || !email.includes('@')) {
      loopar.throw({
        message: "Please provide a valid email address"
      });
    }
  }

  async beforeSave() {
    await this.validateEmail();
  }

  async afterSave() {
    console.log(`Customer ${this.name} saved successfully`);
  }
}
7
Step 3: Create a Controller
8
Controllers handle HTTP requests and routing. Extend the appropriate base controller:
9
import { BaseController, loopar } from "loopar";

export default class CustomerController extends BaseController {
  async actionSendWelcome() {
    if (this.hasData()) {
      const customer = await loopar.getDocument('Customer', this.name);
      
      // Send welcome email logic here
      
      return this.success(`Welcome email sent to ${customer.email}`);
    }
  }
}
10
Step 4: Create the Installer
11
The installer defines how your module is deployed. Create an installer.js file in your app root:
12
import path from "pathe";
import { loopar, fileManage } from "loopar";
import BaseDocument from "../../loopar/core/document/base-document.js";

export default class Installer extends BaseDocument {
  async install(reinstall = false) {
    loopar.installingApp = this.app_name;
    console.log("Installing App", this.app_name);

    await this.installData(reinstall);

    loopar.installingApp = null;
    await loopar.setApp({[this.app_name]: true});
    
    await loopar.build();
    console.log(`App ${this.app_name} installed successfully!`);
    return `App ${this.app_name} installed successfully!`;
  }

  async installData(reinstall = false) {
    const moduleRoute = loopar.makePath('apps', this.app_name);
    const appData = await fileManage.getConfigFile('installer', moduleRoute);

    // Installation logic
    for (const e of Object.keys(appData.documents)) {
      const ent = appData.documents[e];
      const [constructor, name] = e.split(':');
      
      if (ent.root) {
        const entityData = await fileManage.getConfigFile(name, ent.root);
        const doc = await loopar.newDocument(constructor, entityData);
        await doc.save({ validate: false });
      }
    }

    return `App ${this.app_name} installed successfully!`;
  }
}

Document Lifecycle Hooks

Loopar provides several lifecycle hooks you can override in your document classes:
export default class MyDocument extends BaseDocument {
  // Called before validation
  async beforeValidate() {
    // Custom validation logic
  }

  // Called before saving to database
  async beforeSave() {
    // Pre-save operations
  }

  // Called after saving to database
  async afterSave() {
    // Post-save operations
  }

  // Called before deletion
  async beforeDelete() {
    // Pre-delete checks
  }

  // Called after deletion
  async afterDelete() {
    // Cleanup operations
  }
}

Working with Values

Access and manipulate document data using the values() method:
export default class Connector extends BaseDocument {
  async connect() {
    const values = await this.values();
    const dbConfig = loopar.getDbConfig();
    const originalConfig = dbConfig.connection || {};

    Object.assign(dbConfig, {
      dialect: values.database,
      connection: Object.assign(originalConfig, values)
    });

    await loopar.setDbConfig(dbConfig);
    await loopar.db.initialize();

    if (await loopar.db.testServer()) {
      await loopar.initialize();
      return true;
    } else {
      loopar.throw({
        message: `Could not connect to the database server`
      });
    }
  }
}

File Naming Conventions

Loopar uses utility functions to convert names between formats:
import { fileManage } from "loopar";

// Convert to kebab-case for file names
fileManage.fileName("CustomerOrder"); // "customer-order.js"
fileManage.folderName("CustomerOrder"); // "customer-order"

// Convert to class name
fileManage.className("Customer Order"); // "CustomerOrder"
Always follow the kebab-case naming convention for files and directories. The framework automatically handles conversions between formats.

Importing and Using Loopar Utilities

Loopar provides a comprehensive set of utilities:
import { loopar, fileManage, BaseDocument } from "loopar";

// File operations
await fileManage.makeFolder('apps', 'my-app', 'modules');
await fileManage.existFile('path/to/file.js');

// Document operations
const doc = await loopar.newDocument('Customer', { name: 'John Doe' });
const existing = await loopar.getDocument('Customer', 'CUST-001');
const list = await loopar.getList('Customer', { data: { status: 'Active' } });

// Database operations
const value = await loopar.db.getValue('Customer', 'email', { name: 'CUST-001' });
const count = await loopar.db.count('Customer', { status: 'Active' });

Next Steps

Now that you understand module structure, learn about:

Build docs developers (and LLMs) love