Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ

I still remember staring at my computer screen late one night, sketching out ideas for a SEO platform. Like many developers, I had a growing list of “someday” projects that never seemed to materialize.

But this time was different. Armed with Wasp Fram…


This content originally appeared on DEV Community and was authored by Luna Rojas

I still remember staring at my computer screen late one night, sketching out ideas for a SEO platform. Like many developers, I had a growing list of “someday” projects that never seemed to materialize.

But this time was different. Armed with Wasp Framework and a clear vision for whitehatseotactics.com, I decided to challenge myself:

“Could I build and launch a complete SaaS in just 7 days?”

⚠ Spoiler alert: Not only did I succeed, but within weeks of launch, the platform had generated $1,680.37 in revenue. This isn’t one of those “too good to be true” stories — it’s about how modern development has changed what’s possible for indie developers.

a

You see, I’ve built my fair share of SaaS applications before, and the pattern was always the same: weeks spent setting up authentication, wrestling with payment integrations, and building basic features that every SaaS needs. It was like reinventing the wheel every single time.

a

But this project was different, and I want to share exactly how.

The secret? A powerful combination of Wasp and OpenSaaS that handled all the boilerplate and infrastructure, letting me focus entirely on what made my SaaS unique. No more endless configuration files, no more authentication headaches, and definitely no more weeks spent on basic setup!

In this article, I’ll take you through my entire journey — from the initial setup to launching a profitable SaaS in just 7 days. I’ll show you how Wasp’s elegant architecture with its main.wasp file became my single source of truth, how Prisma’s schema made database management a breeze, and how features like built-in authentication and Stripe integration saved me days of development time.

a

Check out: https://whitehatseotactics.com/

But this isn’t just about the technical stack. It’s about rethinking how we approach SaaS development in 2025. Whether you’re a seasoned developer or just starting your first serious project, I believe this approach could change your development workflow too.

Let’s dive into how modern tools like Wasp Framework are making it possible to go from idea to profitable SaaS faster than ever before.

Why Wasp Framework Was My Game-Changer

As a full-stack developer, finding the right framework that balances productivity, type safety, and maintainability is crucial.

a

Source: https://wasp.sh/

After exploring various options, Wasp Framework emerged as a game-changing solution for my development workflow. Here’s why:

The Appeal of Full-Stack TypeScript

One of the most compelling features of Wasp is its first-class support for TypeScript across the entire stack. Wasp automatically generates types based on your schema and operations, providing true full-stack type safety:

// Server-side query with full type inference
import type { GetTasks } from 'wasp/server/operations'

export const getTasks: GetTasks = async (args, context) => {
  // Types for args and context are automatically inferred
  const tasks = await context.entities.Task.findMany()
  return tasks // Return type is also checked
}
// Client-side with type-safe queries
import { useQuery } from 'wasp/client/operations'

export function TaskList() {
  // Full type safety for query data and error handling
  const { data: tasks, isLoading, error } = useQuery(getTasks)
  // ...
}

Single Source of Truth with main.wasp

The main.wasp file serves as a centralized configuration that ties everything together. This declarative approach eliminates the need for complex boilerplate while maintaining clarity:

app todoApp {
  wasp: {
    version: "^0.15.0"
  },
  title: "Todo App"
}

route TaskRoute { path: "/tasks", to: TaskPage }
page TaskPage {
  component: import { TaskPage } from "@src/pages/TaskPage.tsx"
}

