The best part of any idea is when it's fresh and new, and you don't yet know the limitations and restrictions.  It can be almost magical!  Oh, the customers you'll help and the money you'll make!  All you have to do first is... write a lot of code.

How much code?  Well, obviously that depends on your idea and what business you're planning on setting up.  But there's a huge amount of code you'll need and want for any SaaS business, and a lot of it you'll have to write before you can write even line one of your business logic.

Where did I come by this list?  Well, I've spent quite a few years working on SaaS businesses at a variety of stages of maturity, and I keep my ear to the ground by listening to good SaaS podcasts. I noticed that there are a lot of common tasks necessary to launch a new SaaS product, and I decided to help fix that problem by taking it all and packing it into a SaaS starter kit to help cut down on the code you need to write (and the time you need to spend) to launch your business.

Let's explore that huge list of code.

Stuff You're Gonna Need

The basics

Okay, first you're gonna need something to start from.  Unless you plan on writing everything from scratch, you'll need to set up some common frameworks to enable a modern web app to run.  On the front-end, that's something like:

Getting all these various tools set up to work together will take some time as well.  Just searching "configuring webpack for X" reveals a minefield of blog posts written for various versions of webpack and X.  Some will help, some won't, and sometimes only experimentation will reveal which is which.

Thankfully, there are tools that make a lot of this easier.  Next.js for React and Nuxt.js for Vue are just two examples, but there are many flavours of UI frameworks that can significantly reduce the setup time for the above.  Of course, now you have to learn how your UI framework works as well as your UI library, but generally that trade-off is worthwhile.

Moving on to the back-end, you're going to want a web framework.  This will largely depend on the language you're working with, but you have plenty to choose from:

This list is by no means extensive - just tracking down all the available frameworks for a single language would be an article in it's own.  But it does display the variety of choices available.  Each language and framework has its own capabilities and trade-offs, and that's something you'll have to take into account before you make your choice.  (Or after!  It's just harder to change your mind at that point.)

Development build system

Actually, let's take a step back for a second.  Sure, those are the basics, but you still need someplace to run all that code, and in a way that speeds up your evaluation of code changes.

You could run everything on your local machine, but that's rarely ideal.  For starters, your local environment is highly unlikely to resemble your production environment, and you don't want seemingly-minor differences causing problems when you deploy.  Plus, it's very hard (comparatively) to automate local environment setup, so adding anyone else to the project is bound to cause conflict, especially if they want to use an entirely different OS from you.

You have a lot of options for this, but the two easiest/most-common are:

1) Use a Virtual Machine

Virtual Machines have the advantage of being very simple to understand and work with.  If you know how to navigate your own system, you'll know how to navigate a virtual one just fine.  They're easily automated with something like Ansible, and easy to use for development with something like Vagrant.  Plus, you'll likely only need to modify a bit of your Ansible scripts or variables to turn your development deploy script into a production deploy script.

But they can be a bit heavy, as they are emulating an entire other machine.  There are good solutions to this (enabling CPU optimizations, using AMIs or other machine images to reduce deploy time, etc), but there's also an alternative.

2) Use docker

Docker containers are crazy lightweight.  Essentially, they just run the bits of the system required to run your code, as dictated by you.  Plus, a great many CI systems accept dockerfiles as input to automatically run tests and deploys of your code.  A well-built docker setup is a thing of beauty.

However, docker can be a bit confusing.  It requires learning a different mindset and tooling from working directly on a machine or virtual machine, and can lead you naturally towards more-complex solutions where a simpler one would otherwise work better for your use case.  (Hello, microservices!)

Reducing your development cycle time with watchers

A small thing that can save you a lot of time is setting watchers on your code.  These are programs that keep an eye out for changes in your code, then re-compile and restart servers so that the latest version of your code is always running when you refresh your browser.  Many of the tools you'll use will come with built-in watchers (webpack, for example), but for others, you'll need to install your own (nodemon to watch your Node.js server).

And like with anything else, there's configuration you have to do to make sure that each watcher is only watching the correct directories, that files are shared between your host system and VM/docker container in a fast method that won't trip up your watchers, etc.

Application template & UI architecture

