Retrieve Data Using Apex
The Node.js app you built in the previous project runs a SOQL query to retrieve the list of sessions from the Salesforce Platform. In order to retrieve that data on Salesforce, you need to convert the Node.js backend logic into Apex code. Apex is a strongly typed, object-oriented programming language that allows developers to execute flow and transaction control statements on Salesforce servers in conjunction with calls to the API.
In this step, you write an Apex controller that executes the SOQL query and exposes it as an endpoint that can be called by your components.
Create an Apex Controller
- In VS Code, right-click the
classes
folder and click SFDX: Create Apex Class. - Enter the name
SessionController
. - If VS Code asks, select
force-app/main/default/classes
as the directory you wish to addSessionController.cls
to. - In the newly opened file, replace the default code with the following code.
- Save
SessionController.cls
.
public with sharing class SessionController { @AuraEnabled(cacheable=true) public static List<Session__c> getSessions(String searchKey) { if (String.isBlank(searchKey)) { return [SELECT Id, Name, toLabel(Room__c), Description__c, format(Date_and_Time__c) dateTime, (SELECT Speaker__r.Id, Speaker__r.Name, Speaker__r.Description, Speaker__r.Email, Speaker__r.Picture_URL__c FROM Session_Speakers__r) FROM Session__c ORDER BY Date_and_Time__c LIMIT 100]; } else { String key = '%' + searchKey + '%'; return [SELECT Id, Name, toLabel(Room__c), Description__c, format(Date_and_Time__c) dateTime, (SELECT Speaker__r.Id, Speaker__r.Name, Speaker__r.Description, Speaker__r.Email, Speaker__r.Picture_URL__c FROM Session_Speakers__r) FROM Session__c WHERE Name LIKE :key ORDER BY Date_and_Time__c LIMIT 100]; } } @AuraEnabled(cacheable=true) public static Session__c getSession(Id sessionId) { List<Session__c> sessions = [SELECT Id, Name, toLabel(Room__c), Description__c, format(Date_and_Time__c) dateTime, (SELECT Speaker__r.Id, Speaker__r.Name, Speaker__r.Description, Speaker__r.Email, Speaker__r.Picture_URL__c FROM Session_Speakers__r) FROM Session__c WHERE Id = :sessionId]; if (sessions.size() != 1) { throw new AuraHandledException('Could not find session'); } return sessions[0]; } }
The @AuraEnabled(cacheable=true)
declaration exposes methods as endpoints so your components can call them. It also enables client-side caching of data returned by the methods. The code you write here is shorter than its Node.js equivalent as you don’t have to worry about authentication or running the query asynchronously.
Retrieve Data with Wires
Now that we have endpoints, let’s replace the mock data implementations and retrieve session records with real data using the Wire Service. This service is a special implementation on the Salesforce Platform that provides optimized read operations and client-side caching using a declarative approach. Let’s see how to build that.
- Open
sessionList.js
. - Replace the import code on the first two lines with the following code.
- Add the following code after
sessions = [];
- Remove the
connectedCallback
function. - Replace the
handleSearchKeyInput
function with the following code. - Replace the
handleSessionClick
function with: - Save
sessionList.js
. - Open
sessionList.html
. - Replace the file content with the following code.
- Save
sessionList.html.
- Open
sessionDetails.js.
- Replace the file content with:
- Save
sessionDetails.js
. - Open
sessionDetails.html
. - Replace the file content with:
- Save
sessionDetails.html
. - Open
speakerCard.html
. - Replace the file content with the following code.
- Save
speakerCard.html
. - Redeploy your components by right-clicking the
force-app
folder and clicking SFDX: Deploy Source to Org.
import { LightningElement, wire } from 'lwc'; import getSessions from '@salesforce/apex/SessionController.getSessions';
This imports a wire adapter that is automatically generated from your Apex method.
searchKey = ''; @wire(getSessions, { searchKey: '$searchKey' }) wiredSessions({ error, data }) { if (data) { this.sessions = data; } else if (error) { this.sessions = []; throw new Error('Failed to retrieve sessions'); } }
The wire uses the getSessions
adapter that maps to our Apex endpoint. The wire is called with a searchKey
parameter that is mapped to the searchKey
property. Notice that the parameter value is passed as a string with a $
prefix. This means that this parameter is reactive: The wire is called each time searchKey
changes.
handleSearchKeyInput(event) { clearTimeout(this.delayTimeout); const searchKey = event.target.value; // eslint-disable-next-line @lwc/lwc/no-async-operation this.delayTimeout = setTimeout(() => { this.searchKey = searchKey; }, 300); }
The filtering is no longer performed on the client side. searchKey
is now passed to the wire so that the Apex returns the filtered session list. You are debouncing the searchKey
property update by applying a 300-millisecond delay to the update. This is to avoid a very large number of Apex method calls.
handleSessionClick(event) { const { sessionId } = event.currentTarget.dataset; const navigateEvent = new CustomEvent('navigate', { detail: { state: 'details', sessionId: sessionId } }); this.dispatchEvent(navigateEvent); }
You now use the session record Id instead of the session list index to navigate to a session detail page.
<template> <div class="list"> <input type="search" placeholder="Search sessions..." oninput={handleSearchKeyInput} /> <template for:each={sessions} for:item="session"> <a key={session.Id} class="session" data-session-id={session.Id} onclick={handleSessionClick}> <div> <p class="title">{session.Name}</p> <p class="icon time">{session.dateTime}</p> <p class="icon room">{session.Room__c}</p> </div> <template if:true={session.Session_Speakers__r}> <div class="speaker-pictures"> <template for:each={session.Session_Speakers__r} for:item="speaker"> <img key={speaker.Speaker__r.Id} src={speaker.Speaker__r.Picture_URL__c} alt={speaker.Speaker__r.Name} title={speaker.Speaker__r.Name}> </template> </div> </template> </a> </template> </div> </template>
The template structure is mostly unchanged, but you now use the Salesforce record field names. This spares you the effort of mapping field names with JavaScript like you did earlier in the Node.js backend. In a real project, you would directly use the Salesforce field names in the component’s template.
import { LightningElement, api, wire } from 'lwc'; import getSession from '@salesforce/apex/SessionController.getSession'; export default class SessionDetails extends LightningElement { @api sessionId; session; @wire(getSession, { sessionId: '$sessionId' }) wiredSession({ error, data }) { if (data) { this.session = data; } else if (error) { this.session = undefined; throw new Error('Failed to retrieve session'); } } handleSessionsClick() { const navigateEvent = new CustomEvent('navigate', { detail: { state: 'list' } }); this.dispatchEvent(navigateEvent); } }
Just like for sessionList
, you wire the getSession
Apex method to the session
property. You pass the sessionId
public property as a reactive parameter to getSession
.
<template> <template if:true={session}> <h2><a href="#" onclick={handleSessionsClick}>Sessions</a> > {session.Name}</h2> <p class="icon time">{session.dateTime}</p> <p class="icon room">{session.Room__c}</p> <h3>Abstract</h3> <div class="abstract">{session.Description__c}</div> <h3>Speakers</h3> <div class="speaker-list"> <template if:true={session.Session_Speakers__r}> <template for:each={session.Session_Speakers__r} for:item="speaker"> <c-speaker-card key={speaker.Speaker__r.id} speaker={speaker.Speaker__r}> </c-speaker-card> </template> </template> </div> </template> </template>
<template> <div class="card"> <div class="header"> <img src={speaker.Picture_URL__c} alt="Speaker picture"> <div> <p class="title">{speaker.Name}</p> <p class="icon email">{speaker.Email}</p> </div> </div> {speaker.Description} </div> </template>
Project Summary
Congratulations! You successfully converted a Lightning Web Component Open Source app and deployed it on the Salesforce Platform.
You’ve just scratched the surface on the powerful declarative features that the Salesforce Platform provides, reducing the amount of code that you have to implement in order to build an app. Check out the many other Lightning Web Component projects and modules to learn more about the technology that lets you build modern apps faster.