Every time I started a new SaaS project, I spent the first 3 days building the same things.
Auth. Billing. Database setup. Deployment config. Every. Single. Time.
So I stopped. I packaged everything into a production-ready boilerplate and I'm never doing it again.
Here's exactly what I built and how it works.
The Stack
- Backend: Node.js + Express
- Frontend: Next.js 14 + Tailwind CSS
- Database: PostgreSQL + Prisma ORM
- Auth: JWT (access + refresh tokens) + Google OAuth
- Billing: Stripe subscriptions
- Deployment: Railway (backend) + Vercel (frontend)
Auth — Done Right
Most tutorials show you JWT auth but skip the details that matter in production:
- Access tokens expire in 7 days
- Refresh tokens are stored in the database and rotated on every use — if a token is stolen and used, the original is invalidated
- Google OAuth creates or links accounts automatically
- bcrypt with 12 salt rounds for password hashing
- Rate limiting on auth routes (10 requests per 15 minutes) to block brute force attacks
// Refresh token rotation — prevents reuse after theft
const rotateRefreshToken = async (rawRefresh) => {
const stored = await prisma.refreshToken.findUnique({
where: { token: rawRefresh },
include: { user: true },
});
if (!stored || stored.expiresAt < new Date()) return null;
// Delete old token before issuing new one
await prisma.refreshToken.delete({ where: { id: stored.id } });
return issueTokens(stored.user);
};
Stripe Billing — The Parts Nobody Explains
Stripe docs are good but they don't show you how to wire everything together in a real app. Here's what I set up:
Checkout Sessions — user clicks upgrade, gets redirected to Stripe, comes back subscribed
Customer Portal — one line of code lets users manage, upgrade, downgrade, or cancel themselves
Webhooks — subscription changes sync to your database automatically
Plan-gated routes — lock API endpoints behind a minimum plan level
// Lock a route behind a plan — one line
router.get('/analytics', requireAuth, requirePlan('STARTER'), handler);
The webhook handler covers all the important events:
customer.subscription.created — new subscriber
customer.subscription.updated — plan change
customer.subscription.deleted — cancellation, auto-downgrades to FREE
Database Schema
Three models — kept it simple:
User — email, password hash, Google ID, Stripe customer ID
Subscription — plan, status, billing period, Stripe subscription ID
RefreshToken — token, user, expiry (enables token rotation and logout-all-devices)
Deployment
Both services have config files included:
railway.json for the backend
Vercel auto-detects Next.js
The backend handles graceful shutdown on SIGTERM — Railway and Render send this signal on every deploy, so in-flight requests finish cleanly before the process exits.
Live Demo
You can see the full frontend running here:
https://saas-boilerplate-hsh5xrikl-johnolee15-9278s-projects.vercel.app
Landing page, auth, dashboard, billing — all working.
Get the Code
I packaged this up and listed it on Gumroad for $49:
👉 https://looneyjoons.gumroad.com/l/nodejs-saas-boilerplate
Includes the full backend + frontend source, README with step-by-step setup, and .env.example with every variable documented.
If you have any questions about the stack or implementation drop them in the comments — happy to help.
Top comments (0)