Fundamentos do Next.js 15

0/26 aulas0%
teoria

Server Actions: Mutação de Dados no Servidor

Aprenda sobre server actions: mutação de dados no servidor

35 min
Aula 1 de 5

Módulo 3: Gerenciamento de Dados e Interatividade

Aula: Server Actions: Mutação de Dados no Servidor 🚀

Bem-vindos à aula sobre Server Actions! Esta é uma funcionalidade poderosa no Next.js que simplifica a mutação de dados e a execução de lógica no servidor, diretamente a partir de interações do cliente.


1. Introdução: O Desafio da Mutação de Dados no Servidor

Tradicionalmente, em aplicações web, quando você precisa realizar uma ação que modifica dados persistentes (como adicionar um item a um banco de dados, atualizar um perfil de usuário, etc.), o fluxo geralmente é o seguinte:

  1. O usuário interage com um formulário ou botão no cliente.
  2. O cliente envia uma requisição HTTP (POST, PUT, DELETE) para um endpoint de API (REST ou GraphQL).
  3. O servidor processa a requisição, interage com o banco de dados e retorna uma resposta.
  4. O cliente recebe a resposta e atualiza sua UI conforme necessário.

Este modelo funciona, mas pode envolver a criação de endpoints de API separados, gerenciamento de estados de carregamento e erro no cliente, e a necessidade de fetch ou bibliotecas como axios para cada interação.

Server Actions chegam para simplificar esse processo no Next.js App Router. Elas permitem que você defina funções que são executadas diretamente no servidor, mas que podem ser invocadas a partir de eventos do cliente, como a submissão de um formulário.

Em resumo, Server Actions permitem:

  • Chamar funções de servidor diretamente de componentes React.
  • Mutar dados no servidor de forma segura e eficiente.
  • Reduzir a necessidade de endpoints de API REST/GraphQL explícitos para mutações simples.
  • Manter a lógica sensível no servidor, longe do cliente.

2. Explicação Detalhada: Como Funcionam as Server Actions

Server Actions são funções assíncronas que você marca explicitamente para serem executadas no servidor. Elas podem ser definidas em qualquer lugar, mas são mais comumente usadas em componentes de servidor, componentes de cliente ou em arquivos separados de utilitários de servidor.

2.1. A Diretiva "use server"

A chave para transformar uma função comum em uma Server Action é a diretiva "use server".

  • No topo de um arquivo: Se você colocar "use server" no topo de um arquivo, todas as funções exportadas nesse arquivo serão Server Actions. Isso é ideal para arquivos de utilitários que contêm apenas lógica de servidor.

    // app/lib/actions.ts
    'use server'; // Todas as funções exportadas aqui são Server Actions
     
    import { revalidatePath } from 'next/cache';
    import { db } from './db'; // Supondo um módulo de banco de dados
     
    export async function createPost(formData: FormData) {
      const title = formData.get('title') as string;
      const content = formData.get('content') as string;
     
      if (!title || !content) {
        return { error: 'Título e conteúdo são obrigatórios.' };
      }
     
      await db.post.create({ data: { title, content } });
      revalidatePath('/blog'); // Revalida o cache da página do blog
      return { success: true };
    }
     
    export async function deletePost(id: string) {
      await db.post.delete({ where: { id } });
      revalidatePath('/blog');
    }
  • Dentro de uma função: Você pode colocar "use server" dentro do corpo de uma função específica. Isso permite que você defina Server Actions dentro de arquivos que também contêm lógica de cliente ou outras funções não-server.

    // app/components/PostForm.tsx
    // Este é um componente de cliente, mas a Server Action está aninhada
     
    import { revalidatePath } from 'next/cache';
    import { db } from '@/app/lib/db'; // Supondo um módulo de banco de dados
     
    export default function PostForm() {
      async function handleCreatePost(formData: FormData) {
        'use server'; // Apenas esta função é uma Server Action
        const title = formData.get('title') as string;
        const content = formData.get('content') as string;
     
        if (!title || !content) {
          // Lógica de erro ou retorno
          return;
        }
     
        await db.post.create({ data: { title, content } });
        revalidatePath('/blog');
      }
     
      return (
        <form action={handleCreatePost}>
          <input type="text" name="title" placeholder="Título" />
          <textarea name="content" placeholder="Conteúdo"></textarea>
          <button type="submit">Criar Post</button>
        </form>
      );
    }

2.2. Integração com Formulários HTML

A maneira mais comum de invocar uma Server Action é através da prop action de um elemento <form>.

<form action={yourServerActionFunction}>
  {/* Campos do formulário */}
  <button type="submit">Enviar</button>
</form>

