SapotaCorp

LWC Basics for the Apex-Heavy Developer: Leaving Aura Behind

An Apex-heavy Salesforce developer faces a Lightning Component requirement. Aura is familiar but officially deprecated. LWC is the future but feels foreign. A practical introduction focused on the parts an Apex dev needs to know.

LWC Basics for the Apex-Heavy Developer: Leaving Aura Behind

Key takeaways

  • Aura is deprecated; LWC is the future. Apex-heavy developers who avoided LWC by sticking with Aura need to learn the new component framework. The migration path is not trivial but not as foreign as it first appears.
  • LWC calls Apex via 2 patterns: @wire (reactive, declarative, automatic re-render on data change) and imperative (manual call, manual response handling, useful for actions like button clicks). Wire for data display; imperative for user-triggered actions.
  • Decorators replace Aura's component metadata. @api makes a property public (parent can set it), @track makes a property reactive (UI re-renders on change, no longer needed in modern LWC), @wire connects to Apex or Lightning Data Service. Once the decorators click, the rest follows.
  • Migration from Aura to LWC is not 1-to-1. Some Aura patterns (component-to-component events) translate cleanly; others (component dynamic instantiation) need rethinking. Plan the migration as a rewrite per component rather than a mechanical translation; the resulting LWC is cleaner than the Aura would have been.

A Salesforce developer who has spent 5 years writing Apex now faces a Lightning Component requirement. The familiar option is Aura: tag-based markup, similar to JSP, comfortable. The official option is LWC: a modern JavaScript framework that looks foreign at first glance. Aura is in Maintain mode, meaning Salesforce will keep it running but not improve it. LWC is where new investment goes.

Most Apex developers approach LWC with a deeper learning curve than necessary. The framework is more straightforward than its first impression suggests. The parts an Apex dev actually needs to know are smaller than the full documentation implies.

What LWC is, briefly

Lightning Web Components are Salesforce's implementation of standard Web Components (HTML, JavaScript, CSS). Each component is three files:

  • A .html template.
  • A .js JavaScript class.
  • An optional .css stylesheet.
  • An optional .js-meta.xml config file.

The framework is built on web standards (custom elements, Shadow DOM, JavaScript modules). The Salesforce-specific layer is mostly: how to call Apex from the JavaScript, how to use Lightning Data Service, and the Salesforce-specific decorators.

The whole framework can be learned in a week. The first useful component can be built in a day.

A minimal LWC

A component that shows a list of Accounts:

<!-- accountList.html -->
<template>
  <template if:true={accounts.data}>
    <ul>
      <template for:each={accounts.data} for:item="acc">
        <li key={acc.Id}>{acc.Name}</li>
      </template>
    </ul>
  </template>
  <template if:true={accounts.error}>
    <p>Error: {accounts.error.body.message}</p>
  </template>
</template>
// accountList.js
import { LightningElement, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
  @wire(getAccounts)
  accounts;
}
// AccountController.cls
public with sharing class AccountController {
  @AuraEnabled(cacheable=true)
  public static List<Account> getAccounts() {
    return [SELECT Id, Name FROM Account
            WITH USER_MODE LIMIT 10];
  }
}

Three files, working component. Apex method has @AuraEnabled (yes, called Aura for legacy reasons; LWC uses the same annotation). The cacheable=true flag tells LWC the result can be cached and refreshed.

Wire vs imperative Apex calls

LWC offers two patterns for calling Apex:

Wire pattern. Declarative. The Apex method runs when the component loads and reruns automatically when relevant parameters change. The result is bound to a property.

@wire(getAccounts, { searchKey: '$searchKey' })
accounts;

When searchKey changes, the wire fires again with the new value. The component re-renders automatically. Used for read operations where the result should reflect current state.

Imperative pattern. The Apex method is called from JavaScript explicitly. The component decides when to call.

import { LightningElement } from 'lwc';
import saveAccount from '@salesforce/apex/AccountController.saveAccount';

export default class AccountEditor extends LightningElement {
  handleSave() {
    saveAccount({ accountData: this.accountData })
      .then(result => {
        // Handle success.
      })
      .catch(error => {
        // Handle failure.
      });
  }
}

Used for write operations and any logic that should run in response to user action.

The wire pattern handles 70% of cases. Use imperative for anything that triggers from user interaction or that needs explicit control over when it fires.

The decorators

LWC uses a few JavaScript decorators that have specific meanings:

@api: marks a property as a public API. Other components can pass values into this property. Without @api, the property is private.

@api recordId;

@track: marks a property as reactive. Changes to nested object properties (e.g., this.config.value = 'x') trigger re-renders. Salesforce auto-tracks primitives, so @track is mostly used for objects and arrays.

@track config = { value: '', count: 0 };

@wire: subscribes the property (or method) to a wire adapter. Auto-refreshes when dependencies change.

@wire(getAccountById, { id: '$recordId' })
account;

