EmpiezaTuSaaS
Funcionalidades

Base de Datos

PostgreSQL con Prisma ORM para tu SaaS

Base de Datos

EmpiezaTuSaaS usa PostgreSQL + Prisma ORM. Tipos TypeScript automaticos, migraciones versionadas y control total sobre tu base de datos.

Setup

Instala las dependencias e inicializa Prisma:

npm install @prisma/client
npm install -D prisma
npx prisma init

Esto crea prisma/schema.prisma y agrega DATABASE_URL a tu .env.

Configura la conexión en .env:

# Local con Docker
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/empiezatusaas"

# O con Neon (recomendado para producción)
DATABASE_URL="postgresql://user:password@ep-xxx.us-east-1.aws.neon.tech/neondb?sslmode=require"

Para desarrollo local con Docker:

docker run --name postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=empiezatusaas \
  -p 5432:5432 \
  -d postgres:15

Schema

El boilerplate incluye todos los modelos necesarios para autenticación (Better Auth), usuarios y suscripciones Stripe:

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id            String    @id @default(cuid())
  name          String
  email         String    @unique
  emailVerified Boolean   @default(false)
  image         String?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  role          String?   @default("user")
  banned        Boolean?  @default(false)
  banReason     String?
  banExpires    DateTime?

  sessions      Session[]
  accounts      Account[]
  subscription  Subscription?

  @@map("users")
}

model Session {
  id        String   @id @default(cuid())
  expiresAt DateTime
  token     String   @unique
  ipAddress String?
  userAgent String?
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("sessions")
}

model Account {
  id                    String    @id @default(cuid())
  accountId             String
  providerId            String
  userId                String
  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  accessToken           String?
  refreshToken          String?
  idToken               String?
  accessTokenExpiresAt  DateTime?
  refreshTokenExpiresAt DateTime?
  scope                 String?
  password              String?
  createdAt             DateTime  @default(now())
  updatedAt             DateTime  @updatedAt

  @@map("accounts")
}

model Verification {
  id         String    @id @default(cuid())
  identifier String
  value      String
  expiresAt  DateTime
  createdAt  DateTime? @default(now())
  updatedAt  DateTime? @updatedAt

  @@map("verifications")
}

model Subscription {
  id                   String    @id @default(cuid())
  userId               String    @unique
  user                 User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  stripeCustomerId     String?   @unique
  stripeSubscriptionId String?   @unique
  stripePriceId        String?
  stripeCurrentPeriodEnd DateTime?
  plan                 String?
  status               String    @default("inactive")
  createdAt            DateTime  @default(now())
  updatedAt            DateTime  @updatedAt

  @@map("subscriptions")
}
ModeloTablaDescripción
UserusersUsuarios con roles y campos de baneo
SessionsessionsSesiónes activas con token, IP y user agent
AccountaccountsCuentas de proveedor (credenciales, Google, GitHub)
VerificationverificationsTokens de verificación (email, reset password)
SubscriptionsubscriptionsSuscripciones Stripe vinculadas al usuario

Aplica el schema a tu base de datos:

npx prisma migrate dev --name init

Cliente de base de datos

El boilerplate incluye un cliente singleton que evita multiples instancias en desarrollo con hot reload:

lib/db.ts
import { PrismaClient } from "@prisma/client";

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const db =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
  });

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;

Este patron singleton evita crear multiples conexiónes durante hot reload en desarrollo. Importa db desde @/lib/db en cualquier parte del proyecto.

Consultas con Prisma

Crear

import { db } from "@/lib/db";

const user = await db.user.create({
  data: {
    name: "Juan Garcia",
    email: "juan@example.com",
    emailVerified: false,
  },
});

Leer

// Por ID o campo unico
const user = await db.user.findUnique({
  where: { email: "juan@example.com" },
});

// Con relaciones
const userWithSub = await db.user.findUnique({
  where: { id: "user-id" },
  include: { subscription: true, sessions: true },
});

