Thursday, 31 October 2024

Project 2 - Simplest SAAS - Concept and Approach

In my quest to build a side-hustle software-as-a-service business, I am building and deploying projects to level up my front-end and full stack development skills.  Now that I have completed and deployed my first Vue based solution, it is time to move on to something more challenging.

For my second project, I want to build and deploy a complete but minimalist software-as-a-service site.  To meet my self-imposed definition-of-done, this site will need to be functionally complete, hosted and have some potential to actually make money.  The site will also need to have a responsive UI that works on desktop, tablet and mobile platforms.

Since this will be a SAAS solution, it has to have something to sell. For this site the concept will be the simplest thing I can think of. The site will enable members to create and publish permanent 'bulletin board' articles. Users will pay a nominal amount for each article that they want to publish to the world.

The 'elevator pitch' here is that it is actually not easy to publish a simple 'open letter' to the world with any degree of permanence or which isn't either behind a paywall or saturated with adverts. Even a blog site like this one requires some effort.  A simpler 'no frills' option may be more attractive.  

Anyway, even if this concept isn't a winner, it doesn't really matter. The purpose of this exercise is to 'learn by doing' rather than making an actual profit, but I want at least to have some kind of product so that I can look at building the sort of account/payment features that a SAAS site demands.  Keeping the core site concept simple will enable be to focus on learning how a SAAS ecosystem works.
 
Unlike my first project which was front-end only, this solution will require multiple layers. Eventually I will need to integrate some sort of persistence layer, an authentication service,  and of course a payment provider. 

However, because another key focus of this project will be improving my woeful UX skills,  I am going to take a UI-first approach, where I will abstract and simulate the back end layers to start with. The general idea will be to get a fully featured version of the UI working first, then look at integrating back-end services.  I'll also learn from my previous project mistakes and start building a test suite from day one!

Here is the basic list of features that the solution will need to support

  • Customers - Core Features
    • Create, Edit and Delete articles
    • Publish/unpublish articles
  • Customers- Account/Billing Features
    • View/Modify Account Details
    • Purchase article publishing 'slots'
    • View Order History 
  • Authentication
    • Sign up new users
    • Login and authorise customers
  • Support
    • Support/feedback page
  • Public Users
    • View Published Articles

I think this covers the minimal set of features a SAAS solution will need.  If I get these working then I should be able to use this as a reference for building more complex SAAS  solutions for future projects.

The technology stack for the front-end will be identical to that of the first project:  Vue, Vuetify, Vite and Vitest all seem to work well together and this will be a chance to do a deeper dive with them,

I'll leave a deep discussion of the technology stack for the back-end for a future post, but eventually I aim to integrate the front-end with Supabase for Authentication and Persistence and with Lemon Squeezy as a payment provider. 


Saturday, 19 October 2024

Project1 - HowMuchIsThisMeetingCosting - Mistakes were made!

I am trying to level up my front-end development skills as part of a plan to build a side-hustle business. In the previous posts I have been discussing my first project  - howmuchisthismeetingcosting.com - a simple front-end only site for calculating the live cost of a meeting.

I was excited to finish and deploy this first project. Getting a side project - even a simple one - over the finish line is no small  feat.  In software development the Pareto Principle is often considered to apply: finishing the last 20% of a solution can take 80% of your effort.  For a side project or a self-training project it is all too easy to avoid this overhead and quit when the project is 'good enough', but not done.

I am completely guilty of this. I am a serial project starter and rarely get something finished before I get distracted by something shiny. One of the drives behind picking a simple project was to pick a task that wouldn't be too challenging to finish, and to start trying to get out of the bad habit of abandoning projects before they are done.

This is one of the core ideas from the book 'Atomic Habits' by James Clear.  To build positive habits or get rid of bad ones,  start by making easy to achieve changes and do them enough that it becomes automatic. Set the cadence with easy to achieve actions and then add complexity/challenge _after_ the desired behaviour has become automatic and habitual.  In my case I want to start building a habit of actually finishing projects - and one way to do this is to make sure that I start with projects that aren't overly challenging to get done.

Given all that, I was pleased to get my first project over the line.I was so excited that I couldn't wait to show it off.  Look friends - I actually finished something!  Cheers and Congratulations to me! 

Then, about 30 seconds after trying to show the site off, a good friend of mine pointed out that the meeting cost numbers were not making any sense. The site was miscalculating the cost by a factor of around 100. The web site - which was intended to be a small blow for the cubicle worker against management bureaucracy and waste - was making it seem like pointless meetings were 100 times cheaper than they should be!

In my excitement to get something finished, I used the 'hold my beer' approach to software review and testing . The site was simple enough - and it seemed to be working - surely eyeballing it would be fine, right? Wrong.