query getTasks {
  fn: import { getTasks } from "@src/server/queries.ts",
  entities: [Task]

The Power of Prisma Schema

Wasp leverages Prisma for database operations, providing a clean and type-safe way to define your data model:

model Task {
  id          Int      @id @default(autoincrement())
  description String
  isDone      Boolean  @default(false)
  user        User     @relation(fields: [userId], references: [id])
  userId      Int
}

model User {
  id    Int     @id @default(autoincrement())
  tasks Task[]
}

This schema automatically generates:

  • Type-safe database queries
  • Migration scripts
  • TypeScript types for your entities
  • Full-stack type safety when used with queries and actions

How Cursor IDE Accelerated Development

While Wasp Framework provided the foundation, my development speed got an extra boost from Cursor IDE. As I built whitehatseotactics.com, I discovered that Cursor’s AI-powered features perfectly complemented Wasp’s development workflow.

a

https://www.cursor.com/

The real game-changer was Cursor’s ability to understand Wasp’s specific patterns and conventions. For example, when working with the main.wasp file, Cursor would intelligently suggest completions for routes, queries, and actions:

route DashboardRoute { 
  path: "/dashboard", 
  to: DashboardPage,
  authRequired: true
}

query getSEOMetrics {
  fn: import { getSEOMetrics } from "@src/server/queries.ts",
  entities: [Website, SEOMetrics]
}

But where Cursor really shined was in helping me maintain consistency across my full-stack TypeScript code. When implementing new features, Cursor would suggest proper type definitions and help me maintain the connection between my server operations and client components:

// Cursor would help maintain consistency between
// server operations and client components
import { useQuery } from 'wasp/client/operations'
import type { GetSEOMetrics } from 'wasp/server/operations'

export const getSEOMetrics: GetSEOMetrics = async (args, context) => {
  const { websiteId } = args
  return context.entities.SEOMetrics.findMany({
    where: { websiteId }
  })
}

Cursor understood Wasp’s patterns and could help generate boilerplate code for new features, saving me hours of typing.

Building the Core Features at Lightning Speed

When building whitehatseotactics.com, the speed at which I could implement core features was remarkable. Let me walk you through how Wasp’s architecture made this possible.

Authentication System Implementation

One of the most time-consuming parts of building a SaaS is usually the authentication system. With Wasp, it was surprisingly straightforward. The framework provides a built-in auth system that took minutes to set up:

app whitehatseotactics {
  wasp: {
    version: "^0.15.0"
  },
  title: "White Hat SEO Tactics",
  auth: {
    userEntity: User,
    methods: {
      usernameAndPassword: {}
    },
    onAuthFailedRedirectTo: "/login"
  }
}

On the client side, Wasp provides ready-to-use components that I could easily customize with Tailwind:

import { LoginForm } from 'wasp/client/auth'

export function LoginPage() {
  return (
    <div className='min-h-screen flex items-center justify-center bg-gray-50'>
      <div className='max-w-md w-full space-y-8'>
        <LoginForm 
          className='mt-8 space-y-6'
          appearance={{
            submitButton: 'w-full py-2 px-4 bg-indigo-600 hover:bg-indigo-700 rounded-md'
          }}
        />
      </div>
    </div>
  )
}

Database Setup and Management

Setting up the database schema was incredibly efficient with Prisma. I defined my core models in schema.prisma:

model User {
  id        Int       @id @default(autoincrement())
  projects  Project[]
  websites  Website[]
  createdAt DateTime  @default(now())
}

model Website {
  id          Int      @id @default(autoincrement())
  url         String
  user        User     @relation(fields: [userId], references: [id])
  userId      Int
  seoMetrics  SEOMetrics[]
  lastScanned DateTime?
}

model SEOMetrics {
  id        Int      @id @default(autoincrement())
  website   Website  @relation(fields: [websiteId], references: [id])
  websiteId Int
  score     Int
  findings  Json
  createdAt DateTime @default(now())
}

React TSX Components Structure

I organized my components following a feature-based structure, which Wasp made easy to maintain:

// src/features/seo-analysis/components/MetricsDisplay.tsx
import { useQuery } from 'wasp/client/operations'

export function MetricsDisplay({ websiteId }: { websiteId: number }) {
  const { data: metrics, isLoading } = useQuery(getSEOMetrics, { websiteId })

  if (isLoading) {
    return <div className='animate-pulse'>Loading metrics...</div>
  }

  return (
    <div className='grid grid-cols-3 gap-4'>
      {metrics.map(metric => (
        <MetricCard 
          key={metric.id}
          score={metric.score}
          findings={metric.findings}
        />
      ))}
    </div>
  )
}

I kept my components focused and reusable, leveraging Wasp’s built-in hooks for data fetching:

// src/features/shared/components/DashboardLayout.tsx
import { useAuth } from 'wasp/client/auth'
import { Outlet } from 'react-router-dom'

export function DashboardLayout() {
  const { user } = useAuth()

  return (
    <div className='min-h-screen bg-gray-100'>
      <Sidebar user={user} />
      <main className='ml-64 p-8'>
        <Outlet />
      </main>
    </div>
  )
}

TailwindCSS Integration Benefits

Wasp’s seamless integration with Tailwind CSS was a massive productivity boost. I could style components rapidly without context switching:

  1. Rapid Prototyping: Tailwind’s utility classes allowed me to build UI components quickly.
  2. Responsive Design: Building a mobile-friendly interface was straightforward:

The combination of Wasp’s structured approach to full-stack development and these modern tools allowed me to build complex features in hours rather than days. The built-in authentication, type-safe database operations, and seamless UI development created a development experience that was both enjoyable and incredibly productive.

Seamless Third-Party Integrations

One of the most impressive aspects of Wasp was how smoothly it handled third-party integrations. Here’s how I integrated essential services with the OpenSaaS Template without the usual integration headaches.

a

https://opensaas.sh/

Using this template you won’t need to do all of the following but look how easy it is!

Stripe Payment Processing

Setting up Stripe for my SaaS pricing tiers was straightforward. First, I defined the payment logic in my operations file:

// src/server/payments/operations.ts
import type { CreateSubscription } from 'wasp/server/operations'
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export const createSubscription: CreateSubscription = async (args, context) => {
  if (!context.user) throw new Error('Not authorized')

  const { priceId } = args

  const session = await stripe.checkout.sessions.create({
    customer_email: context.user.email,
    line_items: [{
      price: priceId,
      quantity: 1
    }],
    mode: 'subscription',
    success_url: `${process.env.WASP_WEB_CLIENT_URL}/dashboard?success=true`,
    cancel_url: `${process.env.WASP_WEB_CLIENT_URL}/pricing`
  })

  return { sessionId: session.id }
}

Then, I declared it in main.wasp:

action createSubscription {
  fn: import { createSubscription } from "@src/server/payments/operations.js",
  entities: [User, Subscription]
}

And implemented the client-side component:

// src/features/billing/components/PricingCard.tsx
import { useAction } from 'wasp/client/operations'
import { loadStripe } from '@stripe/stripe-js'

export function PricingCard({ price, features }: PricingProps) {
  const createSubscriptionFn = useAction(createSubscription)
  const stripePromise = loadStripe(process.env.STRIPE_PUBLIC_KEY!)

  const handleSubscribe = async () => {
    const { sessionId } = await createSubscriptionFn({ priceId: price.id })
    const stripe = await stripePromise
    await stripe?.redirectToCheckout({ sessionId })
  }

  return (
    <div className='rounded-lg border p-6 bg-white shadow-sm'>
      <button
        onClick={handleSubscribe}
        className='w-full bg-indigo-600 text-white py-2 rounded-md'
      >
        Subscribe
      </button>
    </div>
  )
}

OpenAI API Implementation

Integrating OpenAI for SEO content analysis was equally smooth:

// src/server/ai/operations.ts
import type { AnalyzeContent } from 'wasp/server/operations'
import { OpenAI } from 'openai'

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })

