CRUD operation with Apex & Lightning Web Component – LWC
This article walks you through the steps of performing CRUD operation with Apex & Lightning Web Component on Salesforce Data. You are going to learn explore code sample the below usecases:
- Read Data with Apex and Lightning Web Component.
- Create Data with Apex and Lightning Web Component.
- Update Data with Apex & LWC
- Delete Data with Apex and Lightning Web Component
Before learning how to perform CRUD with Apex and Lightning Web Component, we encourage you to read our earlier posts on
- Using LWC base components to work with Salesforce Data.
- Using JS Adapters & Methods to speak with Salesforce Data.
We should always try to use standard base components for most of our requirements, if standard components are not suitable for your use case use the JS Adapters/ Methods. JS Adapter/ Methods provide more customization over base components. At last, if we need better control on the client side, or need more control of the data communication between the client and service side we should go for Apex.
This tutorial will walk you through the steps to perform CRUD operations on Salesforce Data with LWC & APEX.
Read Data with Apex and Lightning Web Component
Reading data with Apex and Lightning Web Component is one of the easiest ways to get started with understanding the data communication between LWC and Apex. All you have to do is create an Apex class and annotate a method static method with @AuraEnabled(cacheable = true). The annotation makes the method visible in the LWC Component.

Note: Using cacheable=true in the annotation improves the overall performance of your LWC component, the component renders data from the cache instead of waiting for a server trip. If the cache data is obsolete, the framework retrieves the latest data from the server and updates the cache. Read More
Let’s go through the code snippet to see how it works.

