404 Not Found

404 Not Found


nginx

Prisma Database

MegaShop sempre usou dados mock—dados de produto armazenados em arrays JavaScript, que são perdidos ao reiniciar. Charlie precisa de um banco de dados real. O SQL nativo escrito por Bob é propenso a erros e carece de segurança de tipos. O Prisma ORM fornece consultas de banco de dados type-safe e gera tipos TypeScript com apenas uma linha de código.

1. O Que Você Vai Aprender


2. Uma História Real de um Arquiteto

(1) Dor: Dados mock não podem ser persistidos

Após Bob reiniciar o servidor de desenvolvimento do MegaShop, todos os dados de produto desapareceram. Todos os produtos que Alice havia adicionado e os pedidos que ela havia feito sumiram. Para piorar, não havia banco de dados no ambiente de produção—não havia onde armazenar os dados de milhões de produtos.

(2) Solução com Prisma ORM

O Prisma fornece operações de banco de dados type-safe e gera automaticamente tipos TypeScript:

TYPESCRIPT
// Consulta type-safe - sem SQL bruto
const products = await prisma.product.findMany({
  where: { category: { slug: 'electronics' } },
  take: 20,
  skip: (page - 1) * 20
})

(3) Benefícios: Segurança de Tipos + Persistência de Dados

Os dados de produto são armazenados de forma persistente, e as consultas de API possuem inferência de tipos completa—então Bob nunca mais vai digitar errado um nome de campo, e o pedido de Alice ainda estará lá após uma reinicialização.


3. Instalação e Inicialização do Prisma

(1) ▶ Exemplo: Instalando o Prisma

BASH
npm install prisma @prisma/client
npx prisma init

Saída:

TEXT
# Comando executado com sucesso

(2) ▶ Exemplo: Schema do Prisma

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

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

model Product {
  id          Int       @id @default(autoincrement())
  name        String
  slug        String    @unique
  description String?
  price       Decimal   @db.Decimal(10, 2)
  image       String?
  inStock     Boolean   @default(true)
  rating      Float     @default(0)
  reviewCount Int       @default(0)
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  categoryId  Int
  category    Category  @relation(fields: [categoryId], references: [id])
  orderItems  OrderItem[]
  cartItems   CartItem[]

  @@index([categoryId])
  @@index([slug])
  @@index([price])
}

model Category {
  id        Int       @id @default(autoincrement())
  name      String
  slug      String    @unique
  parentId  Int?
  parent    Category? @relation("CategoryTree", fields: [parentId], references: [id])
  children  Category[] @relation("CategoryTree")
  products  Product[]

  @@index([slug])
}

model User {
  id        Int       @id @default(autoincrement())
  email     String    @unique
  name      String
  password  String?
  avatar    String?
  provider  String    @default("email")
  role      Role      @default(CUSTOMER)
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  orders     Order[]
  cartItems  CartItem[]

  @@index([email])
}

model Order {
  id          Int       @id @default(autoincrement())
  userId      Int
  user        User      @relation(fields: [userId], references: [id])
  total       Decimal   @db.Decimal(10, 2)
  status      OrderStatus @default(PENDING)
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  items       OrderItem[]

  @@index([userId])
  @@index([status])
}

model OrderItem {
  id        Int     @id @default(autoincrement())
  orderId   Int
  order     Order   @relation(fields: [orderId], references: [id])
  productId Int
  product   Product @relation(fields: [productId], references: [id])
  quantity  Int
  price     Decimal @db.Decimal(10, 2)

  @@index([orderId])
}

model CartItem {
  id        Int     @id @default(autoincrement())
  userId    Int
  user      User    @relation(fields: [userId], references: [id])
  productId Int
  product   Product @relation(fields: [productId], references: [id])
  quantity  Int     @default(1)

  @@unique([userId, productId])
}

enum Role {
  CUSTOMER
  ADMIN
}

enum OrderStatus {
  PENDING
  PAID
  SHIPPED
  DELIVERED
  CANCELLED
}

Saída:

TEXT
// Execução bem-sucedida

(1) Diagrama ER do MegaShop

100%
erDiagram
    Product ||--o{ OrderItem : "incluído em"
    Product ||--o{ CartItem : "adicionado ao"
    Product }o--|| Category : "pertence a"
    Category ||--o{ Category : "pai-filho"
    User ||--o{ Order : "faz"
    User ||--o{ CartItem : "tem"
    Order ||--o{ OrderItem : "contém"

4. Integração com Nuxt

(1) ▶ Exemplo: Conexão Singleton do Prisma

TYPESCRIPT
// server/utils/prisma.ts
import { PrismaClient } from '@prisma/client'

// Padrão Singleton - previne múltiplas instâncias em dev
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }

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

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma
}

Saída:

TEXT
// Execução bem-sucedida

(2) ▶ Exemplo: Nitro Pre-built Prisma Client

TYPESCRIPT
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    // Gerar Prisma Client antes do build
    externals: {
      inline: ['.prisma/client']
    }
  },
  hooks: {
    'build:before': async () => {
      const { execSync } = await import('child_process')
      execSync('npx prisma generate')
    }
  }
})