At the heart of this website is a simple typescript class that is responsible for maintaining the running state of the meeting timer (src/components/utilities/timerstate.ts in the source).  This class is a stateful service - a javascript singleton - that keeps track of the number of seconds that have elapsed in the meeting and use this to construct a string that displays the elapsed meeting cost, this string is then stored in a reactive variable that updates the HTML display.  

In the original version, that was all done in a single method. The method was about 20 lines long and looked simple enough.  What could go wrong?    Well obviously something did. 

To construct the meeting cost,  I calculate the total cost of the meeting in cents,  use a floor division by 100 to find the dollar component, then use the remainder to construct the cents string.

let cost_per_second = (this.avg_cost_per_hour * this.no_participants) / 3600;
let total_cost = cost_per_second * this.number_of_seconds;
let dollars = Math.floor(total_cost / 100);
let cents = Math.floor(total_cost - dollars * 100);
let dollarString = dollars.toString();
let centsString = cents.toString().padStart(2, "0");
this.cost_display_string.value = this.currency_symbol + dollarString + "." + centsString;


This issue here of cause is that I am actually calculating the total_cost as dollars not cents which is why the cost is out by a factor of 100.

The fix here is simple, but the fact that it occurred is not great. There are several contributing factors:
  • there are no unit tests. There wasn't even a test framework installed in the project
  • even if there were unit tests, this code isn't written to be well tested. It is making unpure calculations that change the state of the class, making them hard to isolate  
  • the variables are poorly named. if I expect the total cost to be in cents, it should be named something like 'total_cost_cents'.
In my rush to develop a good habit (getting something finished) I didn't look at reviewing this code, or taking the time to perform even basic unit testing.  For a simple site like this the harm was just my embarrassment, but if I want to eventually have my own paying customers then I need to do much better.

(there is a tension between learning new things and building robust things to navigate here. I think it is OK to make mistakes when learning, but the failure here was not being disciplined enough reviewing the solution before calling it 'done' and shipping it. This is probably much like writing a book. I have read that is OK to make mistakes in your first draft and being overly picky at first can probably break your flow, but that you need to be rigorous about editing once the draft is done.)

Anyway, deploying a broken site is unacceptable and the must rot stop here! Although this was just a 1 line fix, I decided to do things better.  I refactored that class for testability by moving the calculations into pure functions, and for readability by improving the variable names.  I then installed vitest and wrote some simple unit tests to make sure that the problem was fixed.  The relevant part of the class look much better now, and more importantly they actually work.

calculate_cost_per_second(cost_per_hour: number, no_particpants: number) {
    return (cost_per_hour * no_particpants) / 3600.0;
  }

  calculate_current_cost_cents(
    cost_per_second: number,
    no_seconds_elapsed: number
  ) {
    return cost_per_second * no_seconds_elapsed * 100;
  }

  calculate_cost_display_string(
    total_cost_cents: number,
    currency_symbol: string
  ) {
    let dollars = Math.floor(total_cost_cents / 100);
    let cents = Math.floor(total_cost_cents - dollars * 100);
    let dollarString = dollars.toString();
    let centsString = cents.toString().padStart(2, "0");

    return currency_symbol + dollarString + "." + centsString;
  }


vitest seems to be another of the v-family of products that are built around Vue (vue, vuetify, vite,)  that just works - it integrates nicely with vite and is simple to learn quickly.  There really wasn't much excuse not to use it. With the calculations extracted into pure functions they become easy to test:


test("MeetingTimer - calculate_cost_per_second", () => {
  const timer = new MeetingTimer();

  const no_people = 100;
  const avg_cost_per_hour = 100;
  const num_seconds_per_hour = 60 * 60;
  const expected = (no_people * avg_cost_per_hour) / num_seconds_per_hour; //100 people at 100 avf cost per hour
  const actual = timer.calculate_cost_per_second(no_people, avg_cost_per_hour);

  expect(actual).toBe(expected);
});

Now that I know the site is fixed, I have redeployed it and it is back to doing its job - waiting for the chance to passive-aggressively shame time wasters. 
 
I think the key take away lesson for this is that I need to be more disciplined about reviewing and testing code, and that not to let the sugar rush of getting something 'done'  overwhelm the need to do things 'right'.

Saturday, 12 October 2024

Project 1 - HowMuchIsThisMeetingCosting - Hosting

In previous posts I have mentioned that this blog is about my journey to create a side-hustle software project brick-by-brick, and to level up my front-end skills (which are not great)  I decided to build and deploy a simple front-end only web application using Vue. You can read more about the implementation  details in the previous few posts.  

In this post I am going to quickly cover how I deployed the site. 


The loose ‘definition-of-done’ that I gave myself for the first project was ‘develop and deploy front-end only site that works, does something useful that has the potential for making some money’


