[readonly] markdown buffer
LDS vs GraphQL vs Apex: How I Choose an LWC Data Layer
LWC offers enough ways to load data that choosing one can become more work than writing the first version.
I do not start with “GraphQL or Apex?” I start with the operation. Is this a standard record form, a reactive read, a shaped query, or a business transaction?
The answer should be boring. Data access is easier to maintain when the mechanism tells the truth about the work.
Stay at the platform level while it fits
For a standard create, view, or edit experience, start with lightning-record-form, lightning-record-edit-form, or lightning-record-view-form.
They already understand metadata, labels, validation rules, permissions, and LDS caching. Slightly different spacing is rarely a good reason to own all of that behaviour.
For a custom component that still revolves around one record, use LDS adapters and functions.
import { LightningElement, api, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import NAME_FIELD from '@salesforce/schema/Account.Name';
export default class AccountHeading extends LightningElement {
@api recordId;
@wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD] })
account;
get name() {
return getFieldValue(this.account.data, NAME_FIELD);
}
}
This is a reactive, permission-aware record stream without an Apex controller that only runs one SOQL query.
Salesforce's LWC data guidelines follow the same progression: base components, LDS adapters and functions, GraphQL, then Apex when UI APIs no longer fit.
Use GraphQL when the screen owns a shape
GraphQL becomes useful when a workspace needs related, filtered, ordered, or paginated data across objects.
Several independent adapters create several loading states and moments where the screen is only half current. GraphQL can request the complete shape through one endpoint while retaining LDS integration for supported data.
That makes it a strong fit for read-heavy workspaces and search surfaces. A one-field badge does not need GraphQL merely because GraphQL exists.
Use Apex when “save” is a transaction
Suppose one action must create an order, reserve stock, write an audit record, and roll everything back if any step fails.
That is a server-side business transaction.
public with sharing class OrderController {
@AuraEnabled
public static OrderResult placeOrder(OrderRequest request) {
return OrderResult.fromServiceResult(
OrderService.place(request)
);
}
}
The controller stays thin. The service owns validation, permissions, DML, rollback, and the response. Apex also fits specialist platform APIs, integrations, substantial calculations, partial-success DML, and reviewed privilege boundaries.
Leave cache and security ownership clear
LDS-backed data is managed. Apex data is not.
After an Apex mutation, decide whether an Apex wire needs refreshApex(), whether LDS-held records became stale, and whether notifyRecordUpdateAvailable() is required. Avoid loading the same conceptual data through Apex and GraphQL without a deliberate boundary.
LDS and GraphQL enforce platform access for supported operations. Apex provides more control and therefore more responsibility: explicit sharing, database access mode, and untrusted-input handling.
The imports should explain the architecture. lightning/uiRecordApi says record-shaped interaction. lightning/graphql says the screen owns a query shape. @salesforce/apex says the operation crosses into server-side business logic.
The best choice is the one that makes the transaction, cache, security, and failure model easiest to explain six months later.