Saída:

TEXT
// Execução bem-sucedida

5. Operações CRUD

(1) ▶ Exemplo: Consultas Paginadas para Lista de Produtos

TYPESCRIPT
// server/api/products/index.get.ts
export default defineEventHandler(async (event) => {
  const query = getQuery(event)
  const page = Number(query.page) || 1
  const limit = Number(query.limit) || 20
  const category = query.category as string
  const search = query.search as string
  const minPrice = Number(query.minPrice) || 0
  const maxPrice = Number(query.maxPrice) || Infinity

  const where = {
    AND: [
      category ? { category: { slug: category } } : {},
      search ? { name: { contains: search, mode: 'insensitive' as const } } : {},
      { price: { gte: minPrice } },
      maxPrice < Infinity ? { price: { lte: maxPrice } } : {}
    ]
  }

  const [items, total] = await Promise.all([
    prisma.product.findMany({
      where,
      skip: (page - 1) * limit,
      take: limit,
      include: { category: { select: { name: true, slug: true } } },
      orderBy: { createdAt: 'desc' }
    }),
    prisma.product.count({ where })
  ])

  return { items, total, page, limit }
})

Saída:

TEXT
// Execução bem-sucedida

(2) ▶ Exemplo: Consulta de Detalhes do Produto

TYPESCRIPT
// server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
  const id = Number(getRouterParam(event, 'id'))

  const product = await prisma.product.findUnique({
    where: { id },
    include: {
      category: { select: { name: true, slug: true } }
    }
  })

  if (!product) {
    throw createError({ statusCode: 404, message: 'Product not found' })
  }

  return product
})

Saída:

TEXT
// Execução bem-sucedida

(3) ▶ Exemplo: Criando um Pedido (Transação)

TYPESCRIPT
// server/api/orders/index.post.ts
export default defineEventHandler(async (event) => {
  const userId = event.context.user?.id
  const { items } = await readBody(event)

  // Transação: criar pedido + atualizar estoque + limpar carrinho
  const order = await prisma.$transaction(async (tx) => {
    // Calcular total
    let total = 0
    const orderItems = []

    for (const item of items) {
      const product = await tx.product.findUnique({ where: { id: item.productId } })
      if (!product || !product.inStock) {
        throw createError({ statusCode: 400, message: `Product ${item.productId} unavailable` })
      }
      total += Number(product.price) * item.quantity
      orderItems.push({
        productId: product.id,
        quantity: item.quantity,
        price: product.price
      })
    }

    // Criar pedido
    const newOrder = await tx.order.create({
      data: {
        userId,
        total,
        items: { create: orderItems }
      },
      include: { items: { include: { product: true } } }
    })

    // Limpar carrinho do usuário
    await tx.cartItem.deleteMany({ where: { userId } })

    return newOrder
  })

  return { order, message: 'Order created successfully' }
})

Saída:

TEXT
// Execução bem-sucedida

(4) ▶ Exemplo: Consulta de Agregação—Estatísticas de Produto

TYPESCRIPT
// server/api/admin/stats.get.ts
export default defineEventHandler(async () => {
  const [
    totalProducts,
    totalUsers,
    totalOrders,
    revenue,
    avgPrice
  ] = await Promise.all([
    prisma.product.count(),
    prisma.user.count(),
    prisma.order.count(),
    prisma.order.aggregate({
      _sum: { total: true },
      where: { status: 'PAID' }
    }),
    prisma.product.aggregate({
      _avg: { price: true }
    })
  ])

  return {
    totalProducts,
    totalUsers,
    totalOrders,
    totalRevenue: revenue._sum.total || 0,
    averagePrice: avgPrice._avg.price || 0
  }
})

Saída:

TEXT
// Execução bem-sucedida

6. Migração de Banco de Dados

(1) ▶ Exemplo: Comando de migração do Prisma

BASH
# Criar migração a partir de mudanças no schema
npx prisma migrate dev --name init

# Aplicar migrações em produção
npx prisma migrate deploy

# Resetar banco de dados (apenas dev!)
npx prisma migrate reset

# Gerar Prisma Client
npx prisma generate

# Abrir Prisma Studio (navegador visual de DB)
npx prisma studio

# Popular banco de dados com dados de teste
npx prisma db seed

Saída:

TEXT
# Comando executado com sucesso

(2) ▶ Exemplo: Dados Seed

TYPESCRIPT
// prisma/seed.ts
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