export const analyzeContent: AnalyzeContent = async (args, context) => {
  if (!context.user) throw new Error('Not authorized')

  const { content } = args

  const completion = await openai.chat.completions.create({
    messages: [{
      role: 'system',
      content: 'You are an SEO expert analyzing content.'
    }, {
      role: 'user',
      content: `Analyze this content: ${content}`
    }],
    model: 'gpt-4'
  })

  return {
    analysis: completion.choices[0].message.content,
    score: calculateSEOScore(completion)
  }
}

a

AWS S3 File Handling

For file uploads and storage, I integrated AWS S3:

// src/server/storage/operations.ts
import type { UploadFile } from 'wasp/server/operations'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'

const s3Client = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
  }
})

export const uploadFile: UploadFile = async (args, context) => {
  if (!context.user) throw new Error('Not authorized')

  const { file, fileName } = args

  await s3Client.send(new PutObjectCommand({
    Bucket: process.env.AWS_BUCKET_NAME,
    Key: `users/${context.user.id}/${fileName}`,
    Body: file,
    ContentType: file.type
  }))

  return {
    url: `https://${process.env.AWS_BUCKET_NAME}.s3.amazonaws.com/users/${context.user.id}/${fileName}`
  }
}

a

Auth System Setup

Wasp’s built-in auth system was a massive time-saver.

