Criando uma lista de contatos com Prisma
20 minutos para ler
Etapas
- Criação do projeto usando NextJS
- Criando o database com Prisma
- Integrando o banco com a aplicação
1 - Criação do projeto usando NextJS
Para criar o projeto usando o NextJS, vamos inicializar com o seguinte comando:
npx create-next-app@latest
Isso vai criar um projeto com a sua estrutura básica do NextJs, eu optei pelas seguintes configurações:
What is your project named? `prisma-contact-list`
Would you like to use TypeScript? `Yes`
Would you like to use ESLint? `Yes`
Would you like to use Tailwind CSS? `Yes`
Would you like to use src/ directory? `Yes`
Would you like to use App Router? (recommended) `Yes`
Would you like to customize the default import alias? `No`
Isso vai criar a estrutura necessária para o projeto, após a instalação das dependências, vamos ao VSCode e iremos apagar tudo que estiver dentro do bloco <main>
dentro de src/app/page.tsx
e deixaremos com essa estrutura.
<main className="flex min-h-screen flex-col items-center p-24 gap-6">
<h2 className="text-5xl text-white font-bold">Lista de contatos</h2>
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
<table className="w-full text-sm text-left rtl:text-right text-gray-400">
<thead className="text-xs text-gray-400 uppercase bg-gray-700">
<tr>
<th scope="col" className="px-6 py-3">
Nome
</th>
<th scope="col" className="px-6 py-3">
Telefone
</th>
<th scope="col" className="px-6 py-3">
Email
</th>
<th scope="col" className="px-6 py-3">
Empresa
</th>
<th scope="col" className="px-6 py-3">
<span className="sr-only">Editar</span>
</th>
</tr>
</thead>
<tbody>
<!-- aqui virá o loop de contatos -->
</tbody>
</table>
</div>
</main>
Essa vai ser a estrutura padrão para listar os contatos e permitir o editar (falaremos posteriormente).
2 - Criando o database com Prisma
Agora vamos instalar o Prisma no projeto e configurá-lo para criar a base e as migrations.
Para inicializar precisamos adicionar o Prisma/Cli no projeto, basta executar
npm install prisma @prisma/client
Após a instalação e suas dependências, precisamos iniciar o Prisma no projeto, basta executar
npx prisma init
Feito isso, ele vai criar um arquivo .env
na raíz do projeto parecido com esse
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail:
https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for
PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options:
https://pris.ly/d/connection-strings
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
Vamos usar o SQLite apenas para facilitar o processo, mas poderia ser uma base como MySQL, Postgree e etc, atualize o arquivo para isso
DATABASE_URL="file:./dev.db"
Pronto, a nossa base de dados foi definida, agora vamos a configuração dentro do schema do Prisma, vamos editar o arquivo prisma/schema.prisma
na raíz do projeto.
Importante, mude o provider para sqlite - pois o padrão é postgree, senão irá receber um erro ao rodar a migration.
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
Adicione no final do arquivo a estrutura do Model que será a tabela contato no projeto.
model Contact {
id Int @id @default(autoincrement())
name String
email String @unique
phone String
company String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Após rodar a migration, precisamos gerar o client para ser consumido pelo NextJS, basta rodar
npx prisma generatenpx prisma generate
Pronto, o Prisma foi criado com sucesso no projeto, agora vamos criar a API para fazer as chamadas CRUD na aplicação.
Dentro de src
crie a seguinte estrutura api/contact/route.ts
e vamos adicionar a estrutura para gerenciar as chamadas.
import { PrismaClient } from "@prisma/client";
export const config = {
api: {
bodyParser: false,
},
};
const prisma = new PrismaClient();
export async function GET() {
try {
const contacts = await prisma.contact.findMany();
return Response.json(contacts);
} catch (error) {
return Response.json({ error: "Erro ao obter contatos" });
}
}
export async function POST(req: Request) {
try {
const { name, email, phone, company } = await req.json();
const newContact = await prisma.contact.create({
data: { name, email, phone, company },
});
return Response.json(newContact);
} catch (error) {
return Response.json({ error: "Erro ao criar contato" });
}
}
export async function PUT(req: Request) {
try {
const { name, email, phone, company, id } = await req.json();
const updatedContact = await prisma.contact.update({
where: { id: Number(id) },
data: { name, email, phone, company },
});
return Response.json(updatedContact);
} catch (error) {
return Response.json({ error: "Erro ao atualizar contato" });
}
}
export async function DELETE(req: Request) {
try {
const { id } = await req.json();
await prisma.contact.delete({
where: { id: Number(id) },
});
return Response.json({ status: true });
} catch (error) {
return Response.json({ error: "Erro ao deletar contato" });
}
}```
Com essa estrutura feita, já podemos fazer a integração com a UI, vamos dentro de src/page.txt
e iremos fazer as chamadas para gerenciar a tela.
Vamos atualizar o arquivo src/page.txt
para o seguinte conteúdo
"use client";
import { useState, useEffect } from "react";
type Contact = {
id: number;
name: string;
email: string;
phone: string;
company: string;
};
export default function Home() {
const [contacts, setContacts] = useState<Contact[]>([]);
const [addingNew, setAddingNew] = useState(false);
const [isEditing, setIsEditing] = useState<number | null>(null);
const [formValues, setFormValues] = useState<Omit<Contact, "id">>({
name: "",
email: "",
phone: "",
company: "",
});
const handleFetchContacts = async () => {
const res = await fetch("/api/contacts");
const data = await res.json();
if (data) {
setContacts(data);
}
};
const handleUpdateContact = async () => {
const res = await fetch("/api/contacts", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...formValues, id: isEditing }),
});
await handleFetchContacts();
setIsEditing(null);
setFormValues({ name: "", email: "", phone: "", company: "" });
};
const handleCreateContact = async () => {
const res = await fetch("/api/contacts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formValues),
});
await handleFetchContacts();
setFormValues({ name: "", email: "", phone: "", company: "" });
};
const handleDeleteContact = async (id: number) => {
const res = await fetch("/api/contacts", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id }),
});
await handleFetchContacts();
};
useEffect(() => {
handleFetchContacts();
}, []);
return (
<main className="flex min-h-screen flex-col items-center p-24 gap-6">
<div className="flex flex-col justify-center items-center gap-4">
<h2 className="text-5xl text-white font-bold w-full flex">
Lista de contatos
</h2>
<button
onClick={() => {
setAddingNew((prevState) => !prevState);
setFormValues({ name: "", email: "", phone: "", company: "" });
setIsEditing(null);
}}
type="button"
className="
py-2.5 px-2 m-auto text-sm font-medium
focus:outline-none rounded-lg border
focus:z-10 focus:ring- focus:ring-gray-700
bg-gray-800 text-gray-400 border-gray-600
hover:text-white hover:bg-gray-700 self-start
"
>
Adicionar novo
</button>
</div>
{(addingNew || isEditing) && (
<form className="max-w-sm mx-auto flex gap-4 flex-col">
<div className="flex gap-4">
<div className="mb-5">
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Nome
</label>
<input
type="text"
id="name"
className="
border text-sm rounded-lg block w-full
p-2.5 bg-gray-700 border-gray-600
placeholder-gray-400 text-white
focus:ring-blue-500 focus:border-blue-500
"
required
onChange={(e) =>
setFormValues({ ...formValues, name: e.target.value })
}
value={formValues.name}
/>
</div>
<div className="mb-5">
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Telefone
</label>
<input
type="text"
id="phone"
className="
border text-sm rounded-lg block w-full
p-2.5 bg-gray-700 border-gray-600
placeholder-gray-400 text-white
focus:ring-blue-500 focus:border-blue-500
"
required
onChange={(e) =>
setFormValues({ ...formValues, phone: e.target.value })
}
value={formValues.phone}
/>
</div>
</div>
<div className="flex gap-4">
<div className="mb-5">
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Email
</label>
<input
type="email"
id="email"
className="
border text-sm rounded-lg block w-full
p-2.5 bg-gray-700 border-gray-600
placeholder-gray-400 text-white
focus:ring-blue-500 focus:border-blue-500
"
required
onChange={(e) =>
setFormValues({ ...formValues, email: e.target.value })
}
value={formValues.email}
/>
</div>
<div className="mb-5">
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Empresa
</label>
<input
type="text"
id="company"
className="
border text-sm rounded-lg block w-full
p-2.5 bg-gray-700 border-gray-600
placeholder-gray-400 text-white
focus:ring-blue-500 focus:border-blue-500
"
onChange={(e) =>
setFormValues({ ...formValues, company: e.target.value })
}
value={formValues.company}
/>
</div>
</div>
{isEditing ? (
<button
type="button"
className="
text-white bg-blue-700 hover:bg-blue-800
focus:ring-4 focus:outline-none focus:ring-blue-300
font-medium rounded-lg text-sm w-full sm:w-auto
px-5 py-2.5 text-center
"
onClick={handleUpdateContact}
>
Editar
</button>
) : (
<button
type="button"
className="
text-white bg-blue-700 hover:bg-blue-800
focus:ring-4 focus:outline-none focus:ring-blue-300
font-medium rounded-lg text-sm w-full sm:w-auto
px-5 py-2.5 text-center
"
onClick={handleCreateContact}
>
Criar
</button>
)}
</form>
)}
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
<table className="w-full text-sm text-left rtl:text-right text-gray-400">
<thead className="text-xs text-gray-400 uppercase bg-gray-700">
<tr>
<th scope="col" className="px-6 py-3">
Nome
</th>
<th scope="col" className="px-6 py-3">
Telefone
</th>
<th scope="col" className="px-6 py-3">
Email
</th>
<th scope="col" className="px-6 py-3">
Empresa
</th>
<th scope="col" className="px-6 py-3">
<span className="sr-only">Editar</span>
</th>
</tr>
</thead>
<tbody>
{contacts?.map((contact, index) => (
<tr
className="
border-b bg-gray-800
border-gray-700
hover:bg-gray-600
"
key={index}
>
<th
scope="row"
className="px-6 py-4 font-medium whitespace-nowrap text-white"
>
{contact.name}
</th>
<td className="px-6 py-4">{contact.phone}</td>
<td className="px-6 py-4">{contact.email}</td>
<td className="px-6 py-4">{contact.company}</td>
<td className="px-6 py-4 text-right flex gap-4">
<a
onClick={() => {
setIsEditing(contact.id);
setFormValues({
name: contact.name,
email: contact.email,
phone: contact.phone,
company: contact.company,
});
}}
className="font-medium text-tertiary hover:underline"
>
Editar
</a>
<a
onClick={() => {
setIsEditing(null);
setFormValues({
name: contact.name,
email: contact.email,
phone: contact.phone,
company: contact.company,
});
handleDeleteContact(contact.id);
}}
className="font-medium text-tertiary hover:underline"
>
Excluir
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
</main>
);
}
Feito isso já verá funcionando o projeto utilizando o Prisma com NextJS.
Clique aqui para ver o projeto no GitHub.