With any luck, you'll have a design already to work with, but you still need to translate that design into an application template and common UI components and architecture.  A good CSS framework can really help here, allowing you to set up common colours and sizes that you can use across the entire project, and using component-based development can allow you to, say, create a TextInput element once, then use it across your project multiple times.  You'll also need to set up some form of menu infrastructure that allows you to enable/disable or hide/show certain menus based on user access or page location.

Logging

Proper logging can give you more and more-useful information than a slapdash setup can.  You'll want to log requests and request data, useful checkpoint information, and the usual stuff - errors, stack traces, etc.  But you also want to make sure not to log too much.  For example, you'll obviously want to omit passwords, but you should also in general omit headers, especially headers containing authentication tokens, for obvious security reasons.

Database migrations

Database schemas are part of your app as well, and that means they need to be represented as code somewhere and checked into version control.  Manually updating your production database to match your development database is amateur-hour.

So in addition to your back-end frameworks and your front-end frameworks, you'll need a database migration framework, and you'll need to write migrations for it.

Users

Users are the fundamental primitive of a SaaS application, and there's a common set of interactions you'll require: sign-up, login, logout, edit profile, etc.  But sitting underneath all that is a bit of a contentious topic: user authentication.

There are a bunch of ways to do user authentication, but most of them are wrong and will end up leaving you with security vulnerabilities.  JWTs are popular and can be secured, but you need to follow some best practices:

  • Don't store JWTs in localStorage, since any JS that runs on your page can access them, and if you get hit with a cross-site scripting attack, they can export your tokens en masse.
  • Store JWTs in secure, HTTPS-only cookies.
  • Include a global version code in your JWTs so that you can instantly invalidate all JWTs every issued.
  • Include a user version code in your JWTs so that a user can instantly invalidate all JWTs ever issued for them specifically.  This is useful to include a "log out all devices" option for users who may have lost a device or had their account compromised.
  • Send a Cross-Site Request Forgery token with every request as a javascript-injected header, and make sure that token matches one you've stored for the user on login.

You'll notice a lot of these practices are "in case of a security breach", and you'd hope that if you did everything correctly, they'd be unnecessary.  However, that's a fantasy and should be treated as such.  No site is 100% secure and bug-free, and yours won't be either.  Instead, you need to work in layers, so that if any one layer of security fails, there are still other layers and countermeasures in place.

Form validation

When users sign up, log in, and really all throughout your app, they'll be filling out and submitting forms.  These forms will need to be validated for the appropriate data, preferably on both the front-end (before the data is sent to the server, to provide the best experience to the user) and the back-end (to ensure no junk data is saved to the database).  If your back-end isn't in JavaScript, you'll need validation libraries for both languages that have the same semantics.

Transactional email

Transactional email is the email you send when certain events happen for your users.  These can be lifecycle events, like welcome emails, "trial about to expire" emails, etc, or service-related emails like email address confirmation emails, password reset emails, notifications about your service, etc.

You'll need to find and configure a decent mailer module, and usually perform some DNS configuration at your mail service host's instruction.  Some mailer modules will come with template capabilities built-in, while others will leave you to install your own.

Subscriptions/Payments

Getting paid is why most people are going to start a SaaS in the first place, so processing payments and subscriptions is mightily important.  Choosing and setting up an account with a payments provider is up to individual preference, but Stripe offers probably the best API and developer experience out there, while PayPal is usually the most-requested provider of choice from users.  It's likely that you'll want to offer multiple ways to pay through multiple providers, just to ensure that no potential customer is left behind.

If you offer subscriptions, you'll want to allow users to choose between a monthly billing cycle and an annual one.  Annual billing is a great way for dedicated users to save money, while also offering you the benefits of higher LTV and getting you the money up-front, increasing your liquidity.

If you have multiple levels of plans, you'll need to implement the ability for users to change between those levels, usually offering a prorated fee for the month of transition.

Though it's definitely not the "happy path", you'll need to offer users the ability to cancel subscriptions.  You shouldn't add extra friction to this, since some users will just be cancelling temporarily, and you want to leave a good impression on them, but it's important to try to capture the reason they're leaving, so you can improve your service.

Production deploy system

