Next: Server Actions aprendendo na prática

Next: Server Actions aprendendo na prática
Patrícia Silva
Patrícia Silva

Compartilhe

O Next.js se destaca como uma escolha popular para quem desenvolve web, oferecendo uma solução eficiente para renderização no servidor (SSR) e geração de sites estáticos (SSG). Com o lançamento da versão 14, uma nova funcionalidade tem roubado a cena: Server Actions. Essa característica elimina a necessidade de rotas de API dedicadas, permitindo a execução de código no servidor diretamente do Next.js. Isso abre um leque de possibilidades para as pessoas desenvolvedoras, desde a busca de dados em APIs externas até a manipulação de lógica de negócios complexa, tudo de forma simplificada e integrada.

Este artigo explora o que são Server Actions, como aplicá-las e apresenta casos de usos práticos.

O que são Server Actions?

Imagine que você está em um restaurante e decide pedir um prato que não está no cardápio. Você chama o garçom e faz o pedido. O que acontece nos bastidores você não sabe, mas o garçom, vai até a cozinha e resolve tudo, sem que você precise se preocupar com os detalhes. Se necessário, ele até pode buscar o pedido em outro restaurante, garantindo que você receba o que deseja. Isso é como ir buscar dados em APIs externas, sem afetar a segurança ou performance, pois podemos executar as Server Actions, diretamente sem a necessidade de implementar a camada de servidor para lidar com API. Lembrando que podemos implementar normalmente a camada de servidor e ter API’s, então Server Actions é flexível, onde podemos chamar API interna e externas.

Agora, pense em quando você preenche um formulário em um site. Você clica em enviar e espera que a mensagem chegue ao destino. Essa ação é como enviar uma carta sem precisar ir aos correios. O formulário vai direto para quem precisa, sem passar por intermediários, simplificando todo o processo, ou seja, é como executar lógicas de negócios que necessitam da execução no servidor, como o caso do processamento dos dados do formulário no servidor, mas sem precisar implementar os detalhes desse servidor.

Atualizar um banco de dados sem complicações é como chegar rapidamente ao destino sem a necessidade de fazer todo o roteiro, simplificando toda a operação, ou seja, lidar com a conexão com o banco de dados, de forma segura, os tokens de autorização e demais elementos sensíveis permanecem protegidos no lado do servidor, uma prática comum no desenvolvimento back-end. No entanto, é possível estabelecer essa conexão com banco de dados através das Server Actions que moram no mesmo repositório que nossos componentes React.

Banner de divulgação da Imersão IA da Alura em colaboração com o Google. Mergulhe em Inteligência artificial com a Alura e o Google. Serão cinco aulas gratuitas para você aprender a usar IA na prática e desenvolver habilidades essenciais para o mercado de trabalho. Inscreva-se gratuitamente agora!

E como as Server Actions funcionam?

As Server Actions do Next.js são como funções de JavaScript assíncronas que operam no servidor em resposta a interações do usuário no cliente. Por exemplo, ao enviar um formulário para salvar um usuário, uma Server Action é acionada. O servidor executa essa ação e retorna a resposta para o Next.js, que a envia de volta para o front-end.

A Sintaxe para Definir Server Actions

Você pode definir Server Actions no próprio componente do servidor ou em arquivos separados para reutilização. Basta adicionar a diretiva "use server" no início da função.

// Server Component
export default function ServerComponent( ) {
            // Server Action
    async function minhaAction( ) {
        'use server'
        // ... implementação
            }

   return ( … )
} 

Ou em um arquivo separado para reutilização, essencialmente elas são funções. Isto significa que podem ser reutilizadas em qualquer parte da sua aplicação, onde podemos utilizar tanto em Client Components, quanto em Server Components.

// app/actions.ts
'use server'

export async function minhaAction( ) {
    //... implementação
}

Client Components

Client Components só podem importar ações que utilizam a diretiva "use server" no nível do módulo.

Para chamar uma Server Action em um Client Component:

'use client'

import { minhaAction } from ‘@/app/actions’

export default function ClientComponent() {
  return <form action={ minhaAction }>{/* ... */}</form>
}

Bom saber: mais detalhes na documentação sobre Client Components clique aqui.

Vamos mergulhar na prática!

Como dizem que mar calmo não faz bom marinheiro, então vamos agitar um pouco as coisas. Antes de mais nada, este tutorial começa com um código-base que simula um ambiente real, onde frequentemente nos deparamos com este tipo de situação. Aqui, vamos concentrar-nos nas Server Actions, otimizando nosso tempo ao invés de começarmos do absoluto zero.

Instale as dependências:

yarn

Com a instalação concluída, abra o projeto no seu editor de código favorito e prepare-se para a ação!

A estrutura do projeto é basicamente esta:

app/
  ├── lib/
  │   └── types.ts
  ├── api/
  │   └── users
  │       └── route.ts
  ├── components/
  │   ├── UserFormClientSide.tsx
  │   ├── UserFormServerSide.tsx
  │   └── UserList.tsx
  ├── page.tsx
  └── layout.tsx
public/
   └── data.json
  • /app: Contém todas as rotas, componentes e lógica para da aplicação, é aqui que vamos trabalhar a maior parte do tempo.
  • /app/components: Contém todos os componentes que usaremos para criar a interface de usuário.
  • /app/api/users/route.ts: Contém a lógica de API usada para buscar os usuários de nosso banco de dados mockado.
  • /public/data.json: Contém nossos dados de usuários - servirá como nosso banco de dados mockado.

Sem mais delongas, vamos codar!!!

Primeiro, vamos ajustar o componente app/components/UserList.tsx, para fazer o fetch para o endpoint de users:

const users = await fetch("http://localhost:3000/api/users", {
    next: { tags: ["User"] },
  });

Note que estamos associando a esta requisição fetch() uma tag - "User", que irá invalidar o cache. Vamos utilizar essa tag mais tarde, quando quisermos buscar novamente nossos usuários atualizados, após cadastrar um novo usuário no banco de dados.

O código do componente UserList.tsx, agora deve estar assim:

import React from "react";
import { User } from "../lib/types";

