Наръчникът на SaaS - Как да изградите своя първи продукт като софтуер като услуга стъпка по стъпка

В това обширно описание ще разгледам как всички основни парчета се събраха за първия SaaS, който някога стартирах.

От внедряването на favicon до внедряването в облачна платформа, ще споделя всичко, което научих. Също така ще споделя обширни кодови фрагменти, най-добри практики, уроци, ръководства и ключови ресурси.

Надявам се, че нещо тук ще ви бъде полезно. Благодаря за четенето. ❤️

Съдържание

  • Въведение
  • Намиране на идеи
  • Стека
  • Репо
    • Започнете разработването на пълен стек локално
  • Клиент
    • Npm Script
    • Променливи на околната среда
    • Webpack & Babel
    • Уеб изпълнение
    • Диференциално сервиране
    • Шрифтове
    • Икони
    • Favicon
    • API разговори
    • Тествайте производството на локално ниво
    • Сигурност
  • Дизайн
    • Модулна везна
    • Цветове
    • Нулиране на CSS
    • Практика за стилизиране
    • Оформление
  • Сървър
    • Файлова структура
    • Npm Script
    • База данни
    • Създаване на таблични схеми
    • Отпадане на база данни
    • Crafting SQL Queries
    • Redis
    • Error Handling & Logging
    • Permalink for URL Sharing
  • User Authentication System
  • Email
  • Tenancy
  • Domain Name
    • How to get one of those app.example.com
  • Deployment
    • Deploy Nodejs
    • Deploy Postgresql
    • Setup schemas in production database
    • Deploy Redis
    • File Storage
    • Deploy New Changes in Back-end
  • Hosting Your SPA
    • Deploy New Changes in Front-end
  • Rich-text Editor
  • CORS
  • Payment & Subscription
  • Landing Page
  • Terms and Conditions
  • Marketing
  • Well-being

Introduction

I switched careers to web development back in 2013. I did it for two reasons.

First, I noticed I could get lost in building customer-facing products among all the colors and endless possibilities for interactivity. So while being reminded of the trite "Find a job you enjoy doing, and you will never have to work a day in your life", I thought "Why not make this a job?"

And second, I wanted to make something of myself, having spent my teenage years inspired by Web 2.0 (Digg.com circa 2005 opened the world for me!). The plan was to work on the latter while working in the former.

Turns out, though, that the job and the 'JavaScript fatigue' ensued and wholly consumed me. It also didn't help that I was reckless in my pursuit of my ambition, after being influenced by the rhetoric from 'Silicon Valley'. I read Paul Graham's Hackers & Painters and Peter Thiel's Zero to One. I thought, I'm properly fired up! I'm hustling. I can do this too!

But nope, I couldn't. At least not alone. I was always beat after work. I couldn't find a team that shared my dreams and values.

So meanwhile, I rinsed and repeated less than half-baked projects in my free time. I was chronically anxious and depressed. I mellowed out as the years went by. And I began to cultivate a personal philosophy on entrepreneurship and technology that aligned better with my personality and life circumstances – until September 2019.

The fog in the path ahead finally cleared up. I got pretty good at it – the job then became less taxing, and I'd reined in my 'Javascript fatigue'. For the longest time, I had the mental energy, time, and the mindset that allowed me to see through a side project. And that time, I started small. I believed I had this!

I was wrong.

Since I had been a front-end developer for my entire career, I could only go as far as naming the things that I imagined I would need – a 'server', a 'database', an 'authentication' system, a 'host', a 'domain name', but how... where... and what...I..I don..I don't even... ?

Now, I knew my life would have been easier if I'd decided to use one of those abstract tools like 'create-react-app', 'firebase SDK', 'ORM', and 'one-click-deployment' services. The ode of 'Don't reinvent the wheel. Iterate fast'.

But there were a few qualifications I wanted my decisions to meet:

  • No vendor lock-in — This ruled out using the Firebase SDK all over my codebase. This included 'create-react-app', because ejecting it forced me to inherit and maintain its massive tooling infrastructure.
  • Simple & Minimalistic — Cut having to learn new opinionated syntax and patterns. This ruled out 1) Project generators that output complex architecture and layers of boilerplate codes, 2) Using third-party libraries such as 'knex.js' or the 'sequelize' ORM.
  • Pay-as-you-need — I wanted to keep my operating cost proportional to the usage level. This ruled out services such as 'one-click-deployment'.