a

I just needed to configure it in main.wasp and create the login pages:

app whitehatseotactics {
  auth: {
    userEntity: User,
    methods: {
      usernameAndPassword: {
        allowUnverifiedLogin: false,
        onAuthFailedRedirectTo: "/login"
      },
      google: {
        configFn: import { googleAuthConfig } from "@src/server/auth/config.js"
      }
    }
  }
}

The auth UI components were ready to use with my styling:

// src/features/auth/pages/LoginPage.tsx
import { LoginForm } from 'wasp/client/auth'

export function LoginPage() {
  return (
    <div className='min-h-screen flex items-center justify-center bg-gray-50'>
      <div className='max-w-md w-full space-y-8'>
        <LoginForm 
          appearance={{
            submitButton: 'w-full py-2 px-4 rounded-md bg-indigo-600 hover:bg-indigo-700',
            socialButton: 'w-full py-2 px-4 rounded-md border hover:bg-gray-50'
          }}
          logo={<img src='/logo.png' alt='Logo' className='h-12 w-auto' />}
        />
      </div>
    </div>
  )
}

The real power came from how these integrations worked together. For example, when a user subscribes through Stripe, their auth session is automatically updated with their subscription status, and they can immediately start uploading files to S3 and using the OpenAI-powered features.

This integrated approach meant I could focus on building features rather than wrestling with authentication states, API keys, or service connections. Wasp handled all the complex integration patterns, allowing me to build a fully-featured SaaS in just 7 days.

The Technical Stack Deep Dive

Let me walk you through the technical decisions that made White Hat SEO Tactics both maintainable and scalable. The beauty of Wasp is how it brings together modern tools in a cohesive way.

Frontend Architecture Decisions

I structured my frontend using a feature-first architecture, which kept the codebase organized as features grew:

src/
  features/
    seo-analysis/
      components/
        AnalysisDashboard.tsx
        MetricsChart.tsx
      operations.ts
      types.ts
    website-management/
      components/
        WebsiteList.tsx
        AddWebsiteModal.tsx
      operations.ts
    shared/
      components/
        Layout.tsx
        Navbar.tsx
      hooks/
        useDebounce.ts

For state management, I leveraged Wasp’s built-in query caching and real-time updates:

// src/features/seo-analysis/components/AnalysisDashboard.tsx
import { useQuery } from 'wasp/client/operations'

export function AnalysisDashboard() {
  const { data: websites, isLoading } = useQuery(getWebsites)
  const { data: metrics } = useQuery(getSEOMetrics, {
    // Automatic cache invalidation when websites change
    dependencies: [websites]
  })

  return (
    <div className='space-y-6'>
      <MetricsOverview data={metrics} />
      <WebsiteList 
        websites={websites}
        isLoading={isLoading}
      />
    </div>
  )
}

Backend Structure with Node.js

Wasp’s backend architecture made it easy to organize business logic and maintain clean separation of concerns:

// src/server/core/validation.ts
export const validateWebsite = async (url: string) => {
  // Shared validation logic
}

