Pular para o conteúdo
🎯 Aula 07

🗄️ Prisma ORM + Neon Database

Configure um banco de dados PostgreSQL moderno com Neon e integre o Prisma ORM para gerenciar dados de forma type-safe e eficiente em sua aplicação Next.js.

40 min
Intermediário
Prático

🎯 Por que isso é importante

Prisma é o ORM mais popular do ecossistema Node.js, usado por 89% dos desenvolvedores TypeScript para banco de dados. Neon oferece PostgreSQL serverless com auto-scaling, perfeito para aplicações modernas. Juntos, criam a stack de dados mais robusta e type-safe do mercado.

⚠️ Conceitos Importantes para Entender

ORM (Object-Relational Mapping):

Ferramenta que traduz dados entre sistemas incompatíveis usando linguagens orientadas a objetos, eliminando SQL manual.

Type Safety:

Garantia de que os tipos de dados estão corretos em tempo de compilação, prevenindo erros em produção.

Serverless Database:

Banco que escala automaticamente baseado no uso, sem gerenciamento de infraestrutura manual.

Schema Migration:

Processo de versionar e aplicar mudanças na estrutura do banco de dados de forma controlada.

🌐 Neon Setup📦 Prisma Install🗂️ Schema & Models⚡ CRUD Operations
configuracao-neon

Configurando Neon Database

🎯 O que é Neon?

Neon é um PostgreSQL serverless que separa computação de armazenamento, oferecendo auto-scaling, branching de banco e cold starts instantâneos.

Passo 1: Criar Conta no Neon

1. Acesse https://neon.tech

2. Clique em "Sign Up"

3. Use GitHub, Google ou email para criar conta

4. Confirme seu email se necessário

Passo 2: Criar Novo Projeto

1. No dashboard, clique "Create Project"

2. Escolha nome do projeto: nextjs-curso-app

3. Selecione região mais próxima (ex: US East)

4. PostgreSQL version: 17 (mais recente)

5. Clique "Create Project"

Passo 3: Obter Connection String

Após criar o projeto, você verá a connection string. Copie ela:

connection-string-example
# Exemplo de connection string do Neon
postgresql://username:password@ep-example-123456.us-east-1.aws.neon.tech/neondb?sslmode=require

✅ Vantagens do Neon

  • 🚀 Serverless: Escala automaticamente baseado no uso
  • ⚡ Cold Starts: Ativação instantânea após inatividade
  • 🌿 Branching: Crie branches do banco como no Git
  • 💰 Cost-Effective: Pague apenas pelo que usar
  • 🔒 Seguro: SSL por padrão e isolamento completo
prisma-install

Instalação e Configuração do Prisma

🎯 Por que Prisma?

Prisma oferece type safety completa, auto-completion inteligente, migrations automáticas e uma query engine otimizada para performance máxima.

Passo 1: Instalar Dependências

terminal
# Instalar Prisma CLI e Client
npm install prisma @prisma/client

# Instalar driver PostgreSQL
npm install pg
npm install -D @types/pg

Passo 2: Inicializar Prisma

terminal
# Inicializar Prisma no projeto
npx prisma init

Este comando cria a pasta prisma/com o arquivo schema.prisma e adiciona DATABASE_URL no .env

Passo 3: Configurar Variáveis de Ambiente

Adicione sua connection string do Neon no arquivo .env:

.env
# Database
DATABASE_URL="postgresql://username:password@ep-example-123456.us-east-1.aws.neon.tech/neondb?sslmode=require"

# Substitua pela sua connection string real do Neon
# Mantenha as aspas para evitar problemas com caracteres especiais

Passo 4: Configurar Schema Prisma

prisma/schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

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

// 🎯 Modelo de exemplo - User
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("users")
}

⚠️ Pontos Importantes

  • @id @default(cuid()): ID único usando CUID
  • @unique: Garante email único no banco
  • @@map("users"): Nome da tabela no banco
  • DateTime @default(now()): Timestamp automático
schema-models

Schema e Migrations

Passo 1: Executar Primeira Migration

terminal
# Criar e aplicar migration
npx prisma migrate dev --name init

# Este comando:
# 1. Cria arquivo de migration em prisma/migrations/
# 2. Aplica mudanças no banco Neon
# 3. Gera o Prisma Client atualizado

Passo 2: Schema Completo para Aplicação

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

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

// 👤 Modelo User
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  avatar    String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  // Relacionamentos
  posts     Post[]
  comments  Comment[]

  @@map("users")
}

// 📝 Modelo Post
model Post {
  id          String   @id @default(cuid())
  title       String
  content     String?
  published   Boolean  @default(false)
  slug        String   @unique
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  
  // Relacionamentos
  authorId    String
  author      User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
  comments    Comment[]

  @@map("posts")
}

// 💬 Modelo Comment
model Comment {
  id        String   @id @default(cuid())
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  
  // Relacionamentos
  authorId  String
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)
  postId    String
  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)

  @@map("comments")
}

Passo 3: Aplicar Nova Migration

terminal
# Aplicar mudanças do schema
npx prisma migrate dev --name add-posts-comments

# Verificar status das migrations
npx prisma migrate status

Passo 4: Configurar Prisma Client

lib/prisma.ts
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'

// 🎯 Singleton pattern para evitar múltiplas conexões
const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

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