Once you've fully-developed your fancy new SaaS, you're going to need to put it up on the web for people to interact with, and for that, you're going to need a deploy system.  Even if that system is largely manual, you're going to want defined, repeatable, documented steps that ensure that deploys go off without incident.

You're going to want to cover the following bases, at a minimum:

  • Ensure server is reachable
  • Ensure server is set up correctly (correct runtime libraries installed, etc.)
  • Update code
  • Run DB migrations
  • Ensure front-end UI code is not cached in user's browser (update ETags, etc)

There are a whole lot more things you can do to ensure a safe and clean deploy, but this list is at least a good starting place.

Production backups

Much like how we discussed security in layers above, backups of production data are another layer of defence in case something goes wrong.  If you're still using manual processes to alter user data, it can be very easy for a slip of the keys to accidentally alter or delete the wrong user's data.  And if you're using automated processes, it's usually a lot harder to make those simple mistakes, but more complex mistakes can make it very easy to edit or delete huge swathes of user data.  Proper backups will one day save your bacon, bet on it.

What makes a proper backup, then?  That's a whole topic on its own, but you should start with:

  • Complete: Don't just backup the database - if the user uploads files, those should be backed up as well.
  • Regular: Backups should happen on a schedule, ideally daily or more, for more-volatile data.
  • Retained: You'll want to keep your backups around for a while, though you might want to set up a schedule for longer-retained backups.  (i.e. Daily backups retained for 30 days, weekly backups retained for 3 months, monthly backups retained for 1 year.)
  • Secure: Your backups should be kept with the same level of security as your data.  If your data is encrypted at rest, your backups should be as well.  Make sure to keep your encryption keys secure.  If you lose those keys, you lose the backup.
  • Tested: A backup that hasn't been tested is not a backup.  You don't want to find out that your backup process doesn't work (or stopped working) when you need to restore critical data.  There should be an automated test process that runs after backups are created.

If you're lucky, your hosting platform will offer some level of database backup as a service, which will save you a lot of time and effort setting up.  It likely won't cover 100% of your needs, but it will get you a lot closer than starting from scratch.

Stuff You're Gonna Want

Okay!  That'll get you off the ground, but once you start seeing any success at all, you're going to start wanting something a little more... robust.  Eventually, manually editing the database is going to get tedious (not to mention dangerous), and users will start asking the same questions over and over.  You're going to have to slow down on development related to your core business and implement a bunch more supporting features.

Admin console

You can edit and delete users directly from the database, sure, but all it takes is one time forgetting to add a WHERE or LIMIT clause to a statement to make you long for a proper administration console.  (And backups.  You set up backups, right?)

An admin console is also a great place for dashboards, user statistics, summaries, metrics, etc.  Your admin console can become your one-stop-shop for running your SaaS.

Documentation

Documentation can serve multiple purposes.  Primarily, it's for user education, but conveniently, this is user education you don't have to do manually.  Think about it like automated customer support - a user that answer their question from your documentation is a user that doesn't email you.

If your documentation is publicly available, it can also help users make purchasing decisions.  By answering questions about your service openly and up-front, you can let users more-easily determine if your service will work for them, as well as reassure them about your transparency.

Public documentation also helps with SEO, since your keywords will likely naturally come up frequently on your documentation pages.

Billing history

Once you have a sufficient number or sufficiently large customers, you'll likely start getting requests around tax time for their billing history.  Your payment system will keep track of payments for you, and many of them will be able to generate invoices from their web interface that you can send to customers who request it.

That might hold you for a while, but eventually, you'll want this functionality built into your system, so clients can self-serve, and your customer support team can focus on more-important issues.

Stuff That's Gonna Make Your Life A Lot Easier

Making the right decisions early on and as your service grows can have compounding benefits, but frequently, it's difficult to find time to devote to tasks that aren't seen as critical.  Still, if you can make the time to invest in them, it can pay off for you and your users as well.

Pause subscriptions & credit

Especially now, when people are trying to cut costs in both their lives and businesses, the ability to pause a subscription instead of cancel it outright can mean the difference between saving a customer and losing them.  Similarly, the ability to credit customers some free time or usage on your service can aid in retention, especially if something goes wrong and you want to make it up to them.

