🗄️ 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.
🎯 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.
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:
# 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
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
# Instalar Prisma CLI e Client
npm install prisma @prisma/client
# Instalar driver PostgreSQL
npm install pg
npm install -D @types/pgPasso 2: Inicializar Prisma
# Inicializar Prisma no projeto
npx prisma initEste 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:
# 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 especiaisPasso 4: Configurar 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 e Migrations
Passo 1: Executar Primeira Migration
# 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 atualizadoPasso 2: Schema Completo para Aplicação
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
# Aplicar mudanças do schema
npx prisma migrate dev --name add-posts-comments
# Verificar status das migrations
npx prisma migrate statusPasso 4: Configurar Prisma Client
// 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
Operações CRUD com Prisma
Route Handler para Users
// 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
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
// 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.