Quando o formulário é submetido, o Next.js intercepta a submissão e chama a yourServerActionFunction no servidor. Os dados do formulário são automaticamente empacotados em um objeto FormData e passados como o primeiro argumento para a Server Action.

2.3. Acesso aos Dados do Formulário (FormData)

Server Actions chamadas por formulários recebem um objeto FormData como seu primeiro argumento. Você pode acessar os valores dos campos do formulário usando formData.get('nomeDoCampo').

async function myServerAction(formData: FormData) {
  'use server';
  const username = formData.get('username'); // Valor do input com name="username"
  const password = formData.get('password'); // Valor do input com name="password"
  // ... lógica para processar username e password
}

2.4. Retorno de Dados e Tratamento de Erros

Server Actions podem retornar qualquer valor serializável que será recebido no cliente. Isso é útil para feedback de sucesso/erro ou para retornar dados atualizados.

// app/lib/actions.ts
'use server';
 
export async function updateUserEmail(userId: string, newEmail: string) {
  try {
    // Lógica para atualizar o email no banco de dados
    // ...
    return { success: true, message: 'Email atualizado com sucesso!' };
  } catch (error) {
    console.error('Erro ao atualizar email:', error);
    return { success: false, message: 'Falha ao atualizar email.' };
  }
}
 
// No componente cliente (ex: usando useFormState)
import { useFormState } from 'react-dom';
import { updateUserEmail } from '@/app/lib/actions';
 
function ProfileSettings({ userId, currentEmail }) {
  const [state, formAction] = useFormState(
    (prevState, formData) => updateUserEmail(userId, formData.get('email') as string),
    { success: false, message: '' } // Estado inicial
  );
 
  return (
    <form action={formAction}>
      <input type="email" name="email" defaultValue={currentEmail} />
      <button type="submit">Atualizar Email</button>
      {state.message && <p>{state.message}</p>}
    </form>
  );
}

2.5. Revalidação de Cache (revalidatePath, revalidateTag)

Após uma mutação de dados no servidor, a UI do cliente pode precisar ser atualizada para refletir as mudanças. Server Actions se integram perfeitamente com as funções de revalidação de cache do Next.js:

  • revalidatePath(path: string): Revalida o cache para um caminho específico. Útil quando você sabe que uma mutação afeta os dados exibidos em uma determinada URL.
  • revalidateTag(tag: string): Revalida o cache para uma tag de dados específica. Útil quando você busca dados com fetch e os associa a uma tag, e a mutação afeta esses dados.
// app/lib/actions.ts
'use server';
 
import { revalidatePath } from 'next/cache';
import { db } from './db';
 
export async function addTodo(todoText: string) {
  await db.todo.create({ data: { text: todoText } });
  revalidatePath('/todos'); // Garante que a página /todos seja re-renderizada com o novo todo
}

2.6. Segurança

Server Actions são executadas no servidor, o que significa que a lógica sensível (como acesso a banco de dados, chaves de API) permanece segura e não é exposta ao cliente. Além disso, o Next.js automaticamente adiciona proteção contra ataques CSRF (Cross-Site Request Forgery) para Server Actions usadas com formulários.

2.7. useFormStatus e useFormState (Hooks de Cliente)

Embora esta seja uma aula teórica sobre a mutação em si, é importante mencionar dois hooks do React (disponíveis no Next.js) que melhoram a experiência do usuário com Server Actions:

  • useFormStatus: Permite que componentes clientes leiam o estado de submissão de um formulário (se está pendente, se o formulário foi enviado, etc.) para exibir feedback de UI (ex: desabilitar um botão).
  • useFormState: Permite que você gerencie o estado de um formulário com base no resultado de uma Server Action, retornando um novo estado para o cliente.

3. Código de Exemplo Oficial (Adaptado)

Vamos ver um exemplo prático de como criar um item "todo" usando uma Server Action.

// app/todos/page.tsx
// Este é um Server Component que exibe a lista de todos e o formulário.
 
import { revalidatePath } from 'next/cache';
import { db } from '@/app/lib/db'; // Supondo que você tenha um módulo de banco de dados simples
 
// Ações do servidor para manipular os todos
// Definimos as ações em um arquivo separado para melhor organização
// app/lib/actions.ts
// 'use server'; // Já está no topo do arquivo actions.ts
 
// import { revalidatePath } from 'next/cache';
// import { db } from './db';
 
export async function addTodo(formData: FormData) {
  'use server'; // Esta função é uma Server Action
  const todoText = formData.get('todo') as string;
 
  if (!todoText || todoText.trim() === '') {
    // Poderíamos retornar um objeto de erro aqui, ou lançar um erro
    console.error('O texto do todo não pode ser vazio.');
    return;
  }
 
  await db.todo.create({
    data: { text: todoText.trim(), completed: false },
  });
 
  // Revalida o cache da página '/todos' para mostrar o novo item
  revalidatePath('/todos');
}
 
