How to Build Modern SPA’s on Salesforce

Prologue

About 3 years ago I wrote a CPQ in Visualforce. Like most enterprise software, it started simple(ish) and iteratively grew into a beast of complexity. The original requirements were to support up to 50 line items. Today, it’s not uncommon for someone create a quote with over 500 items.

Needless to say, my solution did not aged well. Every day I was supporting users who had encountered uncatchable platform limit exceptions. I spent many late nights optimizing between Viewstate and CPU time. But improving one would always stress the other. Even when it wasn’t crashing, the UI was PAINFULLY slow. Inevitably, we decided that a full re-write would be necessary.

Unfortunately the sales team became very familiar with this screen

After months of development, our new and improved CPQ recently gone live. The whole process, from start to finish, has just been much better than I could have hoped. The new app is:

  • blazing fast
  • not bound by platform limits
  • testable
  • easy to debug & support
  • takes full advantage of modern development tools & CI processes
  • blazing fast
  • easier to find qualified developers already understand the framework

I’ve learned that salesforce development doesn’t have to be a dreadful experience and I’d like to share how.

Visualforce to the rescue?

You probably expected I would highlighting be the new Lightning Component Framework. However I would argue, that because our app is completely standalone, Visualforce is actually the superior choice.

Visualforce truly is a horrible framework for web applications. It’s slow, monolithic, extremely limited and has been neglected worse than our countries crumbling infrastructure.

Q: So why did we stick with it?
A: Visualforce is GREAT when it does not contain any Visualforce

The B.A.S.S. Stack

So yea… we’re just using Visualforce as a container. Nothing ground breaking; you may have seen/done something similar. What I really want to demonstrate is how this approach can be combined with the latest and greatest in web development to give you a truly “modern” app & development experience.

I’ve extracted the tooling, frameworks, configurations & scripts that make up our app into something I’ve dubbed the Bad Ass Salesforce Stack starter:

BASS like the fish

React: proven, lightweight, fast, and it doesn’t hurt to officially be supported by a tech giant. Falls somewhere in between the over-engineering of Angular2 & the minimalism of vuejs. Quick & Easy to learn.

Redux (optional): Since our app is heavy in business logic, we needed dedicated state management. However, for applications that are primary data display & CRUD, you probably don’t need this. You could also swap redux with other frameworks like mobx.

Typescript: While I’ve listed it 3rd, this is my favorite part. You could easily make a case for Angular2, VueJs or any other framework to replace the rest of this stack, but (IMO) Typescript is CRITICAL to writing enterprise javascript applications. Advanced typing system, code-completion, additional language features, compilation errors, improved refactoring… the list goes on…

antd (optional): Component library. There are a lot of options to choose from in this category, but I’ve really enjoyed working with antd. If it’s important that your app looks for “salesforcey”, you could swap with React LDS.

ts-force (optional): a strongly typed Salesforce ORM. Uses code generation to created concrete classes for SObjects. (Disclaimer: I’m the author and this package is still in development… that said, it’s been surprisingly stable). You could replace with js-force or any other javascript client, but you’d lose productivity & type safety.

webpack: Used to package app and as a local development server

sfdx-cli: used to authenticate & deploy to salesforce

It’s worth noting, the specific choices above could be replaced with pretty much any modern frameworks/libraries. The true goal of this repo is really just to demonstrate how you can apply modern develop & build processes to Salesforce. It works well as a “starter” which can then be modified to fit your needs.

The important thing is that our solution is no longer “owned” by Salesforce.

Dev Workflow

Local Development

B.A.S.S. is optimized to give the best possible developer experience (B.A.S.S.D.X?). Salesforce recently opened up custom CORS, which allows us to use it’s API’s with localhost.

Execute npm start and in a few seconds your app be running on localhost. With Hot Module Reloading, as soon as you save changes, your app will automatically update without having to reload:

HMR FTW. “Real” data coming from Salesforce

Manage Salesforce Resources in same project

B.A.S.S. is setup as a DX project, so you can manage any dependent meta-data (classes, objects, etc) from a single project. Anything you add here will automatically be packaged with your deployment.

The DX app along side src

