Skip to main content
SFDC Developers
Apex

Calling Apex Methods to Create Records in Loops from LWC

Vinay Vernekar · · 4 min read

Introduction

When building sophisticated Lightning Web Components (LWC), developers often face the challenge of creating multiple records based on user input. A common pitfall for those transitioning from traditional web development to the Salesforce ecosystem is the urge to call an Apex method inside a JavaScript loop. In Salesforce, this is an anti-pattern that can quickly exhaust your governor limits and degrade application performance. In this guide, we'll examine why this happens, how to restructure your code for bulk processing, and the best practices for calling Apex methods to create records effectively.

The Anti-Pattern: Calling Apex in a Loop

In standard web development, making an API call for every item in a list might be acceptable. However, in Salesforce, every @AuraEnabled method call initiates a separate transaction. If you iterate through an array of objects in your JavaScript controller and call an Apex method for each, you are essentially initiating as many database transactions as there are items.

Why this fails:

  • DML Limits: Salesforce imposes strict limits on the number of DML statements per transaction (currently 150). If you have 200 items in your loop, your code will throw a LimitException.
  • Network Latency: Each call incurs a round-trip time between the browser and the Salesforce server, resulting in a sluggish user experience.
  • Atomicity: If the 50th record fails, you may end up with 49 records created and the rest lost, making error handling and data integrity nearly impossible to manage.

The Solution: Bulkifying via Collections

Instead of calling Apex repeatedly, the golden rule of Salesforce development is: Pass the collection to Apex once. By sending an entire array of objects to a single Apex method, you can leverage bulkified DML operations. This keeps your transaction count low and ensures your code remains within governor limits.

Step 1: Define the Data Structure

Ensure your LWC sends a structured object or a list of objects that Apex can easily deserialize using @AuraEnabled.

Step 2: Implement the Apex Handler

Create a method that accepts a List of the sObject you intend to create.

public with sharing class RecordHandler {
    @AuraEnabled
    public static void createRecords(List<Account> accounts) {
        if (accounts != null && !accounts.isEmpty()) {
            try {
                insert accounts;
            } catch (DmlException e) {
                throw new AuraHandledException('Error creating records: ' + e.getMessage());
            }
        }
    }
}

Step 3: Call from LWC

In your JavaScript file, prepare the full list before calling the method.

import { LightningElement } from 'lwc';
import createRecords from '@salesforce/apex/RecordHandler.createRecords';

handleSave() {
    const records = [
        { sobjectType: 'Account', Name: 'Test 1' },
        { sobjectType: 'Account', Name: 'Test 2' }
    ];

    createRecords({ accounts: records })
        .then(() => {
            // Handle success
        })
        .catch(error => {
            // Handle error
        });
}

Handling Complex Data Relationships

Often, you need to create parent-child relationships (e.g., an Account with multiple Contacts). You cannot rely on simple sObject lists here. Instead, create a wrapper class in Apex to handle the complex structure.

The Wrapper Approach

public class AccountContactWrapper {
    @AuraEnabled public String accountName;
    @AuraEnabled public List<String> contactNames;
}

// In your Apex method:
@AuraEnabled
public static void processBulkData(List<AccountContactWrapper> wrappers) {
    List<Account> accs = new List<Account>();
    // Parse and insert...
}

This allows you to encapsulate business logic on the server side, keeping your LWC clean and ensuring that your data transformation happens in a secure, server-side environment.

Best Practices for Robust Implementations

To ensure your implementation is production-ready, keep these best practices in mind:

  • Client-Side Validation: Always validate data in your LWC before sending it to Apex. This prevents unnecessary round-trips for bad data.
  • Use AuraHandledException: When errors occur in Apex, always throw an AuraHandledException to return a meaningful error message to your JavaScript controller.
  • Idempotency: Consider adding checks to see if a record already exists before attempting to insert, preventing duplicate data.
  • Atomic Transactions: By using a single list, all records in that list are processed in one transaction. If one fails, you can roll back the entire batch, maintaining high data integrity.

Key Takeaways

  • Avoid the loop: Never invoke @AuraEnabled methods inside a JavaScript loop.
  • Bulkify: Always aggregate data into a single list or wrapper and pass it to a single Apex method call.
  • Leverage DML: Single insert or update statements on a list are significantly more efficient than individual calls.
  • Use Wrappers: For complex, nested data structures, use Apex wrapper classes to receive and process data from the client.
  • Data Integrity: Keeping operations in a single method ensures that your record creation follows "all-or-nothing" ACID principles for the batch.

Share this article

Get weekly Salesforce dev tutorials in your inbox

Comments

Loading comments...

Leave a Comment

Trending Now