Most components use one or more of these. @api for inputs from parent or page context, @wire for data fetching, @track for complex internal state.

Lightning Data Service

For standard CRUD on records, LWC offers Lightning Data Service (LDS): a built-in cache and edit framework that bypasses Apex entirely.

import { getRecord } from 'lightning/uiRecordApi';

@wire(getRecord, { recordId: '$recordId',
                   fields: ['Account.Name', 'Account.Industry'] })
account;

LDS handles caching, sharing, FLS, and concurrent updates automatically. For simple read-or-write of a single record, LDS is preferable to a custom Apex method.

When to skip LDS and write Apex:

  • Complex queries with custom logic.
  • Aggregate operations across multiple records.
  • Callouts or business rules that go beyond simple field updates.
  • Cases where LDS performance is insufficient.

LDS is the right default; Apex is the escape hatch.

Events between components

Parent components pass data to children via @api properties. Children communicate back to parents via custom events:

// In the child component:
this.dispatchEvent(new CustomEvent('selected', {
  detail: { accountId: this.accountId }
}));
<!-- In the parent template: -->
<c-account-list onselected={handleSelection}></c-account-list>

For components that are not in a parent-child relationship (siblings, distant cousins), use Lightning Message Service (LMS). LMS is a pub-sub framework that lets any component publish or subscribe to a message channel.

What's different from Aura

Five mental shifts for an Aura-fluent developer:

1. JavaScript modules, not Aura component bundles. Each LWC is a self-contained module. The .cmp file with attribute tags is gone; everything is JS classes and HTML templates.

2. No aura: namespace. Aura's aura:attribute, aura:handler, etc. become @api, event listeners on the class, and standard template syntax.

3. Standards-based syntax. LWC uses for:each (not aura:iteration), if:true (not aura:if), standard event syntax. Looks like vanilla web components plus a few Salesforce conventions.

4. Stricter scoping. LWC enforces Shadow DOM by default, meaning CSS does not leak between components and DOM queries are scoped to the component. Aura was more permissive; LWC is more disciplined.

5. Lifecycle hooks. LWC uses connectedCallback, renderedCallback, and disconnectedCallback (web standard). Aura's init, render, unrender are different but conceptually similar.

For most Aura components, the LWC equivalent is shorter and clearer. The translation is mostly mechanical.

Migration path from Aura

For an org with many Aura components, the recommended migration:

  1. New components are LWC by default. No new Aura.
  2. Identify high-traffic Aura components. Page Builder analytics or manual triage. These are the migration priority.
  3. Migrate the high-traffic components in chunks. Each migration is its own PR with regression tests.
  4. Leave low-traffic Aura components in place. Salesforce keeps Aura working; refactoring everything at once is rarely the right investment.
  5. Embed LWCs inside Aura where helpful. Aura can host LWCs, easing incremental migration of larger UIs.

A typical migration completes the high-traffic components in 3-6 months. The long tail of low-traffic Aura components may stay for years.

Common LWC mistakes for Apex devs

Five patterns Sapota has seen:

1. Building a custom component when LDS would suffice. A team writes a custom Apex method to read a single Account, when getRecord from lightning/uiRecordApi handles it natively with caching and FLS.

2. Using imperative calls for read operations. Wire is the right pattern for reads. Imperative makes the component responsible for refreshing when inputs change, which is what wire handles automatically.

3. Apex method without cacheable=true. LWC's auto-refresh and cache features need this flag on read methods. Without it, the wire pattern still works but caching is disabled.

4. Mutating wired data directly. Wired properties are read-only. Modifying this.accounts.data directly throws. Make a local copy first if mutation is needed.

5. Forgetting @api on parent-supplied properties. A component meant to receive recordId from the page context needs @api recordId;. Without the decorator, the framework does not pass the value in.

What good LWC adoption looks like

A Salesforce org with healthy LWC adoption:

  • All new component work uses LWC.
  • LDS used for simple CRUD; Apex reserved for complex logic.
  • Wire pattern preferred for reads, imperative for writes.
  • Aura components migrated by priority (high-traffic first).
  • LWC tests with @salesforce/sfdx-lwc-jest run in CI.

Sapota's Salesforce team holds the Platform Developer I credential and runs Aura-to-LWC migrations as a defined engagement type. The transition is more mechanical than developers fear; the components emerging on the other side are easier to maintain and more performant.


Migrating Aura to LWC or starting a new Lightning component build? Sapota's Salesforce team handles LWC architecture, migration planning, and front-end testing on production engagements. Get in touch ->

See our full platform services for the stack we cover.

Contact Us Now

Share Your Story

We build trust by delivering what we promise – the first time and every time!

We'd love to hear your vision. Our IT experts will reach out to you during business hours to discuss making it happen.

WHY CHOOSE US

"Collaborate, Elevate, Celebrate where Associates - Create Project Excellence"

SapotaCorp beyond the IT industry standard, we are

  • Certificated
  • Assured quality
  • Extra maintenance

Tell us about your project