// src/server/websites/operations.ts
import type { CreateWebsite } from 'wasp/server/operations'
import { validateWebsite } from '../core/validation'

export const createWebsite: CreateWebsite = async (args, context) => {
  if (!context.user) throw new Error('Not authorized')

  await validateWebsite(args.url)

  return context.entities.Website.create({
    data: {
      url: args.url,
      userId: context.user.id,
      settings: args.settings
    }
  })
}

Query and Action Patterns

Wasp’s operation system provides a clean pattern for all data operations. Here’s how I structured my queries and actions:

// main.wasp
query getWebsiteMetrics {
  fn: import { getWebsiteMetrics } from "@src/server/metrics/operations.js",
  entities: [Website, SEOMetrics]
}

action updateWebsiteSettings {
  fn: import { updateWebsiteSettings } from "@src/server/websites/operations.js",
  entities: [Website]
}

Implementation with type safety:

// src/server/metrics/operations.ts
import type { GetWebsiteMetrics } from 'wasp/server/operations'

export const getWebsiteMetrics: GetWebsiteMetrics = async ({ websiteId }, context) => {
  if (!context.user) throw new Error('Not authorized')

  return context.entities.SEOMetrics.findMany({
    where: { 
      websiteId,
      website: { userId: context.user.id }
    },
    include: {
      website: true
    }
  })
}

Client-side usage becomes straightforward:

// src/features/metrics/components/MetricsView.tsx
import { useQuery, useAction } from 'wasp/client/operations'

export function MetricsView({ websiteId }: { websiteId: number }) {
  const { data: metrics } = useQuery(getWebsiteMetrics, { websiteId })
  const updateSettingsFn = useAction(updateWebsiteSettings)

  const handleSettingsUpdate = async (settings: WebsiteSettings) => {
    await updateSettingsFn({
      websiteId,
      settings
    })
  }

  return (
    // Component implementation
  )
}

Database Design Choices

I used Prisma’s schema to define clear relationships and constraints:

model User {
  id        Int       @id @default(autoincrement())
  websites  Website[]
  plan      Plan      @default(FREE)
  createdAt DateTime  @default(now())
}

model Website {
  id          Int         @id @default(autoincrement())
  url         String      @unique
  user        User        @relation(fields: [userId], references: [id])
  userId      Int
  metrics     SEOMetrics[]
  settings    Json?
  lastScanned DateTime?
}

model SEOMetrics {
  id        Int      @id @default(autoincrement())
  website   Website  @relation(fields: [websiteId], references: [id])
  websiteId Int
  score     Int
  findings  Json
  createdAt DateTime @default(now())

  @@index([websiteId, createdAt])
}

enum Plan {
  FREE
  PRO
  ENTERPRISE
  ENTERPRISE<br>}</span>

Key database design decisions included:

1. Using JSON fields for flexible storage of settings and findings

2. Creating appropriate indexes for common queries

3. Implementing soft deletes for important data

4. Using enums for fixed value sets like subscription plans

This technical foundation allowed me to:

  • Scale efficiently as user base grew
  • Maintain type safety across the stack
  • Add features quickly without breaking existing functionality
  • Keep the codebase organized and maintainable

The combination of Wasp’s structured approach with these architectural decisions meant I could move fast without sacrificing code quality or scalability.

From Development to Deployment

After building the core features of whitehatseotactics.com, I focused on a strategic launch that would maximize early adoption and revenue. Here’s how I approached each phase:

Launch Strategy

I followed a three-phase launch approach:

1. Soft Launch (Days 1–2)

  • Released with basic pricing tiers

2. Marketing Push (Days 3–5)

  • Posted on Product Hunt
  • Shared on Twitter/X
  • Engaged with SEO communities
  • Started content marketing with blog posts

3. Full Launch (Days 6–7)

  • Enabled all payment tiers
  • Started running targeted ads

And take profit đź’µ.

Early User Feedback