async function main() {
  // Criar categorias
  const electronics = await prisma.category.create({
    data: { name: 'Electronics', slug: 'electronics' }
  })
  const clothing = await prisma.category.create({
    data: { name: 'Clothing', slug: 'clothing' }
  })

  // Criar produtos
  for (let i = 1; i <= 1000; i++) {
    await prisma.product.create({
      data: {
        name: `Product ${i}`,
        slug: `product-${i}`,
        price: Math.round(Math.random() * 500 * 100) / 100,
        categoryId: i % 2 === 0 ? electronics.id : clothing.id,
        inStock: Math.random() > 0.2,
        rating: Math.round(Math.random() * 5 * 10) / 10,
        reviewCount: Math.floor(Math.random() * 5000)
      }
    })
  }

  // Criar usuário administrador
  await prisma.user.create({
    data: {
      email: 'bob@megashop.com',
      name: 'Bob Admin',
      role: 'ADMIN'
    }
  })

  console.log('Seed completed: 1 thousand products + 2 categories + 1 admin')
}

main()

Saída:

TEXT
// Execução bem-sucedida

7. Exemplo Completo: Integração de Banco de Dados do MegaShop

TEXT
# .env
DATABASE_URL="postgresql://megashop:password@localhost:5432/megashop"
REDIS_URL="redis://localhost:6379"
JWT_ACCESS_SECRET="your-access-secret"
JWT_REFRESH_SECRET="your-refresh-secret"
TYPESCRIPT
// server/api/products/search.get.ts - Busca avançada
export default defineEventHandler(async (event) => {
  const { q, category, minPrice, maxPrice, sort, page, limit } = getQuery(event)

  const where = {
    AND: [
      q ? { OR: [{ name: { contains: q as string, mode: 'insensitive' } }, { description: { contains: q as string, mode: 'insensitive' } }] } : {},
      category ? { category: { slug: category as string } } : {},
      minPrice ? { price: { gte: Number(minPrice) } } : {},
      maxPrice ? { price: { lte: Number(maxPrice) } } : {}
    ]
  }

  const orderBy: any = sort === 'price-asc' ? { price: 'asc' }
    : sort === 'price-desc' ? { price: 'desc' }
    : sort === 'rating' ? { rating: 'desc' }
    : { createdAt: 'desc' }

  const [items, total] = await Promise.all([
    prisma.product.findMany({
      where, orderBy,
      skip: ((Number(page) || 1) - 1) * (Number(limit) || 20),
      take: Number(limit) || 20,
      include: { category: { select: { name: true, slug: true } } }
    }),
    prisma.product.count({ where })
  ])

  return { items, total, page: Number(page) || 1, limit: Number(limit) || 20 }
})

❓ Perguntas Frequentes

P Qual é a diferença entre Prisma e TypeORM?
R Prisma usa um schema declarativo e gera código client-side, oferecendo inferência de tipos mais abrangente. TypeORM usa decorators e é mais próximo de um ORM tradicional. A comunidade Nuxt 3 recomenda Prisma.
P Por que o singleton do Prisma precisa de uma variável global?
R No ambiente de desenvolvimento, o hot reload do Nuxt re-executa o código do servidor; sem uma variável global, múltiplas instâncias de PrismaClient seriam criadas, causando vazamentos de conexão. Esse problema não existe no ambiente de produção.
P Como é a performance de consultas com milhões de registros?
R O findMany do Prisma combinado com skip/take gera paginação LIMIT/OFFSET, que fica lenta quando o valor de OFFSET é grande para conjuntos de dados na casa dos milhões. Para grandes conjuntos de dados, use paginação baseada em cursor (cursor + take).
P O Prisma suporta MongoDB?
R Sim, mas é um recurso de preview. PostgreSQL é a melhor combinação do Prisma e oferece o conjunto mais completo de funcionalidades. MegaShop recomenda PostgreSQL.
P Múltiplas operações de escrita podem ser realizadas dentro de uma transação?
R Sim. Você pode realizar qualquer número de operações de leitura e escrita dentro de prisma.$transaction; a transação é commitada apenas se todas as operações forem bem-sucedidas, e revertida se qualquer uma falhar.
P Como executar o script seed?
R Adicione "prisma": { "seed": "npx ts-node prisma/seed.ts" } ao package.json, depois execute npx prisma db seed.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (Dificuldade ⭐): Instale Prisma e PostgreSQL, crie os modelos Product e Category, e execute migrate e seed.
  2. Exercício Avançado (Dificuldade: ⭐⭐): Implemente uma API CRUD completa de produto, substituindo os dados mock anteriores, e suporte paginação e filtragem por categoria.
  3. Desafio (Dificuldade: ⭐⭐⭐): Implemente uma transação de pedido—criar pedido, deduzir estoque e limpar carrinho—tudo em uma única transação; se qualquer etapa falhar, reverta toda a transação.

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%