The site is marginally useful - there may be someone out there who wants a no-frills meeting cost calculator, but the potential for making money is, um., not very high.  I had some ideas for how to use it to generate revenue (I’ll probably cover that in a future post) but given the simplicity of the site none of them were realistically ever going to make much.


Still, I definitely wanted to deploy a working site to the internet as my ‘finish line’. There is usually a huge complexity difference between 'it works on my machine' and 'it works for everyone' and I want to make sure that I am learning web deployment and hosting techniques as part of my skill-up journey.


Since the aim of the game for my side-hustle is to make money, I also wanted the hosting costs for this low-potential site to be as cheap as possible.


I ended up choosing Cloudflare Pages as my hosting option for a number of reasons

  1. It is a free product for low traffic web-sites 

  2. It is great fit for Javascript  sites that don’t have a back-end component

  3. It has integrated continuous deployment with GitHub

  4. It has free SSL

  5. I had already bought the DNS for the site from Cloudflare, so there was no issue with DNS remapping.

  6. Did I mention that it is free?


The other options that I considered were Azure, AWS and Digital Ocean.  I came up with this short list by googling ‘best low cost SAAS hosting options’ and then adding Azure to the list. I added Azure because this is the cloud provider that I am most familiar with, but a quick look at the real hosting costs quickly put me off. Both Azure and AWS might be good options for business that have actual paying clients, but they are not friendly for cheapskates or tyre-kickers.


Digital Ocean looks like a reasonable option for sites that need a self-hosted back-end, but their pricing isn’t free. I really like free.  Especially for this site. The only limitation of the free plan that I can see is that it limits you to a maximum of 500 builds per month. If you are building more frequently than that then you probably way past the 'just getting started' phase and hopefully have a solid revenue stream!


Anyway, choosing Cloudflare Pages enabled me to check the ‘deployed and actual web site’ box without paying anything.


(In full disclosure, since I am paying for the DNS entry for the site I am actually losing a small amount of money on this site. Horror! My side hustle business is currently in the red!  I should never have made the dog my CFO - I can tell her heart just isn’t in it... In seriousness though, I have given myself a very modest budget to get this whole thing started, I can sustain some costs but where possible I will try to avoid them! )


The process of setting up the Cloudflare pages site is relative simple if you already have the DNS entry for your host registered with Cloudflare DNS.  All you really need to do is
  1. Create a new Pages site and integrate is with a GitHub repository, as detailed in the documentation here:  
  1. Create a custom domain for your site and set up a custom domain that links it to the Cloudfare DNS entry you have created, as detailed in the documentation here
  1. Fully test your site locally. Run a local build and resolve any errors.
  1. Push a git commit to the github branch you have linked to. CloudFlare pages will detect the commit, and automatically build and deploy your site.
If all goes well your site will be compiled and deployed to your chosen DNS address in a matter of minutes.

(Side-hustle revenue opportunity note: One side-hustle revenue option is to join affiliate programs for vendors you like and are willing to put a good word in for.   However, I personally believe that affiliate relationships should always be disclosed. If you are open and honest about affiliate programs and the pros/cons of affiliate products then it can be a win-win situation where you get paid by a vendor for linking them with potential customers who are genuinely interested in their services. If you are dishones t about the products or try and hide affiliate relationships then you can lose the trust of the people you 'trick'.  A downside of affiliate programs is that they can lead to a perception of bias.  For example, I could just be shilling Cloudflare here because I signed up for an affiliate program and I want those sweet click-through returns.  Unfortunately, Cloudflare doesn't seem to currently have an affiliate program so that revenue option is a dry hole.  Fortunately, that means that I can avoid any perception of bias here - I actually found it to be a worthwhile product)   




Wednesday, 9 October 2024

Interlude - VSCode Formatting for Vue and Typescript

 

I've been having trouble getting VSCode code formatting to work the way that I want with Typescript files and Vue SFCs.   Here is what finally seemed to work for me.

1) Install the official Vue extension Vue.volar

2) Install the Prettier Code Formatter extension esbenp.prettier-vscode

3) Open your user settings.json  file (%AppData%\Code\User\settings.json on Windows)

4) Add/change the entries for Vue and Typescript and HTML as follows:

 "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnType": true,
    "editor.formatOnSave": true
  },

  "[vue]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnType": true,
    "editor.formatOnSave": true
  },
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "html.format.maxPreserveNewLines": 1,
  "html.format.preserveNewLines": false,

This sets the default formatter for .ts and ..vue files to prettier, and ensures that code is formatted as you type and when it is saved.

The settings outside of the [vue] amd [typescript] sections are applied to all document types, and change the default formatter to pretter, and ensure that at most 1 new line is maintained in the HTML template section of *.vue files.