The feedback loop was crucial for quick improvements:

  • Feedback Collection
  • Monitored user behavior through analytics
  • Quick Iterations
  • Fixed reported bugs within hours
  • Improved UX based on session recordings
  • Updated pricing based on user suggestions

Revenue Generation Approach

My revenue strategy focused on providing immediate value:

  1. Pricing Structure
  • Free tier: 3 basic SEO tactics
  • Pro tier ($27): 25 advanced tactics
  • Lifetime deal ($49): All 100+ tactics

2. Revenue Streams

  • Direct sales through tiered pricing
  • Upsells for additional features
  • Affiliate commissions
  • Custom enterprise solutions

3. Key Results

  • First sale within 24 hours of launch
  • Reached $1,680.37 in revenue
  • 42% conversion rate from free to paid

The rapid deployment was possible thanks to Wasp’s built-in features and seamless integration with services like Stripe. The auth system implementation made user management straightforward.

Key Takeaways

Looking back at building whitehatseotactics.com, I’ve gathered valuable insights about rapid SaaS development with Wasp Framework. Here’s what I learned and where I’m headed next.

What Worked Exceptionally Well

  1. Wasp Framework’s Core Features
  • Auth system implementation saved 2–3 days of development time
  • Prisma schema made database changes painless
  • TypeScript integration caught errors early
  • Built-in deployment pipeline simplified launch

2. Development Workflow

// Example of Wasp's elegant operation definitions
query getSEOMetrics {
  fn: import { getSEOMetrics } from "@src/server/metrics/operations.js",
  entities: [Website, SEOMetrics]
}

action updateMetrics {
  fn: import { updateMetrics } from "@src/server/metrics/operations.js",
  entities: [Website, SEOMetrics]
}

3. Tech Stack Choices

  • TailwindCSS for rapid UI development
  • React + TypeScript for robust frontend
  • Prisma for type-safe database operations
  • Stripe for seamless payments

The experience of building whitehatseotactics.com with Wasp has proven that rapid SaaS development doesn’t mean sacrificing quality or scalability. The framework’s robust features and elegant architecture have provided a solid foundation for future growth.

Moving forward, the focus will be on scaling the infrastructure, expanding features, and growing the user base while maintaining the agility that made the initial development so successful.


This content originally appeared on DEV Community and was authored by Luna Rojas


Print Share Comment Cite Upload Translate Updates
APA

Luna Rojas | Sciencx (2025-02-13T15:47:21+00:00) Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ. Retrieved from https://www.scien.cx/2025/02/13/building-a-profitable-saas-in-7-days-how-i-made-1680-37%f0%9f%92%b5/

MLA
" » Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ." Luna Rojas | Sciencx - Thursday February 13, 2025, https://www.scien.cx/2025/02/13/building-a-profitable-saas-in-7-days-how-i-made-1680-37%f0%9f%92%b5/
HARVARD
Luna Rojas | Sciencx Thursday February 13, 2025 » Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ., viewed ,<https://www.scien.cx/2025/02/13/building-a-profitable-saas-in-7-days-how-i-made-1680-37%f0%9f%92%b5/>
VANCOUVER
Luna Rojas | Sciencx - » Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/02/13/building-a-profitable-saas-in-7-days-how-i-made-1680-37%f0%9f%92%b5/
CHICAGO
" » Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ." Luna Rojas | Sciencx - Accessed . https://www.scien.cx/2025/02/13/building-a-profitable-saas-in-7-days-how-i-made-1680-37%f0%9f%92%b5/
IEEE
" » Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ." Luna Rojas | Sciencx [Online]. Available: https://www.scien.cx/2025/02/13/building-a-profitable-saas-in-7-days-how-i-made-1680-37%f0%9f%92%b5/. [Accessed: ]
rf:citation
» Building a Profitable SaaS in 7 Days: How I made $1,680.37đź’µ | Luna Rojas | Sciencx | https://www.scien.cx/2025/02/13/building-a-profitable-saas-in-7-days-how-i-made-1680-37%f0%9f%92%b5/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.