HTML Code
<template>
<table>
<tr>
<th>Contact Name</th>
<th>Title</th>
<th>Email</th>
<th>Action</th>
</tr>
<template for:each={contactRecords} for:item="contact">
<tr key={contact.Id}>
<td>{contact.Name}</td>
<td>{contact.Title}</td>
<td>{contact.Email}</td>
<td>
<lightning-icon size="x-small" variant="error" data-contactid={contact.Id} onclick={deleteContactRec} icon-name="utility:delete" alternative-text="Delete" title="Delete">
</lightning-icon>
</td>
</tr>
</template>
</table>
</template>
JS Code
import { LightningElement,wire,track,api} from 'lwc';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
import getRelatedContactRec from '@salesforce/apex/ContactHandler.fetchContactAccountRecords';
export default class Crudexample extends LightningElement {
contactRecords;
@api recordId;
@api objectApiName;
@wire(getRelatedContactRec,{ accountId: '$recordId'})
contactRec({ error, data }) {
if (data) {
this.contactRecords = data;
} else if (error){
console.log('error : ',error);
}
}//contactRec Ends
}
Apex Code
public with sharing class ContactHandler {
@AuraEnabled(cacheable=true)
public static List<Contact> fetchContactAccountRecords(String accountId){
try {
if(!String.isBlank(accountId)){
List<Contact> contactRecords = [select id,Name,Title,Email,Phone from Contact where AccountId=:accountId limit 10];
return contactRecords;
}
return null;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
}
Explanation
In the example above we are querying related Contact records and displaying them in a tabular format. To achieve this first of all we need to do is, create an Apex class with a static method annotated with @AuraEnabled(cacheable=true), the method accepts accountId as a parameter, queries the related contact records and returns the list.
Next we import the static method from the Apex class in LWC using import getRelatedContactRec from ‘@salesforce/apex/<ClassName>.<MethodName>’. After successfully importing the method, we call the method using the @wire decorator, this calls the apex method on page load. After calling the apex method we assign the returned result in this.contactRecords variable.
We now iterate over the contactRecords variable in HTML and construct a table, by using right value for each column of the table.
Create Data with Apex and Lightning Web Component
Creating data using Apex and Lightning Web Component is similar to reading data, the only difference here is we need to pass the field values to the Apex method. For better coding standards make sure to create a JSON object with fields/values and pass that as a String (json.stringify) to the Apex method.
Follow the below code for better clarity.

HTML Code
<template>
<h1 style="text-align:center;font-weight:bold">CRUD Example</h1>
<lightning-card title="Create Contact Record">
<lightning-input name="firstName" type="text" label="First Name" onchange={handleChange}></lightning-input>
<lightning-input name="lastName" type="text" label="Last Name" onchange={handleChange}></lightning-input>
<lightning-input name="email" type="email" label="Email" onchange={handleChange}></lightning-input>
<lightning-button variant="brand" label="Submit" onclick={submitForm}></lightning-button>
</lightning-card>
</template>
JS Code
import { LightningElement,wire,track,api} from 'lwc';
import createContactRecords from '@salesforce/apex/ContactHandler.createContactRecords';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
export default class Crudexample extends LightningElement {
contactRecords;
inputForm={};
@api recordId;
@api objectApiName;
/*The below syntax dynamically created the JSON Object.
Sample JSON that gets created
inputForm = {
"firstName" : "elon",
"lastName" : "grey",
"email" : "sampleemail.gmail.com"
}
*/
handleChange(event){
this.inputForm[event.target.name] = event.target.value;
}
//Method fired when clicked on the Submit button.
submitForm(event){
//Populating another JSON. We could have used inputForm directly here.
let contactSubmitForm ={
firstName:this.inputForm.firstName,
lastName:this.inputForm.lastName,
email:this.inputForm.email,
accountId:this.recordId
}
//Converting the whole JSON into string and passing as a parameter
let contactString = JSON.stringify(contactSubmitForm);
createContactRecords({contactJSON:contactString})
.then((contact) => {
this.contact = contact.id;
this.dispatchEvent(
new ShowToastEvent({
title: "Success",
message: "Contact created successfully!",
variant: "success"
})
);
})
.catch((error) => {
console.log('error : ',error);
this.dispatchEvent(
new ShowToastEvent({
title: "Error",
message: error.body.message,
variant: "error"
})
);
});
}//submitForm Ends
Apex Code
public with sharing class ContactHandler {
@AuraEnabled
public static Contact createContactRecords(String contactJSON){
try {
ContactWrapper contactWrapperRec = (ContactWrapper)JSON.deserialize(contactJSON,ContactWrapper.class);
if(contactWrapperRec!=null){
Contact conRec = new Contact();
conRec.FirstName = contactWrapperRec.firstName;
conRec.LastName = contactWrapperRec.lastName;
conRec.Email = contactWrapperRec.email;
conRec.AccountId = contactWrapperRec.accountId;
insert conRec;
return conRec;
}
return null;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
public class ContactWrapper{
public String firstName;
public String lastName;
public String accountId;
public String email;
}
}
Explanation
We will start by creating an HTML form which takes inputs from the user. The values are entered in the lightning-input field, each field has a name (this is used in handleChange method of the js file) and type (used to define the kind of input a field accepts) attribute.
There is an onchange event on each field which calls the handleChange method of the JS file and saves the value of that field in a JSON Object ( inputForm variable), as a key pair value <fieldName>: <fieldValue>.
When user enters value for all the field in the UI a complete JSON is dynamically constructed in the JS file, for example :
inputForm = {
"firstName" : "gourav",
"lastName" : "bhardwaj",
"email" : "gourav@gmail.com"
}
This JSON Object is going to be super helpful when calling the Apex method, we will see that shortly. On click of the Submit button submitForm method is called in the JS code, here we retrieve the values stored in inputForm variable that was earlier populated also include accountId for contact association, convert that into a string using JSON.stringify and store it in contactString variable, pass that as a parameter while calling the Apex method using createContactRecords.
Note: createContactRecords is a reference to the static @AuraEnabled method created in ContactHandler Class. We should import the method using “import createContactRecords from ‘@salesforce/apex/<ClassName>.<MethodName>'” syntax and then use the reference createContactRecords. The name of the parameter should be same as mentioned in Apex class i.e contactJSON in our case.
The apex method createContactRecords received the JSON as a string value. We create a wrapper class in the Apex with a similar structure (same variable names) as the JSON being passed, we then deserialize the JSON String and populate the wrapper class created using (ContactWrapper)JSON.deserialize(contactJSON,ContactWrapper.class) syntax.
Now we have a reference to the wrapper class (Object) which is populated with the fields values passed in the JSON, we can use the dot “.” operator to access each values in the wrapper example: contactWrapperRec.firstName;
Once the wrapper class is populated with the values, we create an object reference for Contact using Contact conRec = new Contact() and populate each field of the Contact object.
In the next line, we insert the contact record with insert conRec, this successfully creates a new contact record in Salesforce which is associated with the AccountId passed.
Update Data with Apex & LWC
Once you have learned reading data and creating data in Salesforce using LWC, updating a record using Apex and Lightning Web Component becomes very easy. To update existing data in Salesforce you must make sure to populate the field in the UI, which can be done easily by either using getRecord wire adapter or using Apex as explained above.
Saving the updated value uses the same methodology that create data uses above.
See the code snippets below on how to render existing data in LWC and then update with an Apex class.

HTML Code
<template>
<lightning-card title="Account Update">
<lightning-input name="accountName" type="text" value={accountName} label="Account Name" onchange={handleChange}></lightning-input>
<lightning-input name="accountWebsite" type="text" value={accountWebsite} label="Account Website" onchange={handleChange}></lightning-input>
<lightning-input name="accountPhone" type="text" value={accountPhone} label="Account Phone" onchange={handleChange}></lightning-input>
<lightning-input name="accountNoOfEmployees" type="text" value={accountNoOfEmployees} label="No Of Employees" onchange={handleChange}></lightning-input>
<lightning-button variant="brand" label="Submit" onclick={submitForm}></lightning-button>
</lightning-card>
</template>
JS Code
import { LightningElement,api,wire } from 'lwc';
import { getRecord,getFieldValue } from 'lightning/uiRecordApi';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
//Account Fields
import ACCOUNT_PHONE from '@salesforce/schema/Account.Phone';
import ACCOUNT_NO_OF_EMPLOYEES from '@salesforce/schema/Account.NumberOfEmployees';
import ACCOUNT_WEBSITE from '@salesforce/schema/Account.Website';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
import updateAccountRecords from '@salesforce/apex/ContactHandler.updateAccountRecords';
export default class UpdateAccount extends LightningElement {
inputForm={};
@api recordId;
accountName;
accountWebsite;
accountPhone;
accountNoOfEmployees;
handleChange(event){
console.log('event.detals.name '+event.target.name+' : '+event.target.value);
if(event.target.name=='accountName'){
this.accountName = event.target.value;
}
if(event.target.name=='accountWebsite'){
this.accountWebsite = event.target.value;
}
if(event.target.name=='accountPhone'){
this.accountPhone = event.target.value;
}
if(event.target.name=='accountNoOfEmployees'){
this.accountNoOfEmployees = event.target.value;
}
}//handleChange Ends
//We will first get the Account Record to populate in the UI, using getRecord wire adapter
@wire(getRecord, {
recordId: "$recordId",
fields: [ACCOUNT_NAME,ACCOUNT_NO_OF_EMPLOYEES,ACCOUNT_PHONE,ACCOUNT_WEBSITE]
})
record({ data, error }) {
if(data){
console.log('data : ',data);
//this.accountName = data.accountname;//wrong
this.accountName = getFieldValue(data, ACCOUNT_NAME);
this.accountWebsite = getFieldValue(data, ACCOUNT_WEBSITE);
this.accountPhone = getFieldValue(data, ACCOUNT_PHONE);
this.accountNoOfEmployees = getFieldValue(data, ACCOUNT_NO_OF_EMPLOYEES);
this.accountFields = data;
} else if (error) {
console.log("error", error);
}
}
//We are calling the updateAccountRecords apex method and passing the JSON of fields to be updated
submitForm(event){
console.log('handle submit');
let contactSubmitForm ={
accountId:this.recordId,
accountName:this.accountName,
accountWebsite:this.accountWebsite,
accountPhone:this.accountPhone,
accountNoOfEmployees:this.accountNoOfEmployees
}
let contactString = JSON.stringify(contactSubmitForm);
updateAccountRecords({accountJSON:contactString})
.then((contact) => {
this.dispatchEvent(
new ShowToastEvent({
title: "Success",
message: "Contact created successfully!",
variant: "success"
})
);
//this.accountRecord = {};
})
.catch((error) => {
console.log('error : ',error);
this.dispatchEvent(
new ShowToastEvent({
title: "Error",
message: error.body.message,
variant: "error"
})
);
})
.finally(() => {
this.isLoading = false;
});
}//submitForm Ends
}
Apex Code
public with sharing class ContactHandler {
@AuraEnabled
public static Account updateAccountRecords(String accountJSON){
try {
AccountWrapper accountWrapperRec = (AccountWrapper)JSON.deserialize(accountJSON,AccountWrapper.class);
system.debug('updateAccountRecords accountWrapperRec : '+accountWrapperRec);
if(accountWrapperRec!=null && accountWrapperRec.accountId!=null){
Account accRec = new Account(Id=accountWrapperRec.accountId);
accRec.Name = accountWrapperRec.accountName;
accRec.Website = accountWrapperRec.accountWebsite;
accRec.Phone = accountWrapperRec.accountPhone;
accRec.NumberOfEmployees = accountWrapperRec.accountNoOfEmployees;
update accRec;
return accRec;
}
return null;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
public class AccountWrapper{
public String accountId;
public String accountName;
public String accountWebsite;
public String accountPhone;
public Integer accountNoOfEmployees;
}
}
Explanation
Similar to creating data we build a form in the HTML file the only difference here is we have an extra attribute called value which displays the initial values of that field in Salesforce Database.
Using the getRecord wire adapter in our JS code we query the record and extract the field values in separate variables, then use that in the value attribute of the lightning-input tag. This populates the fields with the initial value saved in the Salesforce Data for that record.
Once we are able to show the field values, saving the new updates is piece of cake. In our handleChange we update each JS variable with the right field value, this is done using the if, else condition.
In our create data code above we were dynamically doing this with just a single line of code, here we took this approach to show the difference between each approach and easy readability. You can still use inputForm={} JSON object to read and update field value. Give it a try and let me know in case of any issues in the comment box below.
The submitForm JS handler is called on the click of Submit button, we create a JSON Object and populate all the fields and also make sure to include the recordId, for which we want to update the data. Convert the JSON object into the string using JSON.stringify and pass that as a parameter to the apex method reference updateAccountRecords.
Note: Make sure to import the static Apex method which updates the record using import updateAccountRecords from ‘@salesforce/apex/<ClassName>.<MethodName>’ syntax.
In the Apex class we have created an Account wrapper class which is used to deserialize the JSON string received from the LWC component. Once the wrapper is populated with all the updated field values, we can use it to update the Account record.
We create a reference to the Account object and then assign each field values from the wrapper class, make sure to assign the Id field with the account id which you want to update, this is the key to updating the record. Update the Account object reference with update accRec.
Delete Data with Apex and Lightning Web Component
To delete record with Apex and Lightning Web Component, all you need to do is pass the Salesforce recordId to the static Apex method and write the code to delete the record. Deleting things are easy 🙂
Check the below code snippet to understand how it works.

HTML Code
<template>
<table>
<tr>
<th>Contact Name</th>
<th>Title</th>
<th>Email</th>
<th>Action</th>
</tr>
<template for:each={contactRecords} for:item="contact">
<tr key={contact.Id}>
<td>{contact.Name}</td>
<td>{contact.Title}</td>
<td>{contact.Email}</td>
<td>
<lightning-icon size="x-small" variant="error" data-contactid={contact.Id} onclick={deleteContactRec} icon-name="utility:delete" alternative-text="Delete" title="Delete">
</lightning-icon>
</td>
</tr>
</template>
</table>
</template>
JS Code
import { LightningElement,wire,track,api} from 'lwc';
import { createRecord,deleteRecord } from 'lightning/uiRecordApi';
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
import getRelatedContactRec from '@salesforce/apex/ContactHandler.fetchContactAccountRecords';
import deleteContactRecord from '@salesforce/apex/ContactHandler.deleteContactRecord';
export default class Crudexample extends LightningElement {
contactRecords;
inputForm={};
@api recordId;
@api objectApiName;
@wire(getRelatedContactRec,{ accountId: '$recordId'})
contactRec({ error, data }) {
if (data) {
//console.log('recordId : ',this.recordId);
console.log('data : ',JSON.stringify(data));
this.contactRecords = data;
} else if (error){
console.log('error : ',error);
}
}//contactRec Ends
deleteContactRec(event){
let contactidtobedeleted = event.currentTarget.dataset.contactid;
deleteContactRecord({contactId: contactidtobedeleted})
.then((data) => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Success',
message: 'Record deleted',
variant: 'success'
})
);
})
.catch(error => {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error deleting record',
message: error.body.message,
variant: 'error'
})
);
})
.finally(() => {
this.isLoading = false;
});
}//deleteContactRec Ends
}
Apex Code
public with sharing class ContactHandler {
@AuraEnabled
public static Boolean deleteContactRecord(String contactId){
try {
if(!String.isBlank(contactId)){
Contact contactRec = new Contact(Id=contactId);
delete contactRec;
return true;
}
return false;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
}
Explanation
To demonstrate delete functionality we are going to reuse the read data code, which rendered the list of contact records of an account in a tabular format. Each contact record has an action button (trash icon) which can be used to delete an individual record. If you observe the code you can see that each trash icon has an onclick handler -> onclick={deleteContactRec} and a custom attribute data-contactid={contact.Id}
Note: You can create custom attributes in HTML tags using data-<nameOfMyAttribute>, they are used to assign any custom value to a particular tag which can later be used to perform calculations for a specific tag. Like in our delete example above, we are assigning recordId of each contact in a custom attribute of the trash icon. So, when ever user click the icon we know for which contact record we have to execute the logic, by checking the value of data-contactid.
When the user click on the trash icon a JS handler method deleteContactRec is fired, we then extract the contact is that is associated with this trash icon (by finding the value in its custom attribute data-contactid) and sending this value to the Apex static method.
In our Apex method deleteContactRecord we get the contactid to be deleted, then construct a Contact object and delete the record.
Contact contactRec = new Contact(Id=contactId);
delete contactRec;
return true;
Important: You may have noticed that while creating, updating or deleting records in an LWC table you have to refresh the screen to see the latest changes. Won’t it be good if we can auto refresh the table content when there are any changes in the underlying data? This can be achieved in a number of ways but the best and the standard way to do this is by using RefreshApex. In our next article, we will show you how to use RefreshApex with a loader to achieve a seamless user experience.
2 Comments