The key change here is that by default the Vue.Volar extension is used to format .vue files, but I think prettier does a better job - especially at managing empty lines.  Prettier formatting also seems to be more configurable.

Monday, 7 October 2024

Project 1 - HowMuchIsThisMeetingCosting - under the hood

 


I am writing a blog about my journey to create a side-hustle software project brick-by-brick. I have an end goal solution in mind, but before I start on that solution I am building some projects to ‘level up’ the skills that I think I will need to build it.


For my first project, I decided to build a front-end only meeting timer  website in Vue,  using Vueitfy for UX framework and Vite as a tooling engine.You can find more information about why I chose these technologies in previous posts.


In this quick post I’ll link to the public repository for the site for anyone who wants to look 

through the code. 


Project 1 Code - initial version


You can find the source code for the initial version here Github


The best thing that can be said for this is probably that it works?  This was my first experience using Vuetify bootstrap project and it probably shows.  That being said, it gets the job done gave me enough of a boost with learning Vue that I think I’ll soon be ready to move onto something more ambitious.


To navigate the code, and for people like me who are just learning Vue, here are the major parts to look at:

  • /index.html  - this is the actual html page that loads the vue application.It runs the script in main.ts
  • src/main.ts  -  this is the main typescript application entry point for the application. It bootstraps vue and  creates the vue application, mounting the App.vue component
  • src/App.vue - this is the main 'container' for the application. It sets up the vuetify layout system and the sets up a router view that renders sub components based on the URL path
  • src/pages/index.vue  this is the 'main page' of the meeting timer application. It initializes the timer and switches the various UI components on and off
  • src/components/* these are the various UI components that handle the meeting settings form, the timer display and the focused display and the 'about' overlay
  • src/components/utilities/timerstate.ts - this module maintains the state of the timer and handles the presentation logic.

Thursday, 3 October 2024

Project 1 - HowMuchIsThisMeetingCosting - Project Bootstrap

 

I am on a journey to build a side hustle business, and I am building web projects to 'level up' my skills.

In this post  I'll walk through how I set up my first 'front-end' only project.  This will be a simple article because most of the work is taken care of my choice of tech stack - see the previous post for details 


Vuetify Project Setup Process

The setup process I used is as follows:

  • Install Pre-requisites
  • Setup and clone a new git repository
  • Run the Vuetify bootstrap project

Install pre-requisites

For this project, I needed the following pre-requisites

  •   node/npm,
  •   visual studio code,
  •   git for windows

Setup and clone a new git repository

This is standard git stuff, but it is important to add  a .gitignore file for Node/JavaScript projects when setting up the repo to ensure that local node_modules are not checked in. Here is an example:  https://github.com/github/gitignore/blob/main/Node.gitignore

Run the Vuetify bootstrap project

I followed the Vuetify setup guidance for vite and npm here: https://vuetifyjs.com/en/getting-started/installation/#installation

(I choose the option to install everything, including the store and router plugins)

Start Coding

Open VSCode and start coding. Good Luck!


Vuetify bootstrap project plugins

All in all, the Vuetify bootstrap project with Vite works well, but it is worth knowing that the setup project is opinionated and makes some configuration choices that you need to be aware of. Especially if like me you are learning Vue as you go.  In particular the bootstrap project installs a number of Vue plugins that are generally beneficial but which can trip you up if you aren't aware of them and try to debug issues using the 'vanilla' Vue way of doing things,  

The plugins that I think you you need to be most aware of are:

  • unplugin vuerouter
  • unplugin vue component
  • unplugin autoimport

Unplugin Vue Router

The unplugin (unified plugin) vue router extends the default routing behaviour of Vue by automatically generating routes based on the file structure of your project.  This is an opinionated system that can be confusing to debug if you don't understand it. I strongly recommend reading through the File Based Routing section of the plugin documentation if you enable the routing option in the bootstrap project:

(note, one side effect of this plugin seems to be that new routes are sometimes not detected by Vite's hot module replacement. If you are getting weird routing errors it is worth restarting vite to see if that fixes them)

Unplugin Vue Components

The unplugin vue components plugin adds automatic import statements for all vue components in the /components folder. This means that components that are created in this folder do not need to be explicitly imported to be used.

Unplugin AutoImport

Similarly, the autoimport plugin is a convenience plugin that ensures that it isn't necessary to explicitly import vue api references into your components.  The import statements are pre-generated and added at compile time.


Vite configuration

The main configuration file for this project is vite.config.mts file in the root folder. It is worth researching and understanding  each of the plugins that are configured here, especially if you start seeing issues that don't align with the base Vue documentation.


Documentation Links

Here is a list of documentation links that I found useful for this project: 



Hiatus - pressing the pause button

  Some career things have come up which mean that I will need to put the main themes of this blog on pause for a while.  I need to focus on ...