Skip to main content

Step by Step: How do I become a LWC developer?

About Us
Published by JET BI
19 September 2023


If you're unsure where to begin your journey into learning LWC (Lightning Web Components), in need of direction, or interested in Web App Development, or simply enjoy reading articles – brew yourself a cup of tea and settle in comfortably.

How to create Progressive Web App using LWC  without experience:

I am a Marketing Cloud Developer (Marketing Cloud is my passion). Additionally, I have experience as a frontend developer. So, I know my way around building websites, but LWC is something intriguing, uncharted, and captivating. I've made the decision to tackle the Trailheads and create a small application (if all goes according to plan).

The Salesforce Application:

Now, let's delve into what Karma (the name of the app) is and what this application should be capable of. We all have colleagues, and at times, we may want to give someone praise while in other instances, a little criticism might be due. This is where Karma comes in – it's designed to handle exactly that. Here, you can leave feedback about your colleagues, and not just that, you can also assign points: positive (if you're commending someone) and negative (if you're slightly dissatisfied). All these points are recorded and accumulated in the employee's personal ledger. The application's interface looks as follows:


To understand how the application works, please follow the steps:

1st step - Select the desired colleague (ensure that search functionality is operational as well).

2nd step - Create a Karma review for the chosen colleague.

3rd step - A table displaying previously submitted reviews.

Regarding the red frame, we'll discuss that in a little while.

Good Trailheads to get started:

So, now that we have a clear vision of how the application should look and function, let's dive into exploring the Trailheads. Pay particular attention to similar components, as later on, we can make slight adjustments to the code and incorporate them into our application. Here's a list of Trailheads that will certainly prove invaluable:

1. Build Lightning Web Components

2. Lightning Design System Basics

3. Systems Design with the Lightning Design System

4. Build a Bear-Tracking App with Lightning Web Components

5. Migrate from Aura to Lightning Web Components 

Feel free to seek additional resources as well to enhance your knowledge in preparation for this project.

Hooray! I've completed the Trailheads and I'm ready to create the application!

The logic of the Karma App:

The required objects from the schema builder are as follows:


Component Logic:


1. Contact List:

    <template if:true={}>
        <lightning-layout multiple-rows="true" pull-to-boundary="small" oncontactview={handleContactView}>
            <div class="slds-box contact-block" >
                <lightning-input type="search"
                    placeholder="Search people"
                <template for:each={} for:item="contact">
                      <span key={}>
                        <div class="slds-col">
        <template if:false={hasResults}>
            <div class="slds-align_absolute-center slds-var-m-vertical_small">
                This is beary disturbing, we did not find results...
    <template if:true={contacts.error}>
        <div class="slds-text-color_error">
            An error occurred while loading the contact list
import { LightningElement, wire} from 'lwc';
import getContacts from '@salesforce/apex/contactController.getContacts';
import searchContacts from '@salesforce/apex/contactController.searchContacts';
import { publish, MessageContext } from 'lightning/messageService';
import ContactSelected from '@salesforce/messageChannel/Contact_Selected__c';

export default class ContactList extends LightningElement {

    @wire(getContacts) contacts;