export default async function UserList() {
  const users = await fetch("http://localhost:3000/api/users", {
    next: { tags: ["User"] },
  });

  const { data } = await users.json();

  return (
    <div className="max-w-xl mx-auto pt-10">
      <h1 className="text-4xl font-bold mb-5">Lista de usuários</h1>
      <ul>
        {data.map((user: User) => (
          <li
            key={user.id}
            className="bg-gray-800 p-4 rounded-lg mb-2 flex justify-between"
          >
            <div>{user.name}</div>
            <div className="bg-purple-400 text-gray-900 px-2 py-1 rounded text-sm">
              {user.birthday}
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

Já temos o componente UserList.tsx fazendo a requisição para o endpoint de users.

Muito bem, agora temos que importá-lo no arquivo app/page.tsx, que é página inicial, além de incluí-lo dentro de <main> o <UserList />. Ficando desta forma:

import UserList from "./components/UserList";

export default function Home() {
  return (
    <main>
      <UserList />
    </main>
  );
}

Vamos aproveitar que estamos aqui e vamos adicionar um <Link> logo acima do componente <UserList /> . Dessa forma, posteriormente, seremos capazes direcionar para a rota que faz a inclusão de um novo usuário:

import Link from "next/link";
import UserList from "./components/UserList";

export default function Home() {
  return (
    <main>
      <Link
        href=""
        className="block mx-auto text-1xl font-bold mb-5 w-1/2 mt-8 w-80 text-center bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 round"
      >
        Cadastrar novo usuário
      </Link>
      <UserList />
    </main>
  );
}

Agora inicie a aplicação com comando: yarn dev.

A aplicação será executada localmente em: http://localhost:3000. O resultado da página deverá ser este:

Já existem alguns usuários salvos no arquivo mockado do nosso banco de dados, que está localizado em: /public/data.json, por isso, foram renderizados alguns usuários na lista.

Formulário criar novo usuário

Vamos avançar mais uma etapa, agora é o momento de ajustar o formulário para criar um novo usuário.

Vamos lançar mão da joia rara, Server Actions! Desta forma, podemos criar um novo usuário no nosso banco de dados!

Vá para o componente UserFormServerSide.tsx, eis que vamos conhecer a anatomia do nosso formulário, existem dois inputs um para o nome(name) e outro para inserir o aniversário(birthday) do usuário, por último mas não menos importante, o botão do tipo submit que faz a mágica acontecer.

Sabendo disto, agora crie um diretório chamado user na raiz do diretório app/, dentro deste diretório user, crie outro diretório com o nome create, e então adicione um arquivo com nome page.tsx.

A estrutura de diretórios ficará assim:


└── app/
    └── user/
        └── create/
            └── page.tsx

Maravilha! Agora temos uma nova rota: http://localhost:3000/user/create

Mas ainda está faltando adicionar os componentes, a fim de renderizarmos o formulário, então vamos popular essa rota com o código a seguir:

import UserFormServerSide from "@/app/components/UserFormServerSide";

export default function UserFormPage() {
  return <UserFormServerSide />;
}

Show! Agora temos uma página que renderiza o formulário para criar um novo usuário, juntamente com a data de seu aniversário.

No entanto, precisamos voltar na app/page.tsx e linkar a nova página que acabamos de criar.

Atualize o href para: href="/user/create", ficando desta forma:

<Link
        href="/user/create"
        className="block mx-auto text-1xl font-bold mb-5 w-1/2 mt-8 w-80 text-center bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded"
      >
        Cadastrar novo usuário
      </Link>

Muito bem, quando clicarmos no botão cadastrar novo usuário, vamos redirecionar o usuário para a rota que criamos anteriormente: http://localhost:3000/user/create. O resultado desta página deverá ser este:

Adicionando o Server Action

Temos que fazer o post deste formulário para inserir os dados nos bancos de dados. No arquivo UserFormServerSide.tsx, existe uma função chamada addUser() que atua como nosso garçom do lado do servidor, é o Server Action. Ela está marcada com "use server" para garantir que não seja executada no lado do cliente, mantendo nossas operações seguras e eficientes, pois serão executadas na camada de servidor:

const addUser = async (data: FormData) => {
    "use server";
  //restante da implementação…
}

A seguir, veja que pegamos os dados enviados do formulário e associamos as variáveis: “name” e “birthday”, em seguida criamos o objeto “newUserBody”.

 // Lógica para inserir os dados do form...
  const name = data.get("name")?.toString();
  const birthday = data.get("birthday")?.toString();

  const newUserBody = {
    name,
    birthday,
  };

E fazemos o “post” deste objeto para a API user, a fim de salvar os dados no banco de dados.

Depois do novo usuário ser enviado para a nossa base de dados, utilizamos revalidateTag("User") para atualizar os dados, inclusive teremos o novo usuário recém adicionado.

Por fim, navegamos de volta para a página inicial e podemos conferir o novo usuário adicionado!

// Post user para o mock database
  await axios.post("http://localhost:3000/api/users", newUserBody);
  // Refetch usuários
  revalidateTag("User");

  redirect("/");

No entanto, falta integrar a ação addUser ao formulário e dar vida à nossa aplicação!

Abra o arquivo UserFormServerSide.tsx, onde começa a tag <form>, adicione a action a função do nosso Server Action ‘addUser’:

<form
   action={addUser}
   className="space-y-6 p-10 bg-gray-800 rounded-md"
>

Como Obi-Wan já disse para Luke:

Seus olhos podem te enganar, não confie neles.

Se tudo estiver funcionando como esperado, você deve observar o seguinte:

  1. Ao enviar o formulário, você será redirecionado para a rota “/”.
  2. Você poderá visualizar o novo usuário adicionado ao final da lista 🎯

Se você chegou até aqui! Parabéns ⭐️⭐️⭐️⭐️⭐️! Você implementou com êxito sua primeira Server Action!

Vale Lembrar que Server Actions não são limitadas a <form> e podem ser invocadas a partir de manipuladores de eventos, useEffect, bibliotecas de terceiros e outros elementos de formulário, como <button>.

Organizando o Server Action

Vamos organizar nosso Server Action e deixar o componente com uma estrutura mais limpa. No componente UserFormServerSide.tsx, copie a função adduser().

Agora, crie um novo diretório dentro de app/ com o nome actions/ e em seguida crie o arquivo user.ts. Cole a Server Action addUser() para dentro do arquivo user.ts e inclua os imports necessários. Lembre-se de adicionar o “use server” no início do arquivo:

"use server";
import axios from "axios";
import { revalidateTag } from "next/cache";
import { redirect } from "next/navigation";

export const addUser = async (data: FormData) => {
  // Logica para inserir os dados do form...
  const name = data.get("name")?.toString();
  const birthday = data.get("birthday")?.toString();
  const newUserBody = {
    name,
    birthday,
  };
  // Post user para o mock database
  await axios.post("http://localhost:3000/api/users", newUserBody);
  // Refetch User's
  revalidateTag("User");

  redirect("/");
};

Em seguida volte no arquivo UserFormServerSide.tsx, apague o addUser() e os imports não utilizados, e por fim faça o import do addUser() que acabamos de mover:

// UserFormServerSide.tsx
import { addUser } from "../actions/user";

Teste novamente no navegador para assegurar que a aplicação continua a funcionar corretamente.

Testando Server Actions em Client Component

Temos o componente UserFormClientSide.tsx, navegue para o arquivo app/user/create/page.tsx, substitua o <UserFormServerSide />, por <UserFormClientSide />, que do qual, é nosso Client Component que contém a lógica para gerenciar dados dos formulário, utilizando useState, que é permitido apenas em Client Components.

E faça o teste, cadastre um novo usuário, onde a submissão do formulário deverá continuar a funcionar corretamente.

Outros casos de usos para Server Actions

As Server Actions podem ser utilizadas em diversos cenários para otimizar nossas aplicações Next.js. Por exemplo:

  • Autenticação e autorização: Valide as credenciais do usuário e controle o acesso a recursos específicos da aplicação no servidor.
  • Gerenciamento de dados: Buscar, atualizar e excluir dados de bancos de dados ou APIs de forma segura e eficiente, sem expor informações importantes, como tokens, etc.
  • Processamento de pagamentos: Implemente transações seguras sem expor informações confidenciais do cliente.
  • Geração de relatórios e documentos: Crie relatórios dinâmicos e personalizados no servidor.
  • Integração com API externas: Utilize as Server Actions para uma integração direta com APIs externas, eliminando a necessidade de desenvolver um API route interno. Com essa abordagem, é possível acessar dados de forma direta, ao mesmo tempo em que se mantêm informações críticas protegidas e seguras, realizando todas as operações relevantes no lado do servidor.

Casos de Uso Avançados

  1. API Proxy: Server Actions podem agir como um proxy para APIs externas, ocultando chaves de API e aplicando lógica adicional.
  2. Processamento e agregação de dados de múltiplas APIs: Server Actions podem ser usadas para coletar dados de várias APIs externas, processá-los no servidor e então enviar um conjunto consolidado e otimizado de informações para o cliente. Isso não só melhora a eficiência da transferência de dados ao reduzir o tamanho do payload, mas também permite implementar uma camada de lógica de negócios que pode enriquecer os dados antes de serem exibidos ao usuário. Por exemplo, você poderia ter uma Server Action que consulta APIs de clima, tráfego e notícias, combina todos esses dados em uma resposta consolidada em um dashboard por exemplo, enquanto também realiza cache dessas informações para melhorar a performance.
  3. Customização de respostas de API com base no usuário: Server Actions podem ser configuradas para modificar as respostas de APIs externas com base nas preferências ou no histórico de um usuário. Por exemplo, uma aplicação de um e-commerce poderia usar Server Actions para chamar uma API de produtos e, em seguida, personalizar a lista de produtos com base no histórico de navegação ou compra do usuário, oferecendo uma experiência de usuário mais personalizada.

Existem uma infinidade de casos de usos onde a Server Actions nos ajudam a implementar de forma robusta, eficiente e prática. Com isso, podemos focar na lógica de negócios, em vez de dedicar tempo excessivo ao desenvolvimento de estratégias para gerenciar nossas integrações. Esta abordagem otimiza nosso fluxo de trabalho, garantindo que a atenção esteja voltada para o que realmente importa: agregar valor ao nosso negócio e melhorar a experiência do usuário.

Se quiser conhecer mais sobre Server Actions clique aqui e leia a documentação.

Conclusão

As Server Actions são uma adição poderosa ao Next.js, proporcionando segurança, desempenho e simplicidade. Elas nos permitem focar na lógica de negócios, sem perder tempo com infraestrutura complexa. Experimente e compartilhe suas experiências com essa nova funcionalidade!

Se precisar consultar o código modificado, consulte aqui no Github. Através dessa comparação, você consegue visualizar as alterações específicas entre o projeto base e o projeto finalizado.

Espero que esta introdução às Server Actions tenha sido útil e que você aproveite ao máximo essa nova funcionalidade do Next.js!

Se quiser se aprofundar no assunto

Aqui na Alura, temos vários conteúdos sobre Next, vamos conhecê-las?

Aproveite e veja os conteúdos que a comunidade Next divulgou na última conferência.

Compartilhando o que você aprendeu

E vamos lançar um desafio! Se você gostou desse artigo, compartilhe nas redes sociais o que você aprendeu com ele com a hashtag #AprendinoBlogdaAlura.

Patrícia Silva
Patrícia Silva

Sou Engenheira de Software, atualmente atuando com Fullstack Engineer, baseada em Portugal. Sou uma pessoa técnica entusiasmada que ama tecnologia. Trabalho como desenvolvedora web a mais de 14 anos. Ajudo desenvolvedores a melhorar suas habilidades, estou aberta para trocas de conhecimento com qualquer pessoa. No meu tempo livre sou mãe de plantas e de dois meninos. Adoro viajar, conhecer novas pessoas e estar em contato c/ a natureza. Foco e cuidado com a Experiência do usuário é o segredo do sucesso.

Veja outros artigos sobre Front-end