# 🚀 Next.js Freelance Fullstack Starter

Stack: - Next.js (App Router) - TypeScript - MongoDB + Mongoose -
Tailwind CSS - Shadcn UI - Vercel Deployment Ready

------------------------------------------------------------------------

## 📁 Folder Structure

app/ layout.tsx page.tsx error.tsx not-found.tsx

(auth)/ login/page.tsx register/page.tsx

(dashboard)/ layout.tsx page.tsx

api/ auth/ login/route.ts register/route.ts payments/ create/route.ts
webhook/route.ts

components/ ui/ forms/ layout/

lib/ db.ts utils.ts response.ts

models/ User.ts Payment.ts

services/ payment.service.ts

types/ api.ts

.env.local

------------------------------------------------------------------------

## 🗄 Database Connection (lib/db.ts)

``` ts
import mongoose from "mongoose";

const MONGODB_URI = process.env.MONGODB_URI as string;

if (!MONGODB_URI) {
  throw new Error("Please define MONGODB_URI");
}

let cached = (global as any).mongoose;

if (!cached) {
  cached = (global as any).mongoose = { conn: null, promise: null };
}

export async function connectDB() {
  if (cached.conn) return cached.conn;

  if (!cached.promise) {
    cached.promise = mongoose.connect(MONGODB_URI, {
      dbName: "saas_app",
    });
  }

  cached.conn = await cached.promise;
  return cached.conn;
}
```

------------------------------------------------------------------------

## 📦 Standard API Response (types/api.ts)

``` ts
export interface ApiResponse<T = any> {
  success: boolean;
  data?: T;
  error?: string;
}
```

------------------------------------------------------------------------

## 🔁 Response Helpers (lib/response.ts)

``` ts
import { NextResponse } from "next/server";

export function successResponse(data: any) {
  return NextResponse.json({
    success: true,
    data,
  });
}

export function errorResponse(error: string, status = 400) {
  return NextResponse.json(
    {
      success: false,
      error,
    },
    { status }
  );
}
```

------------------------------------------------------------------------

## 👤 User Model (models/User.ts)

``` ts
import mongoose, { Schema, models } from "mongoose";

const UserSchema = new Schema(
  {
    name: { type: String, required: true },
    email: { type: String, required: true, unique: true },
    password: { type: String, required: true },
    role: { type: String, default: "user" },
  },
  { timestamps: true }
);

export const User =
  models.User || mongoose.model("User", UserSchema);
```

------------------------------------------------------------------------

## 💳 Payment Model (models/Payment.ts)

``` ts
import mongoose, { Schema, models } from "mongoose";

const PaymentSchema = new Schema(
  {
    userId: { type: Schema.Types.ObjectId, ref: "User", required: true },
    amount: { type: Number, required: true },
    status: {
      type: String,
      enum: ["pending", "success", "failed"],
      default: "pending",
    },
    providerId: { type: String },
  },
  { timestamps: true }
);

export const Payment =
  models.Payment || mongoose.model("Payment", PaymentSchema);
```

------------------------------------------------------------------------

## 🔐 Register API (app/api/auth/register/route.ts)

``` ts
import { connectDB } from "@/lib/db";
import { User } from "@/models/User";
import { successResponse, errorResponse } from "@/lib/response";
import bcrypt from "bcryptjs";

export async function POST(req: Request) {
  try {
    await connectDB();
    const { name, email, password } = await req.json();

    if (!name || !email || !password) {
      return errorResponse("All fields required");
    }

    const existing = await User.findOne({ email });
    if (existing) return errorResponse("User already exists");

    const hashed = await bcrypt.hash(password, 10);

    const user = await User.create({
      name,
      email,
      password: hashed,
    });

    return successResponse(user);
  } catch (error) {
    return errorResponse("Something went wrong", 500);
  }
}
```

------------------------------------------------------------------------

## 💳 Create Payment API (app/api/payments/create/route.ts)

``` ts
import { connectDB } from "@/lib/db";
import { Payment } from "@/models/Payment";
import { successResponse, errorResponse } from "@/lib/response";

export async function POST(req: Request) {
  try {
    await connectDB();
    const { userId, amount } = await req.json();

    if (!userId || !amount) {
      return errorResponse("Invalid payment data");
    }

    const payment = await Payment.create({
      userId,
      amount,
      status: "pending",
    });

    return successResponse(payment);
  } catch (error) {
    return errorResponse("Payment creation failed", 500);
  }
}
```

------------------------------------------------------------------------

## ⚠ Global Error Page (app/error.tsx)

``` tsx
"use client";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="text-2xl font-bold">Something went wrong</h1>
      <button
        onClick={() => reset()}
        className="mt-4 px-4 py-2 bg-black text-white rounded"
      >
        Try Again
      </button>
    </div>
  );
}
```

------------------------------------------------------------------------

## 🌍 Environment Variables

    MONGODB_URI=your_mongodb_connection_string

------------------------------------------------------------------------

## 🚀 Deployment Notes

-   Keep all secrets in Vercel environment variables
-   Never expose payment secret keys to frontend
-   Add webhook verification for real payment providers
-   Use proper loading + error states in UI
-   Prevent double submission on payment buttons

------------------------------------------------------------------------

This structure is freelance-ready, scalable, and clean.