User ID obfuscation

When displaying publicly-visible auto-incrementing IDs (such as user IDs), it can be a good idea to obfuscate what that number actually is.  This prevents competitors and skittish customers from identifying how much usage your service has seen so far.  A great library for this is Hashids, which has many compatible implementations across many languages.

Limited number of development languages

The fewer languages your app uses, the less common code that you'll have to duplicate between the various services and projects you require.  Some are going to be unavoidable, such as JavaScript if you have a web app with any serious browser interactions, Swift for iOS, and Java/Kotlin for Android.  Web apps, however, offer a truly terrifying number of languages you can choose for server code: PHP, Ruby, JavaScript, Typescript, Go, Rust, Java, Python, Perl, Scala, Erlang, and even C# and C++.  In a microservices environment, it can be tempting to use a variety of languages for your different services, but that means redeveloping and maintaining common libraries for every new language you want to include.

In extreme situations, you can limit yourself to just one language, even across multiple disparate platforms.  JavaScript can do front-end and back-end web development, desktop development through Electron, and mobile development through Cordova.  There are definite trade-offs for going this route, but for a smaller studio, this opens up a multi-platform strategy on a limited budget.

Linters

Linters like ESLint, RuboCop, and Flake8 can make a marked improvement in your code.  They can catch stylistic errors long before they make it into production, and many stylistic errors are really just shortcomings of your chosen language, where hard-to-find bugs breed and propagate.

Monorepo

Monorepos are great!  They're especially great if you're just starting your SaaS, as they're far simpler than trying to work with multiple repositories when managing dependencies, figuring out code re-use, and ensuring that all the correct code is committed before deploys go out.

Everyone's situation is different, of course, and it may make sense in your case to go with multiple repositories, or even one day switch to such a strategy, but when you're starting out, you want to limit the complexity of your project as much as you can, and the monorepo strategy will definitely pay off in this regard.

User impersonation

Being able to log in as your users from your Admin Console can help immensely when trying to sort out customer service issues.  Instead of having several back-and-forth "what do you see now?" emails, you can just log in as them and find out.  There are a lot of things to consider when writing a user impersonation feature, however: Do you require special access to impersonate users?  Do you require the user's permission to impersonate them?  Are actions taken while impersonated logged?  Can you even take actions when impersonating, or view only?  How do you indicate that you are impersonating a user (vs. logged in under your own account)?

These aren't the only considerations, but ideally it's enough to make the point that there's a lot more to user impersonation than simply changing a token ID.

Improved production deployments

Once you start getting enough customers with sufficient expectations, you'll have to make modifications to your deploy process for increased reliability and flexibility:

  • Updating in-place won't work forever.  Eventually, switching to blue/green deploys or even something as simple as displaying a maintenance mode page while you update will be necessary to keep people from interacting with the system while performing significant changes.
  • If you have a complex SPA, you'll want to be able to inform users when you've made an update that requires reloading that code.  Tracking version numbers both in your UI code and on the server will allow you to pop up a notification, allowing the user to save their work and then reload.
  • Ideally, you should be using a bug tracking service.  If you also send your source maps to them when performing a deploy, they can provide even better error messages when UI errors occur.
  • Serving your UI JavaScript from your server is simple and easy, but users appreciate fast, and your job is to do the hard work so that users have a good time.  A relatively easy way to speed up your user's experience is to upload your UI JavaScript on release to a CDN.  This is a one-time change you need to make that pays dividends for your users going forwards.
  • You'll likely be manually checking that releases go as expected, but automated smoke tests that run on every deploy are a better way to catch issues that might otherwise slip by you when you're tired, distracted, or in a hurry.

What's the alternative?

If you don't want to start from an empty folder and write all this code yourself, you should consider using a SaaS starter kit, and it just so happens that you're reading the blog for one right now!  With Nodewood, you can get started writing business logic today, saving weeks or even months of development time.

Nodewood starts you off with a full working web app, with a Vue front-end and Express back-end, built entirely from JavaScript.  Form validation, testing, user authentication and management, subscription/billing are all built-in, alongside a sleek and customizable application theme with an easy-to-extend admin console.

Save critical development time and build your next app with Nodewood!