Start tracking your progress
Trailhead Home
Trailhead Home

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.

Note

Note

Apex is only needed for specific use cases, such as fetching a record and its related records. When you interact with single record data, you can use the power of the User Interface API. This lets you use prebuilt wire adapters and functions to perform CRUD operations on records just with JavaScript, and without writing a single line of Apex.

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

  1. In VS Code, right-click the classes folder and click SFDX: Create Apex Class.
  2. Enter the name SessionController.
  3. If VS Code asks, select force-app/main/default/classes as the directory you wish to add SessionController.cls to.
  4. In the newly opened file, replace the default code with the following code.
  5. 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.

  6. Save SessionController.cls.

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.

  1. Open sessionList.js.
  2. Replace the import code on the first two lines with the following code.
  3. 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. 

  4. Add the following code after sessions = [];
  5. 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.

  6. Remove the connectedCallback function.
  7. Replace the handleSearchKeyInput function with the following code.
  8. 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.

  9. Replace the handleSessionClick function with:
  10. 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.

  11. Save sessionList.js.
  12. Open sessionList.html.
  13. Replace the file content with the following code.
  14. <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.

  15. Save sessionList.html.
  16. Open sessionDetails.js.
  17. Replace the file content with:
  18. 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.

  19. Save sessionDetails.js.
  20. Open sessionDetails.html.
  21. Replace the file content with:
  22. <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>
  23. Save sessionDetails.html.
  24. Open speakerCard.html.
  25. Replace the file content with the following code.
  26. <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>
  27. Save speakerCard.html.
  28. Redeploy your components by right-clicking the force-app folder and clicking SFDX: Deploy Source to Org.

Completed app with session list showing Platform data.

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.