All that we need to run a SaaS company explained as I am 5
Breaking Down SaaS Essentials: A Simple Guide for Beginners using React
SaaS stands for “Software as a Service”. It has become the lowest entry barriers approach for creating a business. However, low-entry barriers do not mean success for sure. It means you can launch quickly and get feedback quickly. Let me guide you on how to build one as fast as possible. In this article I do not assume much no-code development (with one exception), and I will rely more on JavaScript based code. I will also assume you have found a “Service” that address an issue or pain, this part it is on you.
This article is mostly for people who know how to create a service (e.g. an application in Python), and don’t know much how to deploy it to the real world. I limited the discussion to React but there are frameworks such as Flask, Django, etc.
To have a Saas you need (at least) the following elements:
a service
a landing page
a system to manage users
a payment system
As said before I assume here you have a service-software already and it is running on a AWS or private machine, now we have to bring it to the people. I will describe briefly the 3 components used around your service, and then I will go through a simple React code.
Landing page
https://saaslandingpage.com/
A landing page is the business card of your SaaS. It is the first thing people will see and maybe decide whether to become a user, paid or not, of your service. They generally follow the same structure:
There is a menu on top. A title and subtitle where a value proposition is reported, and often a button with a call to action. Later some features, testimonials and link to pay/register.
From a technical point of view, landing pages are designed in Figma or other design tools. The result can be a simple parallax html file or a folder with a React script with its related html page, as these. There are many tamplates available online, some free, some behind a paywall. I would generally discourage landing pages developed behind more advanced frameworks and it will be harder to personalize later the other components, unless you do everything within their framework.
At the bottom, there should be the pricing menu connected to registering the user. Or alternatively, this is where your will redirect the user clicking the bottom on top.
System to manage users
Users are the main actors here. We need to keep track of them properly.
As said on top, most of the code introduced in this page is in React or other Javascript components. React is a popular open-source JavaScript library developed by Facebook used for building user interfaces, particularly for single-page applications where a dynamic and responsive UI is crucial.
To handle the task of registering users or allowing them to access the service. Most of the Saas uses Auth.js, a javascript that would work well keeping all user’s ID and their passwords in a PostgreSQL database through a suitable connector. This would still be the most preferable way as the user contacts will remain in our hands. However, some programming is needed. As I think connecting our service to a payment system and embedding it to our landing page is complicated enough if you are new to it, in the code reported in this page I resort to other solutions less cody. Indeed, there are several tools which also offer free plans with some limitations. The most used currently are:
I personally found AWS cognito not very friendly to use, and I prefer to use Supabase. Therefore, the code below assumes to store users in the Supabase platform.
Payment system
I assume that we are creating a SaaS as a business. Hence, apart some functionalities for free users, the more complete service is behind a paywall for paying users. Payment can be handled in different ways. Defining from scratch a credit card payment system is not trivial, and like the case of user management we can resort to fexisting platforms as:
Paypal is probably the most known and old pay,emt tool. Nevertheless, the current standards adopted by SaaS developers are Stripe and LemonSqueezy due to their flexibility and practicality.
The Lemonsqueezy is a wrap around Stripe which offers some tax handling with some additional fees. In the code below I assume you are fine managing your own taxes, and therefore it is enough to collect payment with Stripe.
If you use the Stripe API as shown in the code below, a user will be redirected to a page handling payments managed by Stripe which will ask the user to pay a fee defined by you in the code below. The payment will then cash-out to your bank account minus the Stripe fee.
The code
The folder with all the code for a Saas wrap that we have described would look something like this:
- .env # Environment variables (like API keys)
- package.json # Dependencies, scripts, etc.
- server.js # Main server entry point
- api/ # All API routes for server-side logic
- create-checkout-session.js
- other-api-routes.js
- src/ # React application (frontend)
- components/ # React components
- pages/ # React pages (e.g., SuccessPage.js)
- App.js # Main React app
- index.js # React entry point
- public/ # Static files (html pages, images, favicon, etc.)
- build/ # Resulting compiled app
React scripts are generally tested in localhost, then compiled using the command
npm run build
where the resulting html with compiled javascript will be in a folder called “build”.
Why structuring the project this way is beneficial?
We put the frontend separated by the backend and the entry point for the backend (server.js) in the main folder together with a hidden file with the environmental variables as the Stipe and Supabase API key.
The frontend can be deployed to a separate service (like Netlify, Vercel, or GitHub Pages) that serves static files optimized for the browser.
The backend (server + API) can be deployed to Node.js environments like Heroku, AWS Lambda, or DigitalOcean, where you run server-side code.
With this separation, you have the flexibility to scale, debug, or update the client and server independently.
Let’s now proceed with creating frontend and backend code. With the following steps:
Create a React app (you should have NodeJs at least version 18) and enter its folder to install the libraries listed below:
npx create-react-app saas-frontend
cd saas-frontend
2. Create a Supabase Project:
npm install @supabase/supabase-js
Go to Supabase and sign in.
Create a new project and note down your Supabase URL and API key.
Go to the Authentication section in the Supabase dashboard.
Set up authentication methods (e.g., email and password).
Create
src/supabaseClient.js
:
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'https://your-supabase-url.supabase.co';
const supabaseKey = 'your-supabase-api-key';
const supabase = createClient(supabaseUrl, supabaseKey);
export default supabase;
The scripts supabaseClient.js and Stripe.js could be inside your main App.js, but for keeping configuration and setup logic is better to ensures that each file has a single responsibility.
3. Obtaine the Stripe variables and set the libraries
Assure you have the Stripe library installed as we will use this for payments
npm install express stripe cors body-parser @stripe/stripe-js @stripe/react-stripe-js
Go to Stripe and sign up.
Create a product and price in the Stripe Dashboard.
In the Stripe Dashboard, navigate to Developers > API keys and copy your Publishable Key and Secret Key.
Create
src/Stripe.js
:
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe('your-stripe-publishable-key');
export default stripePromise;
And don’t forget to put the even more secret variables in a .env
file in the root of your project:
REACT_APP_SUPABASE_URL=https://your-supabase-url.supabase.co
REACT_APP_SUPABASE_KEY=your-supabase-api-key
STRIPE_SECRET_KEY=your-stripe-secret-key
CLIENT_URL=http://localhost:3000
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
4. Write the main App.js script like this (this will be the frontend for now)
// src/App.js
import React, { useState } from 'react';
import supabase from './supabaseClient';
import stripePromise from './Stripe';
function App() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
const handleSignUp = async () => {
try {
// Temporarily sign up the user without setting them as active
const { data, error } = await supabase.auth.signUp({ email, password });
console.log('SignUp Response:', { data, error });
if (error) throw error;
// Proceed to payment before setting user
await handleCheckout(data.user);
} catch (error) {
setError(error.message);
}
};
const handleLogin = async () => {
try {
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
console.log('Login Response:', { data, error });
if (error) throw error;
setUser(data.user);
} catch (error) {
setError(error.message);
}
};
const handleCheckout = async (registeredUser) => {
try {
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
body: JSON.stringify({ email: registeredUser.email }),
headers: { 'Content-Type': 'application/json' },
});
const { id } = await response.json();
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({ sessionId: id });
if (error) throw error;
// If payment is successful, set the user
setUser(registeredUser);
} catch (error) {
setError(error.message);
// Optionally log the error or notify the user about payment failure
console.error('Payment failed:', error.message);
}
};
const handleLogout = async () => {
const { error } = await supabase.auth.signOut();
if (error) setError(error.message);
else setUser(null);
};
return (
<div className="App">
<h1>My SaaS App</h1>
{user ? (
<>
<p>Welcome, {user.email}</p>
<button onClick={handleLogout}>Logout</button>
</>
) : (
<>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button onClick={handleSignUp}>Sign Up</button>
<button onClick={handleLogin}>Login</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</>
)}
</div>
);
}
export default App;
this should also be related to an index.js file as:
// src/App.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
5. Create the API for the checkout on Stripe (this will be the backend for now)
// api/create-checkout-session.js
const Stripe = require('stripe');
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
module.exports = async (req, res) => {
if (req.method === 'POST') {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price: 'price_1', // 1 month price change your code here
quantity: 1,
},
],
mode: 'payment', // or 'subscription' if it's a recurring charge
success_url: `${process.env.CLIENT_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.CLIENT_URL}`,
});
res.json({ id: session.id });
} catch (error) {
console.error('Error creating checkout session:', error.message);
res.status(500).json({ error: error.message });
}
} else {
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
};
Prepare the server to be run independently. This script file will serve as the backend endpoint that handles the creation of a checkout session with Stripe. It will also manage requests to the API endpoints.
// server.js
const express = require('express');
const cors = require('cors'); //const bodyParser = require('body-parser');
const createCheckoutSession = require('./api/create-checkout-session');
const app = express();
// Enable CORS for all routes
app.use(cors({
origin: 'http://localhost:3000', // Update this if you're deploying elsewhere
}));
// Your other middleware and routes here
app.use(express.json());
// API endpoint
app.post('/api/create-checkout-session', createCheckoutSession);
const PORT = process.env.PORT || 5000; // Change port if needed
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
If tested locally this should be run as
node api/server.js
If we are deploying the app on a service like Vercel, we need to move the server.js script inside the backendfolder (/api), and type in our command line
npm i -g vercel
vercel
6. Connect your React code to the landing page
In the index.js we referred to our app as “root”, and we are going to invoke it as well in the html code of the landing page also as root inside a <div> tag. For example, assuming the minimalistic landing page as this just simpling saying “Welcome to My App” and then including the app:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My SaaS App</title>
</head>
<body>
<div id="splash-screen">
<!-- Your splash screen content -->
<h1>Welcome to My App</h1>
</div>
<div id="root">
</div> <!-- React will mount here -->
</body>
</html>
In conclusion, building a SaaS application may seem daunting at first, but with the right tools and a step-by-step approach, it becomes a manageable task. This code is a bit rudimental. It is complete but should be seen as a starting point. For example, I assumed the subscription fee is only one, while usually, people set different prices for monthly and annual subscriptions.
Nevertheless, by breaking down the essentials — creating a landing page, managing users with a platform like Supabase, and handling payments with Stripe — you’ve got a solid foundation for your SaaS business. The combination of React for your frontend and Node.js for your backend ensures a modern, scalable, and flexible setup. Remember, the journey doesn’t stop here; continuously iterate, gather user feedback, and improve your service to meet your customers’ needs. With determination and the right strategies, you’re well on your way to running a successful SaaS company!