Other cool stuff:

  • Ability to develop using local assets on Salesforce (if you need a feature that prevents it from running locally; eg: streaming api)
  • Set source-mapped breakpoints on .ts files and debug without leaving vscode
  • Setup for .less stylesheets
  • React devtools to inspect your app
  • Redux devtoolsto view state and replay actions

Build -> Deploy

Deployment is as easy as executing npm run deploy-[target] where [target] is prod , dev or scratch. The process uses webpack to bundle your app, which is then picked up by sfdx-cli to deploy.

BUILD -> DEPLOY

This may look involved, but it’s actual quite simple:

  1. Transpile typescript files to javascript
  2. Bundle with other assets (to keep file size down, node_modules is separated out to vender.js)
  3. zip and copy to app.resource
  4. Use sfdx-cli to deploy to salesforce (using either sfdx force:org:mdapi or sfdx force:org:push on target)

Debugging

Every VF developer has been there. There‘s a production bug that you just can’t seem to reproduce in sandbox. You spend way too long combing through truncated debug logs until you finally break down and liter System.debug()statements everywhere. Then you wait FOREVER while your code deploys.

Never again. In a B.A.S.S. project, simply run:

npm run make-prod-default
npm run cors-enable
npm start

And your app will be running locally with production data (be careful :0)!

Set a breakpoint in the suspect code, hit f5 and start stepping through and inspecting variables.

When you have fixed the bug, just execute npm run deploy-prod to push your code live (or even better, setup a CI process to deploy code on git-push).

Disclaimer: Always remember to disable CORS (npm run cors-disable) and set the default user away from production (npm run make-dev-default) afterwords so you don’t do anything stupid.

State Capture

If you use redux in your project you can do some really cool things with almost no additional effort. For example, our app has a “submit bug” feature:

When the user submits, we grab the entire redux state and store it in a custom object. I can then launch the app using the logged state and I can see EXACTLY what the user was seeing.

We use this same concept for anytime a unhandled exception is thrown.

Data-Access

ts-force uses code generation to create concrete classes for your SObjects.

  • automatically “prettifies” API names(My_Api_Name__c -> myApiName)
  • allows you to define your own custom mappings.
  • adds “reflection” so you can access field properties like the “salesforce label”, “readonly” & “required”.
  • sets up relationships for any related objects that are included in the config
  • provides a client for both the Rest and Composite API’s
  • can use mappings objects with custom endpoints

To get going, specify which objects you want to work with in your config file:

Then, generate your classes npm run generate-ts-force .

Now you can write “amazing” code like this:

Note: Always generate classes using an “End User”. See readme for more details & configuration options.

Security

Luckily, one of the things Salesforce handles really well is security.

Since the token is just injected via the VF page, no authentication is required.

//src/index.ts
import {setDefaultConfig} from 'ts-force';
setDefaultConfig({
  accessToken: __ACCESSTOKEN__, //injected by VF or webpack
  instanceUrl:  __RESTHOST__,
});

When running locally, it uses sfdx force:org:listto retrieve the default users token and injects it with the webpack define plugin.

The REST API respects the Object, Record and Field Level Security of the current user. ts-force makes things easy by only generating properties for fields they have access to and not sending readonly properties on insert/update request.

If you need to perform a operation the user doesn’t have access to, you can write a custom RestResource which always runs in SYSTEM context.

When I’m designing data-access, I like to start with this simple question:

Is this operation something the user could do via the UI?

If the answer is YES, then you should be safe to just use the REST API. Otherwise you’ll need a custom service.

The exception would be if you need something to be transactional. It’s probably easier to write a custom endpoint and let salesforce handle rollbacks.

Note: We had the luxury of running on a internal system where employees are the only users. If this was a public facing or B2B app, you would need to be careful when making CRUD operations.

Conclusion

I know we just covered a ton of material from a variety of topics, but I wanted to give the complete picture.

Needless to say, I’m pretty-darn-excited to get to work with these technologies. After almost a year of working on this solution, I’m confident that it is a better alternative to VF and also Lightning (depending on your needs). But don’t take my word; clone it down and give it a try!

Back to all Blogs