To be fair, I had the following things going for me:

  • I was building a simple SaaS.
  • I was not anxious to scale, dominate, disrupt etc.
  • I was still holding my day job.
  • I had accepted my odds of failure. ?

Also keep in mind that:

  • This was a one-man show—design, development, maintenance, marketing, etc.
  • I'm not a 10x rockstar full-stack programmer.

Most importantly, I wanted to follow through with a guiding principle: Building things responsibly. Although, unsurprisingly, doing so had had a significant impact on my development speed, and it forced me to clarify my motivations:

  • If I had to ship something as soon as possible, unless it was a matter of life and death, then I probably wasn't solving a unique and hard problem. In that case—assuming I was still at my day job and had zero debt— What was the rush?
  • And second-guessing from the ethical perspective: Was it even a problem that needed solving? What would be the second-order consequences if I solved it? Could my good intentions be better directed elsewhere?

So what follows in this article is everything I've learned while developing the first project I ever launched called Sametable that helps managing your work in spreadsheets.

Let's get to it.

Finding Ideas

Well, first of all, you need to know what you want to build. I used to lose sleep over this, thinking about and remixing ideas, hoping for eureka moments, until I started to look inward:

  • Build things that solve problems that you encounter and piss you off frequently.
  • Solve the so-called 'pain points' or 'frictions'. Go outside, don't stop listening to people and learn from them.
  • Be an expert in your domain. Feel its pains. Maybe solve one of them. Seems to me lots of founders founded company related to their domain on which they have built their career and social network.

The Stack

How your stack looks will depend on how you want to render your application. Here is a comprehensive discussion about that, but in a nutshell:

  • Client-side rendering(CSR); SPA; JSON APIs

    This is perhaps the most popular approach. It's great for building interactive web applications. But be aware of its downsides and steps to mitigate them. This is the approach I took, so we will talk about it in a lot of detail.

  • Hybrid CSR; Both client-side and server-side rendering(SSR)

    With this approach, you still build your SPA. But when a user requests your app, for example, the homepage, you render the homepage's component into its static HTML in your server and serve it to the user. Then at the user's browser, hydration will happen so the whole thing becomes the intended SPA.

The main benefits of this approach are that you get good SEO and users can see your stuff sooner (faster 'First Meaningful Paint').

But there are downsides too. Apart from the extra maintenance costs, we will have to download the same payload twice—First, the HTML, and second, its Javascript counterpart for the 'hydration' which will exert significant work on the browser's main thread. This prolongs the 'First time to interactive', and hence diminishes the benefits gained from a faster 'First meaningful paint'.

The technologies that are adopted for this approach are NextJs, NuxtJs, and GatsbyJs.

  • Server-side rendering and 'sprinkle ✨ it with Javascript'

    — This was the old-school way of building on the web!—Use PHP to build your templates with data in your server, then bind events handlers to the DOM with jQuery in the browser. This approach might have been ill-suited to build the increasingly complex apps that businesses have asked for on the web, but some technologies have emerged to warrant a reconsideration:

    • //stimulusjs.org/
    • //github.com/turbolinks/turbolinks
    • //github.com/phoenixframework/phoenix_live_view
    • For more, check out this twitter thread

To be honest, if I was more patient with myself, I would have gone down this path. This approach is making a comeback in light of the excess of Javascript on this modern web.

The bottom line is: Pick any approach you are already proficient with. But be mindful of the associated downsides, and try to mitigate them before shipping to your users.

With that, here is the boring stack of Sametable:

Front-end

  • Webpack, Babel
  • Preact

Back-end

  • Node — API server with ExpressJS
  • Postgresql — Database
  • Redis — Store users' session data and cache queries' results.

Hosting

  • Google Cloud Platform — GAE for hosting Nodejs, GCE for hosting Redis.
  • Firebase — For hosting my SPA.
Sametable architecture

Repo

//github.com/kilgarenone/boileroom

This repo contains the structure I'm using to develop my SaaS. I have one folder for the client stuff, and another for the server stuff:

- client - src - components - index.html - index.js - package.json - webpack.config.js -.env -.env.development - server - server.js - package.json - .env - package.json - .gitignore - .eslintrc.js - .prettierrc.js - .stylelintrc.js 

The file structure always aims to be flat, cohesive, and as linear to navigate as possible. Each 'component' is self-contained within a folder with all its constituent files (html|css|js). For example, in a 'Login' route folder:

- client - src - routes - Login - Login.js - Login.scss - Login.redux.js 

