DEV Community

Nexus Intelligence Research
Nexus Intelligence Research

Posted on

🚀 Build a Minimal AI‑Powered SaaS in a Day: FastAPI + Groq + Stripe + Nginx

Turn a short brief into a polished 500‑word article in seconds, monetize it with Stripe, and launch a production‑ready service on a cheap VPS.


TL;DR

✅ Feature 📦 Tech Stack
API – /generate (title + keywords → 500‑word article) FastAPI, Groq LLM API
Payments – $29 PRO / $99 ENTERPRISE (monthly) Stripe Checkout + Webhooks
Auth & Rate‑Limiting – 5 req/hr per Stripe customer Stripe Customer ID, slowapi
Deploy – VPS (port 8000) behind Nginx + HTTPS Ubuntu 22.04, Nginx, Let’s Encrypt
Landing Page – HTML/CSS + demo video Static files served by Nginx
Marketing – Dev.to post, Reddit threads, email welcome Playwright automation, free SMTP (Mailgun)
Monitoring – Stripe, LLM cost, VPS load Grafana + Prometheus (optional)

You can copy‑paste the code snippets below, replace the placeholders, and have a live SaaS product before lunch.


1. Why an AI‑Generated Article Service?

Content marketing is still king, but writing SEO‑friendly copy is a bottleneck. With the rise of large language models (LLMs) like Groq’s Llama‑3‑8B‑Chat, you can generate high‑quality, keyword‑rich articles in under a minute. Packaging this capability as a subscription‑based SaaS gives you recurring revenue while solving a real pain point for bloggers, marketers, and startups.

Key benefits

  • Speed: Turn a 5‑word brief into a ready‑to‑publish article in ~30 s.
  • Consistency: Same tone, structure, and SEO optimization every time.
  • Scalability: Groq’s pay‑as‑you‑go pricing lets you scale without over‑provisioning.

2. Core FastAPI App

2.1 Project Layout

ai‑article‑saas/
├─ app/
│  ├─ main.py            # FastAPI entry point
│  ├─ schemas.py         # Pydantic models
│  ├─ services/
│  │   └─ groq.py        # Wrapper around Groq API
│  └─ stripe_webhook.py # Stripe webhook handler
├─ static/
│  └─ index.html        # Landing page (served by Nginx)
├─ requirements.txt
└─ Dockerfile
Enter fullscreen mode Exit fullscreen mode

2.2 requirements.txt

fastapi==0.110.0
uvicorn[standard]==0.27.0
httpx==0.27.0
python‑dotenv==1.0.1
stripe==9.9.0
slowapi==0.1.7        # rate‑limiting
pydantic==2.6.1
Enter fullscreen mode Exit fullscreen mode

2.3 main.py

import os
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
import httpx
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from starlette.status import HTTP_429_TOO_MANY_REQUESTS

app = FastAPI()
limiter = Limiter(key_func=lambda request: request.state.customer_id)
app.state.limiter = limiter
app.add_exception_handler(HTTP_429_TOO_MANY_REQUESTS, _rate_limit_exceeded_handler)

# ---- Models -------------------------------------------------
class Brief(BaseModel):
    title: str
    keywords: str

class ArticleResponse(BaseModel):
    article: str

# ---- Auth ---------------------------------------------------
bearer = HTTPBearer(auto_error=False)

async def get_customer_id(
    credentials: HTTPAuthorizationCredentials = Depends(bearer),
    request: Request = None,
):
    """
    Stripe Customer ID is passed as a Bearer token.
    In production you’d verify the token via Stripe’s OAuth or a JWT.
    """
    if not credentials:
        raise HTTPException(status_code=401, detail="Missing auth token")
    request.state.customer_id = credentials.credentials
    return credentials.credentials

# ---- LLM Wrapper ---------------------------------------------
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
GROQ_ENDPOINT = "https://api.groq.com/openai/v1/chat/completions"

async def generate_article(brief: Brief) -> str:
    system_prompt = (
        "You are an expert copywriter. Write a 500‑word SEO‑optimized article "
        "based on the given title and keywords. Use markdown headings, bullet points, "
        "and a concluding paragraph."
    )
    user_prompt = f"Title: {brief.title}\nKeywords: {brief.keywords}"
    payload = {
        "model": "llama3-8b-8192",
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt},
        ],
        "temperature": 0.7,
    }
    async with httpx.AsyncClient() as client:
        r = await client.post(
            GROQ_ENDPOINT,
            json=payload,
            headers={"Authorization": f"Bearer {GROQ_API_KEY}"},
            timeout=30,
        )
        r.raise_for_status()
        data = r.json()
        return data["choices"][0]["message"]["content"]

# ---- Endpoints ------------------------------------------------
@app.post("/generate", response_model=ArticleResponse)
@limiter.limit("5/hour")
async def generate(
    brief: Brief,
    customer_id: str = Depends(get_customer_id),
):
    article = await generate_article(brief)
    return ArticleResponse(article=article)

# ---- Health ---------------------------------------------------
@app.get("/health")
async def health():
    return {"status": "ok"}
Enter fullscreen mode Exit fullscreen mode

Tip: The rate‑limit key is the Stripe Customer ID, ensuring each paying user gets a fair quota.


3. Stripe Checkout & Webhooks

3.1 Products in Stripe

Plan Price (monthly) Stripe Product ID Price ID
PRO $29 prod_PRO_XXXXX price_PRO_XXXXX
ENTERPRISE $99 prod_ENT_XXXXX price_ENT_XXXXX

Create both products in the Stripe Dashboard and enable a 7‑day trial for the PRO plan (use Stripe’s built‑in trial days).

3.2 Checkout Session Helper

import stripe
stripe.api_key = os.getenv("STRIPE_SECRET_KEY")

def create_checkout_session(customer_id: str, price_id: str, success_url: str, cancel_url: str):
    session = stripe.checkout.Session.create(
        customer=customer_id,
        payment_method_types=["card"],
        line_items=[{
            "price": price_id,
            "quantity": 1,
        }],
        mode="subscription",
        success_url=success_url,
        cancel_url=cancel_url,
    )
    return session.url
Enter fullscreen mode Exit fullscreen mode

Expose a tiny endpoint (e.g., /checkout?plan=pro) that redirects the user to the generated URL.

3.3 Webhook Handler (stripe_webhook.py)


python
from fastapi import APIRouter, Request, HTTPException
import stripe
import os

router = APIRouter()
stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
endpoint_secret = os.getenv("STRIPE_WEBHOOK_SECRET")

@
Enter fullscreen mode Exit fullscreen mode

Top comments (0)