March 7, 2025 • 5 min read
🛠️ Building a SaaS with Next.js – Part 1: Project Setup & Authentication
This is the first part of a multi-step guide on building a SaaS application using Next.js. In this part, we'll cover:
✅ Setting up a Next.js project with Bun
✅ Configuring a PostgreSQL database with Prisma
✅ Adding authentication using Auth.js with GitHub OAuth
✅ Creating a simple sign-in and sign-out UI
By the end of this part, you'll have a fully functional authentication system ready to integrate with other SaaS features like payments, subscriptions, and user dashboards.
🚀 Step 1: Initialize a Next.js Project
Let’s kick things off by creating a new Next.js app:
bunx create-next-app@latest saas && cd saas
This sets up the project and moves you into the newly created saas
directory.
📦 Step 2: Install Essential Dependencies
1️⃣ Add Prisma (Database ORM)
Prisma helps us interact with the database effortlessly. Install it with:
bun add prisma
Then, initialize Prisma:
bunx prisma init
This generates a prisma
directory, including:
schema.prisma
– Defines the database structure..env
– Stores your database connection string.
2️⃣ Add ShadCN (UI Components)
ShadCN provides pre-styled components that work great with Tailwind CSS:
bunx --bun shadcn@latest init
3️⃣ Install Lucide Icons
Lucide is an icon library that works well with ShadCN:
bun add lucide-react
Now, you have a UI setup ready to roll. Let's move on to the database.
🗄️ Step 3: Configure the Database
Update the .env
file to set up your database connection:
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
Define Your Prisma Schema
Now, modify prisma/schema.prisma
to define your user authentication schema:
enum SubscriptionStatus {
Active
Canceled
}
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
Authenticator Authenticator[] // Optional for WebAuthn support
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
identifier String
token String
expires DateTime
@@id([identifier, token])
}
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}
Apply the schema changes:
bunx prisma migrate dev
bunx prisma generate
Now, let’s integrate authentication!
🔐 Step 4: Set Up Authentication with Auth.js
Auth.js (formerly NextAuth) makes adding authentication super simple.
First, install Auth.js:
bun add next-auth@beta
Generate an Encryption Key
bunx auth secret
Copy the generated key and store it in .env
under AUTH_SECRET
.
Create the Auth.js Configuration
Add an auth.ts
file inside src/
:
./src/auth.ts
import NextAuth from "next-auth"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [],
})
This sets up the auth configuration but doesn’t have any providers yet. We'll add GitHub authentication next.
🔑 Step 5: Add GitHub OAuth Provider
If you haven't set up GitHub OAuth before, follow this guide.
Add GitHub OAuth Credentials
Go to GitHub Developer Settings → OAuth Apps, create a new app, and get:
- Client ID
- Client Secret
Then, update your .env
file:
AUTH_GITHUB_ID="your_github_client_id"
AUTH_GITHUB_SECRET="your_github_client_secret"
Register GitHub in Auth.js
Modify auth.ts
to include GitHub as an authentication provider:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
})
🏛️ Step 6: Connect Prisma with Auth.js
Since we’re using a database, we need to integrate Prisma with Auth.js.
Install the Prisma Adapter
bun add @prisma/client @auth/prisma-adapter
Create a Global Prisma Client
./src/prisma.ts
import { PrismaClient } from "@prisma/client"
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma = globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
Use Prisma in Auth.js
Modify auth.ts
to add the Prisma adapter:
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
adapter: PrismaAdapter(prisma),
})
Apply the schema and generate Prisma client:
bunx prisma migrate dev
bunx prisma generate
👤 Step 7: Add Sign-In and Sign-Out Buttons
To test authentication, create a simple sign-in/sign-out UI in ./src/app/page.tsx
:
import { auth, signIn, signOut } from "@/auth.ts";
function SignInButton() {
return <form action={async () => {
"use server";
await signIn()
}}>
<button type="submit">Sign In</button>
</form>
}
function SignOutButton() {
return <form action={async () => {
"use server";
await signOut()
}}>
<button type="submit">Sign Out</button>
</form>
}
export default async function Page() {
const session = await auth();
return <main>
{!session?.user
? <SignInButton />
: <SignOutButton />
}
</main>
}
🎯 What’s Next?
This first part of our SaaS guide covered:
✅ Project setup with Next.js
✅ Database setup with Prisma
✅ Authentication with Auth.js & GitHub OAuth
Next up in Part 2: Handling user roles, Stripe payments, and subscription logic!
👉 Stay tuned for the next part, where we'll integrate Stripe for payments and subscription management! 🚀