// Listado con filtros
const activeUsers = await db.user.findMany({
  where: { banned: false, emailVerified: true },
  orderBy: { createdAt: "desc" },
  take: 10,
});

Actualizar

const user = await db.user.update({
  where: { id: "user-id" },
  data: { name: "Juan Garcia Lopez" },
});

// Upsert: crear o actualizar
const subscription = await db.subscription.upsert({
  where: { userId: "user-id" },
  update: { status: "active", plan: "pro" },
  create: { userId: "user-id", status: "active", plan: "pro" },
});

Eliminar

// Eliminar sesiónes expiradas
await db.session.deleteMany({
  where: { expiresAt: { lt: new Date() } },
});

Transacciones

const result = await db.$transaction(async (tx) => {
  const user = await tx.user.create({
    data: { name: "Maria", email: "maria@example.com", emailVerified: true },
  });

  const subscription = await tx.subscription.create({
    data: { userId: user.id, status: "active", plan: "starter" },
  });

  return { user, subscription };
});

Uso en Next.js

app/admin/users/page.tsx
import { db } from "@/lib/db";

export default async function UsersPage() {
  const users = await db.user.findMany({
    include: { subscription: true },
    orderBy: { createdAt: "desc" },
  });

  return (
    <div>
      {users.map((user) => (
        <div key={user.id}>
          <h2>{user.name}</h2>
          <p>{user.subscription?.plan ?? "Sin suscripcion"}</p>
        </div>
      ))}
    </div>
  );
}
app/admin/actions.ts
"use server";

import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";

export async function banUser(userId: string, reason: string) {
  await db.user.update({
    where: { id: userId },
    data: { banned: true, banReason: reason },
  });
  revalidatePath("/admin/users");
}

Migraciones

Desarrollo

# Crear y aplicar migracion
npx prisma migrate dev --name add_user_phone

# Resetear BD y re-aplicar todo
npx prisma migrate reset

# Sincronizar schema sin crear migracion (prototipado rapido)
npx prisma db push

Producción

# Solo aplicar migraciones pendientes
npx prisma migrate deploy

Nunca uses migrate dev o migrate reset en producción. Estos comandos pueden eliminar datos.

Comandos utiles

ComandoDescripción
prisma migrate devCrear y aplicar migracion (desarrollo)
prisma migrate deployAplicar migraciones pendientes (produccion)
prisma migrate resetResetear base de datos y re-aplicar todo
prisma migrate statusVer estado de migraciones
prisma db pushSincronizar schema sin crear migracion
prisma generateRegenerar cliente Prisma
prisma studioUI visual para tu base de datos

Seed

Crea datos iniciales:

prisma/seed.ts
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function main() {
  const admin = await prisma.user.upsert({
    where: { email: "admin@example.com" },
    update: {},
    create: {
      email: "admin@example.com",
      name: "Administrador",
      emailVerified: true,
      role: "admin",
    },
  });

  await prisma.subscription.upsert({
    where: { userId: admin.id },
    update: {},
    create: {
      userId: admin.id,
      plan: "pro",
      status: "active",
      stripeCustomerId: "cus_seed_admin",
      stripeSubscriptionId: "sub_seed_admin",
    },
  });

  console.log("Seed completado:", { admin });
}

main()
  .catch((e) => { console.error(e); process.exit(1); })
  .finally(async () => { await prisma.$disconnect(); });

Agrega al package.json:

{
  "prisma": {
    "seed": "npx tsx prisma/seed.ts"
  }
}
npx prisma db seed

Hosting

ProveedorTier gratuitoMejor para
NeonSi (generoso)Serverless, recomendado
RailwayTrialSide projects
Vercel PostgresSiApps en Vercel

Recomendamos Neon para producción: tier gratuito generoso, serverless, y funciona perfecto con Prisma en Vercel.

On this page