Dev.to AI 🤖 Ai 👁 0 📖 4 min read

🚀 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 

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

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

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"}

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

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")

@
📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.