    handleItemClick(event) {
        const payload = {contactId: };
        console.log("payload" + JSON.stringify(payload))
        publish(this.messageContext, ContactSelected, payload);

    searchTerm = '';
    @wire(searchContacts, {searchTerm: '$searchTerm'})

    handleSearchTermChange(event) {
        const searchTerm =;
        this.delayTimeout = setTimeout(() => {
            this.searchTerm = searchTerm;
        }, 300);
    get hasResults() {
        return ( > 0);

For the creation of this component, the link proved to be particularly helpful.

2. contact Item

    <lightning-card title="Create Karma Entry">
            <lightning-input-field field-name={personField} value={recordId}> </lightning-input-field>
            <lightning-input-field field-name={typeField}> </lightning-input-field>
            <lightning-input-field field-name={pointsField}> </lightning-input-field>
            <lightning-input-field field-name={commentField}> </lightning-input-field>
            label="Create new">
import { LightningElement, wire, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import {
} from 'lightning/messageService';
import ContactSelected from '@salesforce/messageChannel/Contact_Selected__c';
import CONTACT from '@salesforce/schema/Contact';
import NAME_FIELD from '@salesforce/schema/Contact.Name';
import KARMA_OBJECT from '@salesforce/schema/jetbikarma__Karma_Entry__c';
import PERSON_FIELD from '@salesforce/schema/jetbikarma__Karma_Entry__c.jetbikarma__Person__c';
import TYPE_FIELD from '@salesforce/schema/jetbikarma__Karma_Entry__c.jetbikarma__Type__c';
import POINTS_FIELD from '@salesforce/schema/jetbikarma__Karma_Entry__c.jetbikarma__Value__c';
import COMMENT_FIELD from '@salesforce/schema/jetbikarma__Karma_Entry__c.jetbikarma__Text_Comment__c';

export default class ContactItem extends LightningElement {

    subscription = null;

    // Encapsulate logic for Lightning message service subscribe and unsubsubscribe
    subscribeToMessageChannel() {
        if (!this.subscription) {
            this.subscription = subscribe(
                (message) => this.handleMessage(message),
                { scope: APPLICATION_SCOPE }

    unsubscribeToMessageChannel() {
        this.subscription = null;

    // Handler for message received by component
    handleMessage(message) {
        this.recordId = message.contactId;

    // Standard lifecycle hooks used to subscribe and unsubsubscribe to the message channel
    connectedCallback() {

    disconnectedCallback() {

    // Expose a field to make it available in the template
    nameField = NAME_FIELD;

    // Flexipage provides recordId and objectApiName
    @api recordId;
    contactApiName = CONTACT;
    //submit Karma
    objectApiName = KARMA_OBJECT;

    //fields for record-edit-form (HTML)
    personField = PERSON_FIELD;
    typeField = TYPE_FIELD;
    pointsField = POINTS_FIELD;
    commentField = COMMENT_FIELD;
    karmaId = null;

    handleSuccess(event) {
        this.karmaId =;
        const toastEvent = new ShowToastEvent({
            title: "Karma is created",
            message: "Record ID: " +,
            variant: "success"


    handleError(event) {
        alert("error" + JSON.stringify(event));
        const toastEvent = new ShowToastEvent({
            title: "Error Creating Karma Entry",
            message: "An error occurred while creating the record: " + event.detail.message,
            variant: "error"


For the creation of this component, the 1 link and 2 link proved to be particularly helpful.

3. entry Table

    <div style="height: 200px;">
        <template if:true={entryTable}>
            <lightning-datatable class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_striped"
        <template if:false={entryTable}>
            Table disabled
import { LightningElement, track, wire} from 'lwc';
import getEntryTable from '@salesforce/apex/getRecordDataController.getEntryTable';

export default class EntryTable extends LightningElement {

     @track columns = [
            { label: 'Date', fieldName: 'CreatedDate', hideDefaultActions: true },
            { label: 'Give to whom, ID', fieldName: 'jetbikarma__Person__c', hideDefaultActions: true },
            { label: 'Karma points', fieldName: 'jetbikarma__Value__c', hideDefaultActions: true },
            { label: 'Comment', fieldName: 'jetbikarma__Text_Comment__c', hideDefaultActions: true }

     @track entryTable;

     @wire (getEntryTable) wiredContacts({data,error}){
        if (data) {
             this.entryTable = data;
        } else if (error) {
        console.log("Message" + JSON.stringify(error));

For the creation of this component, the link proved to be particularly helpful:

4. Selector (this is the wrapper for our three components, highlighted in red)

    <div class="wrapper-window">
        <div class="wrapper">
            <section class="content">
                <div class="columns">
                    <main class="main" >
                        <c-contact-list oncontactselected={handleContactSelected} class="contact-list"></c-contact-list>
                    <aside class="sidebar-second">
                        <c-contact-item contact-id={selectedContactId}></c-contact-item>
        <c-entry-table class="entry-table"></c-entry-table>
import { LightningElement } from 'lwc';

export default class Selector extends LightningElement {
body {
    margin: 0;
.wrapper-window {
    height: 100vh;
.wrapper {
    height: 60vh;
    overflow: hidden;
.contact-list {
    display: block;
    overflow-y: scroll;
.content {
    background: #999;
    color: #000;
    display: flex;
    overflow-y: scroll;
    width: 25vh;
    background: #eee;

    width: 90%;
    background: #ddd;
.entry-table {
    height: 45vh;

Within the application, there exists a touch of Apex code (which I also wasn't familiar with). I approached it guided by the logic from the referenced Apex code examples from the links with Trailheads:

public with sharing class ContactController {
    public static List<Contact> getContacts() {
        return [
            SELECT Id, Name, jetbikarma__Photo__c
            FROM Contact
            ORDER BY Name

    @AuraEnabled(cacheable=true scope='global')
    public static Contact[] searchContacts(String searchTerm) {
        searchTerm = searchTerm.trim();
        if (searchTerm == '') {
            return getContacts();
        // Prepare query paramters
        searchTerm = '%' + searchTerm + '%';
        // Execute search query
        return [
            FROM Contact
            WHERE Name LIKE :searchTerm
            ORDER BY Name
public with sharing class getRecordDataController {
    public static List<jetbikarma__Karma_Entry__c> getEntryTable() {
        return [
            SELECT Id, Name, jetbikarma__Value__c,  jetbikarma__Person__c, jetbikarma__Person__r.Name, jetbikarma__Text_Comment__c,
            FROM jetbikarma__Karma_Entry__c
            ORDER BY CreatedDate DESC

    public static String getContactName(Id contactId) {
        String contactName = '';
        if (contactId != null) {
            Contact contactRecord = [SELECT Name FROM Contact WHERE Id = :contactId LIMIT 1];
            if (contactRecord != null) {
                contactName = contactRecord.Name;
        return contactName;

Additionally, I exclusively employed SLDS and refrained from crafting custom components. My application was created via Lightning App Builder. It’s a simple way to render the app quickly.

Issues with communicating between Lightning Web Components:

The most challenging part for me was to establish a connection between the components so that when clicking on a contact, the same contact would be selected in the form. My mentor was a great help to me in this aspect.

Due to the absence of a parent-child relationship between the components contactList and contactItem, I had to resort to using the Lightning Message Service integration. And I believe that I wouldn't have managed with just one. It was quite challenging for a first-time application. But you remember my mentor-superhero :)

A Lightning Message Service was created:

<?xml version="1.0" encoding="UTF-8" ?>
<LightningMessageChannel xmlns="">
    <description>Message Channel to pass Count updates</description>

The code you can see on GitHub 

Tired and proud of myself:

And now, my application is complete! Admittedly, not everything fell into place immediately, which leads me to extend my gratitude to my mentor for their assistance and patience (Hello, Ilya). While the application isn't flawless at the moment, it is indeed operational.

Words can hardly express the pride I feel in my work. Though I may not yet be deemed a proficient LWC developer, I have successfully taken that crucial initial stride.


LWC is a remarkably powerful framework, and if you possess Web Development experience (with basic JavaScript) and complete a few of the Trailheads mentioned in the article, you are well-equipped to create your first application. 

Having a mentor to lean on for guidance when challenges arise is also crucial.

And, of course, your own determination plays a vital role in this journey.

If you need Salesforce development - you know where to go. Our team includes not only beginner LWC specialists (like me), but also experienced senior developers, PMs, BAs, Administrators, testers and just wonderful people 🙂

You can read more about real Karma app.

And if you've read through to this point – you're invited to this link: to explore the actual Karma application. 

If you need help, please, contact us.

Irena Chistyakova
Salesforce Certified Marketing Cloud Developer
Question to the expert

We have available resources to start working on your project within 5 business days

1 UX Designer


1 Admin


2 QA engineers


1 Consultant


Steps following request submission



After receiving your request, we analyze it and we offer free online meeting slots (via email) so that we can discuss your needs in as much detail as possible


We begin gathering all necessary requirements to create comprehensive estimates, including timelines, resource allocations, risk assessments, and underlying assumptions.


Once all preparations are in place, we will initiate the project and move forward with the planned tasks