I’ve been working with low-code tools for years, building automations, integrations, and APIs. Recently, I’ve wondered whether we’ve advanced far enough to use a low-code platform as the back-end for a single page application (SPA). It seems to be a nice combination — the SPA can be hosted anywhere, the API is quick and easy to develop, and they can evolve independently of each other e.g. if you need a native app just develop the UI and use the same API.
So, I decided to see whether we can by building a web application from scratch where
- The developer (me) is not a full-time developer.
- The application must interface with other systems.
- The application must persist data.
Most non-trivial web applications require some kind of user onboarding and authentication with a database to store data and a back-end to do the processing. In this case, I included interfacing with a SaaS product (Xero) using OAuth2 to make sure we can handle some complexity.
The application, therefore, has the following features
- Connect to Xero.
- Disconnect from Xero.
I decided to go for React for the front-end, as it is the most popular web framework, Linx for the back-end, as it is the fastest way I know of to implement an API (I work with it every day), and MS SQLServer as the database.
The following steps are the discrete pieces I worked on. Pick a feature, build UI, design API, implement API, test, repeat.
As a newbie to React, this bit took the longest by far. Luckily there are lots of online resources available to help. What doesn’t help are the number of different ways to do the same thing, styling being a particularly difficult area for me to pick a winner. In the end, I used:
- npx create-react-app: Create the basics.
- React-bootstrap: Styling.
- React-router: Routing.
- Formik + Yup: Forms.
After wading through reams of sample code, I decided to use hooks for state and lifecycle management. They’re a relatively new addition to React but seem to be simpler to use once you understand the concepts. This enabled me to wrap the authentication and API calls in a single context object that any of the components could get to by using a useContext hook. It seems to work well but with my non-existent React experience, it might not be the best way to do things. Let me know if there’s a better way.
Another area I wondered about was how to organize the code. There seems to be lots of opinions out there but in the end I kept it simple and ended up with
One area that I still do not have a satisfactory answer for is where to store the auth token. In this example, it is stored in local storage. Anything in local storage is at risk of XSS attacks (see this) but weighing the risk of a successful attack and the damage an attacker can do, against the pain of setting up the infrastructure to use HTTP-only, same-site cookies (which in theory is the most secure) with a SPA is difficult. In the end, I decided that the risk is very low and the possible damage in this case is negligible, so “get-it-out-of-the-door-quickly” won.
I used Visual Studio Code as the code editor. The ability to edit the code and immediately see your changes reflected in the browser makes for a very efficient workflow.
The finished product looks like this:
This step was fairly straightforward. The most time was spent on thinking through which endpoints will be required and wrangling some finicky JSON OpenAPI3 syntax.
As I implemented the API with Linx, I needed to create an OpenAPI3 specification as the starting point. I used a combination of Notepad++ and Swagger Editor to write and verify the schema. After much to-ing and fro-ing on whether it should be signup or register, login or sign in, recover password or forgot password, I gave up and settled on these endpoints:
- /signup [post]. Accepts an email, first name and last name and sends and email with a unique url to the email address.
- /confirmemail [post]. Once the user clicks on the url on the email the front-end submits the embedded unique code to this endpoint and the signup is confirmed.
- /login [post]
- /recoverpassword [post]. Sends an email with a unique code so a user can reset their password
- /resetpassword [post]. Accepts the unique code and the new password
- /user [get]
- /xero/connect [get, post, delete]. Operations to connect to Xero
- /xero/settings [get]
You can see the full schema here.
I decided to use a bearer security scheme using JWT tokens. It is described in the schema under components/security schemes.
This was a quick and painless step. In Linx Designer:
- Create a new solution.
- Add the REST plugin.
- Add a RESTHost service from the REST plugin to the solution.
- Set its APIDefinition property to the OpenAPI3 schema.
- Set the secret key in the AuthConfig property.
Linx does the rest and creates all the endpoints for you. If that sounds like Klingon, this is what it looks like:
Implementing the API didn’t require any coding, at least not in the classic text-based programming sense. With Linx, you install the plugins you require, drop the functions in the right places, and configure the properties. It is still programming, just with more hand-holding and without the boilerplate.
I added the following plugins:
- REST: Services and functions to host and call REST endpoints
- Database: Functions for database access
- Cryptography: JWT generation, encryption
- Email: Sending the emails
- Text: Used RegularExpression for validation.
and decided to split the solution into distinct parts for easier maintenance:
- App: Main functionality.
- Db: All database access.
- Xero: All Xero interaction.
Linx made it easy to follow an iterative implementation process i.e. pick a feature, implement, test, repeat until it works. Testing early and often was the best way to limit surprises later on. This is what the debugger looks like while testing the recoverpassword endpoint:
As a final step, I moved the application of off my local machine and into the cloud. There are probably 100s of places where you can host a React SPA these days. I put mine in Azure file storage and used their static website hosting to serve it. It literally took minutes.
The API went to a Linx Cloud Server. Just hit the deploy button, select your server and off it goes. The only painful bit is changing the settings for the new environment the first time it is deployed. That doesn’t sound too onerous but you’ll be surprised at how many settings a simple application like this has. Just look at this screenshot:
At least you only have to do it once.
Using a low-code platform to host your API is entirely feasible. It is a great option to consider if you want to get something out of the door quickly.
React has quite a learning curve but once it starts to make sense all is forgiven. I’m just afraid that if I don’t use it every day I’ll probably forget most of it by next week. As a next experiment, I could try to replace it with a low-code UI builder like Bubble or WebFlow.
It was surprising how much complexity lies in user security. I’m going to try to move the user security bits to a service like Auth0, Octa, Netlify or Firebase so our back-end only has to handle the business logic.
The full source code is available here. Let me know if you have any comments or suggestions.