🔧 Comandos Úteis do Prisma

  • npx prisma studio: Interface visual do banco
  • npx prisma db push: Sync schema sem migration
  • npx prisma generate: Regenerar client
  • npx prisma db seed: Popular banco com dados
crud-operations

Operações CRUD com Prisma

Route Handler para Users

app/api/users/route.ts
// app/api/users/route.ts 
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'

// 📖 GET - Listar usuários
export async function GET() {
  try {
    const users = await prisma.user.findMany({
      select: {
        id: true,
        name: true,
        email: true,
        createdAt: true, 
      },
      orderBy: {
        createdAt: 'desc',
      },
    })

    return NextResponse.json(users)
  } catch (error) {
    console.error('Erro ao buscar usuários:', error)
    return NextResponse.json(
      { error: 'Erro interno do servidor' },
      { status: 500 }
    )
  }
}

// ✏️ POST - Criar usuário
export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { name, email } = body

    // Validação básica
    if (!email) {
      return NextResponse.json(
        { error: 'Email é obrigatório' },
        { status: 400 }
      )
    }

    const user = await prisma.user.create({
      data: {
        name,
        email,
      },
    })

    return NextResponse.json(user, { status: 201 })
  } catch (error) {
    console.error('Erro ao criar usuário:', error)
      

    return NextResponse.json(
      { error: 'Erro interno do servidor' },
      { status: 500 }
    ) 
  }
}                 
 

Route Handler para Posts

app/api/posts/route.ts
// app/api/posts/route.ts
 // app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'

// 📖 GET - Listar posts
export async function GET() {
  try {
    const posts = await prisma.post.findMany({
      include: {
        author: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        }, 
      },
      orderBy: {
        createdAt: 'desc',
      },
    })

    return NextResponse.json(posts)
  } catch (error) {
    console.error('Erro ao buscar posts:', error)
    return NextResponse.json(
      { error: 'Erro interno do servidor' },
      { status: 500 }
    )
  }
}

// ✏️ POST - Criar post
export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const { title, content, authorId, published = false } = body

    if (!title || !authorId) {
      return NextResponse.json(
        { error: 'Título e autor são obrigatórios' },
        { status: 400 }
      )
    }

    // Gerar slug único
    const slug = title
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/(^-|-$)/g, '')

    const post = await prisma.post.create({
      data: {
        title,
        content,
        slug: `${slug}-${Date.now()}`,
        published,
        authorId,
      },
      include: {
        author: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    })

    return NextResponse.json(post, { status: 201 })
  } catch (error) {
    console.error('Erro ao criar post:', error)
    return NextResponse.json(
      { error: 'Erro interno do servidor' },
      { status: 500 }
    )
  }
}

Componente para Testar CRUD

components/PrismaTest.tsx
// app/teste/page.tsx
// components/PrismaTest.tsx
'use client'

import { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'

interface User {
  id: string
  name: string | null
  email: string
  createdAt: string 
}

export default function PrismaTest() {
  const [users, setUsers] = useState<User[]>([])
  const [loading, setLoading] = useState(false)
  const [newUser, setNewUser] = useState({ name: '', email: '' })

  // 📖 Buscar usuários
  const fetchUsers = async () => {
    setLoading(true)
    try {
      const response = await fetch('/api/users')
      const data = await response.json()
      setUsers(data)
    } catch (error) {
      console.error('Erro ao buscar usuários:', error)
    } finally {
      setLoading(false)
    }
  }

  // ✏️ Criar usuário
  const createUser = async () => {
    if (!newUser.email) return

    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(newUser),
      })

      if (response.ok) {
        setNewUser({ name: '', email: '' })
        fetchUsers() // Recarregar lista
      }
    } catch (error) {
      console.error('Erro ao criar usuário:', error)
    }
  }

  useEffect(() => {
    fetchUsers()
  }, [])

  return (
    <div className="space-y-6">
      <Card>
        <CardHeader>
          <CardTitle>🧪 Teste do Prisma + Neon</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          <div className="flex gap-2">
            <Input
              placeholder="Nome"
              value={newUser.name}
              onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
            />
            <Input
              placeholder="Email"
              type="email"
              value={newUser.email}
              onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
            />
            <Button onClick={createUser}>Criar</Button>
          </div>

          <Button onClick={fetchUsers} disabled={loading}>
            {loading ? 'Carregando...' : 'Recarregar'}
          </Button>

          <div className="space-y-2">
            {users.map((user) => (
              <div key={user.id} className="p-3 border rounded">
                <p><strong>{user.name || 'Sem nome'}</strong></p>
                <p className="text-sm text-fg-dim">{user.email}</p> 
              </div>
            ))}
          </div>
        </CardContent>
      </Card>
    </div>
  )
}

✅ O que Você Conquistou

  • 🗄️ Banco Moderno: PostgreSQL serverless com Neon
  • 🔒 Type Safety: Prisma garante tipos corretos
  • ⚡ Performance: Query engine otimizada
  • 🔄 Migrations: Versionamento automático do schema
  • 🎯 Produção Ready: Stack profissional completa

🚀 Continue Sua Jornada

Você agora tem uma base sólida de dados com Prisma + Neon. Na próxima aula, vamos implementar autenticação avançada e conectar com nossos modelos de dados.

Database ExpertType SafeORM MasterServerless Ready