export async function deleteTodo(id: string) {
  'use server'; // Esta função é uma Server Action
  await db.todo.delete({ where: { id } });
  revalidatePath('/todos');
}
 
// ====================================================================
 
// Componente principal da página /todos
export default async function TodosPage() {
  const todos = await db.todo.findMany(); // Busca todos os todos do banco de dados
 
  return (
    <div className="max-w-md mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">Minha Lista de Tarefas 📝</h1>
 
      <form action={addTodo} className="flex gap-2 mb-6">
        <input
          type="text"
          name="todo"
          placeholder="Adicionar nova tarefa..."
          className="flex-grow p-2 border border-gray-300 rounded-md"
          required
        />
        <button
          type="submit"
          className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md"
        >
          Adicionar
        </button>
      </form>
 
      <ul className="space-y-3">
        {todos.map((todo) => (
          <li
            key={todo.id}
            className="flex items-center justify-between bg-gray-100 p-3 rounded-md shadow-sm"
          >
            <span className={todo.completed ? 'line-through text-gray-500' : ''}>
              {todo.text}
            </span>
            <form action={deleteTodo}>
              <input type="hidden" name="id" value={todo.id} />
              <button
                type="submit"
                className="bg-red-500 hover:bg-red-600 text-white text-sm py-1 px-3 rounded-md"
              >
                Remover 🗑️
              </button>
            </form>
          </li>
        ))}
      </ul>
    </div>
  );
}
 
// ====================================================================
// Exemplo de como 'db' e 'todo' poderiam ser definidos (simulação)
// app/lib/db.ts
// import { PrismaClient } from '@prisma/client'; // Exemplo com Prisma
// export const db = new PrismaClient();
 
// Ou uma simulação simples para fins de exemplo:
// type Todo = { id: string; text: string; completed: boolean };
// let todos: Todo[] = []; // Array em memória para simular um DB
 
// export const db = {
//   todo: {
//     findMany: async () => {
//       // Simula um delay de DB
//       await new Promise(resolve => setTimeout(resolve, 100));
//       return todos;
//     },
//     create: async (data: { data: { text: string; completed: boolean } }) => {
//       await new Promise(resolve => setTimeout(resolve, 100));
//       const newTodo = { id: String(todos.length + 1), ...data.data };
//       todos.push(newTodo);
//       return newTodo;
//     },
//     delete: async (where: { id: string }) => {
//       await new Promise(resolve => setTimeout(resolve, 100));
//       todos = todos.filter(todo => todo.id !== where.id);
//       return { id: where.id }; // Retorna o ID do item deletado
//     }
//   }
// };

Neste exemplo:

  • As funções addTodo e deleteTodo são Server Actions, marcadas com "use server".
  • Elas recebem o FormData do formulário ou um ID diretamente.
  • Elas interagem com um db simulado (ou real, como Prisma).
  • Após a mutação, revalidatePath('/todos') é chamado para garantir que a página /todos seja re-renderizada com os dados mais recentes.
  • Os formulários action={addTodo} e action={deleteTodo} invocam essas Server Actions diretamente.

4. Resumo e Próximos Passos

Resumo da Aula:

  • Server Actions são funções executadas no servidor, mas invocadas a partir do cliente, simplificando a mutação de dados.
  • A diretiva "use server" marca uma função ou um arquivo inteiro como Server Action.
  • Elas se integram nativamente com elementos <form> através da prop action.
  • Recebem FormData com os dados do formulário e podem retornar valores serializáveis.
  • São essenciais para revalidar o cache do Next.js (com revalidatePath e revalidateTag) após a mutação.
  • Oferecem segurança intrínseca, mantendo a lógica sensível no servidor e prevenindo CSRF.

Server Actions são uma ferramenta poderosa que reduz a complexidade de gerenciar APIs e mutações, permitindo que você escreva lógica de servidor diretamente onde ela é usada na UI.

Próximos Passos:

  • Explore os hooks useFormStatus e useFormState para adicionar feedback visual e gerenciar o estado da UI durante e após as Server Actions.
  • Aprofunde-se em como Server Actions podem ser usadas para autenticação e autorização, interagindo com serviços de backend.
  • Considere a integração de Server Actions com bibliotecas de validação de esquema (como Zod) para garantir a integridade dos dados antes da mutação.

Parabéns por concluir esta aula sobre Server Actions! Você deu um grande passo para dominar a mutação de dados no Next.js 15. 🎉

© 2025 Escola All Dev. Todos os direitos reservados.

Server Actions: Mutação de Dados no Servidor - Fundamentos do Next.js 15 | escola.all.dev.br