I learned this from the Angular2 style guide which has a lot of other good stuff you can take away. Highly recommended.

Start Full-stack Development Locally

The package.json at the root has a npm script that I will run to boot up both my client and server to begin my local development:

"scripts": { "client": "cd client && npm run dev", "server": "cd server && npm run dev", "dev": "npm-run-all --parallel server client" } 

Run the following in a terminal at your project's root:

npm run dev 

Client

- client - src - components - index.html - index.js - package.json - webpack.config.js -.env -.env.development 

The file structure of the 'client' is quite like that of the 'create-react-app'. The meat of your application code is inside the src folder in which there is a components folder for your functional React components; index.html is your custom template provided to the html-webpack-plugin; index.js is a file as a entry point to Webpack.

Note: I have since restructured my build environment to achieve differential serving. Webpack and babel have been organized differently, and npm scripts changed a bit. Everything else remains the same.

Npm Script(Client)

The client's package.json file has two most important npm scripts: 1) dev to start development, 2) build to bundle for production.

"scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server", "build": "cross-env NODE_ENV=production node_modules/.bin/webpack" } 

Environment Variables

It's a good practice to have a .env file where you define your sensitive values such as API keys and database credentials:

SQL_PASSWORD=admin STRIPE_API_KEY=1234567890 

A library called dotenv is usually used to load these variables into our application code for consumption. However, in the context of Webpack, we will use dotenv_webpack to do that during compile and build time as shown here. The variables will then be accessible in the process.env object in your codebase:

// payment.jsx if (process.env.STRIPE_API_KEY) { // do stuff } 

Webpack & Babel

Webpack is used to lump all my UI components and its dependencies (npm libraries, files like images, fonts, SVG) into appropriate files like .js, .css, .png files. During the bundling, Webpack will run through my babel config, and, if necessary, transpiles the Javascript I have written to an older version(e.g. es5) to support my targeted browsers.

When Webpack has done its job, it will have generated one (or several) .js and .css files. Then by using a webpack plugin called 'html-webpack-plugin', references to those JS and CSS files are automatically (default behaviour) injected respectively as and in your index.html. Then when a user requests your app in a browser, the 'index.html' is fetched and parsed. When it sees and , it will fetch and execute the referenced assets, and finally your app is rendered(i.e. client-side rendering) in all its glories to the user.

If you are new to Webpack/Babel, I'd suggest learning them from their first principles to slowly build up your configuration instead of copy/pasting bits from the web. Nothing wrong with that, but I find it makes more sense doing it once I have the mental models of how things work.

I wrote about the basics here:

  • Webpack

Once I understood the basics, I started referring to this resource for more advanced configuration.

  • Babel

Web Performance

To put it simply, a web app that performs well is good for your users and business.

Although web perf is a huge subject that's well documented, I would like to talk about few of the most impactful things I do for web perf (apart from optimizing the images which can account for over 50% of a page's weight).

Critical rendering path

The goal of optimizing for the 'critical rendering path' in your page is to have it rendered and be interactive the soonest possible to your users. Let's do that.

We mentioned before that 'html-webpack-plugin' automatically injects references of all Webpack-generated .js and .css files for us in our index.html. But we don't want to do that now to have full control over their placement and applying the resource hints, both of which are a factor in how efficient a browser discovers and downloads them as chronicled in this article.

Now, there are Webpack plugins that seem to help us in this respect, but:

  • There was no intuitive way to control the ordering of my . Well, there is this method, but how about ordering among my too?
  • There was no plugin that preload my CSS the way I wanted as we will see later. Well, there is this (no control over attributes), this (same), and this (no clear support for MiniCssExtractPlugin).

Even if I could've somehow hack them all together, I would have decided against it in a heartbeat if I'd known I could do it in an intuitive and controlled way. And I did.

So go ahead and disable the auto-injection:

// webpack.production.js plugins: [ new HtmlWebpackPlugin({ template: settings.templatePath, filename: "index.html", inject: false, // we will inject ourselves mode: process.env.NODE_ENV, }), ]; 

And knowing that we can grab Webpack-generated assets from the htmlWebpackPlugin.files object inside our index.html:

// example of what you would see if you // console.log(htmlWebpackPlugin.files) { "publicPath": "/", "js": [ "/js/runtime.a201e1a.js", "/vendors~app.d8e8c.js", "/app.f8fb511.js", "/components.3811eb.js" ], "css": ["/app.5597.css", "/components.b49d382.css"] } 

We inject our assets in index.html ourselves:

Original text