NextJS - Criando um site fullstack com autenticação e database
Published: 2023-05-17 19:26:14

## Introdução
Neste artigo quero ensinar você a criar uma aplicação fullstack com NextJS. Registro, login, armazenamento de dados… tudo o que vem no pacote! Será, mais especificamente, uma aplicação CRUD (Create, Read, Update and Delete), isto é, um app que permite ao usuário criar, ler, atualizar e deletar informações guardadas em um servidor.

Decidi escrevê-lo porque, quando pesquisei pela primeira vez sobre o assunto, me deparei com tutoriais “para iniciantes” que atiravam no aluno **inúmeros nomes e ferramentas sem justificar as escolhas ou explicar adequadamente que função elas teriam na aplicação.**
> *“Então você instala esse pacote e escreve essa linha aqui e agora o usuário pode logar!”*


Ok, está funcionando, **MAS EU NÃO ENTENDI `/[bad-word]/g` NENHUMA!**


Compreendendo a dor de quem também está começando, tentarei ser o mais didático possível, explicando cada uma das partes móveis que eu introduzir.

Ao mesmo tempo, aviso que o propósito é criar o **esqueleto** de uma aplicação fullstack. Vou utilizar como base o conceito de uma lista de tarefas, mas você pode criar o que quiser enquanto lê o artigo.

Levando isso em conta, não me preocuparei com detalhes de funcionamento do React nem com estilização de componentes. Você vai aprender o que interessa: criar um banco de dados e autenticar o usuário.


### O que eu preciso saber?

* Conhecimento básico para intermediário de React e Next.

* O básico da criação de rotas de API em Javascript.

* O básico de requests de APIs com Javascript.

O primeiro requisito é essencial. Você pode tentar seguir sem os outros dois, mas sugiro que dê uma olhada no assunto antes de seguir se ainda não tem ideia de como eles funcionam.


### O que utilizaremos?

* NextJS

* NextAuth

* Prisma

* MongoDB

* BCrypt

* Axios

* SWR


## Ambientação

Eu gosto de ir direto ao ponto, mas é preciso compreender minimamente as principais ferramentas que nós utilizaremos. Como afirmei acima, existem inúmeros tutoriais de criação de aplicações semelhantes à que criaremos aqui. Mas a grande maioria peca por não justificar a escolha nem explicar a natureza dos instrumentos necessários para fazê-la funcionar.

Sugiro, portanto, que você tenha paciência para se ambientar lendo os capítulos desta seção antes de pular para a parte prática.


### As principais ferramentas

Nós queremos criar um sistema que permitirá ao usuário criar uma conta, salvar informações nessa conta e, depois, logar nessa conta a qualquer tempo. A nossa aplicação, portanto, tem três necessidades.


#### 1. Um local para guardar informações

Precisamos de um banco de dados, ou **database**. Isso é fácil… mas nem tanto. Aqui já nos deparamos com um dilema. Queremos uma database SQL ou NoSQL? O que é isso?

SQL significa **Structured Query Language**, ou seja, SQL é primeiramente uma linguagem de programação. Dizemos que uma database é SQL quando todas as consultas e manipulações de dados são feitas em SQL.

A linguagem foi criada na década de 1970 e é até hoje padrão da indústria. Não por acaso. O modelo de organização utilizado em bancos SQL (modelo relacional) ainda é efetivo e relevante em praticamente qualquer cenário.

Bancos relacionais estruturam os dados em tabelas, com colunas representando propriedades e linhas representando itens individuais nos quais podemos declarar alguma relação com outros itens. Se quero organizar uma coleção de guitarras, posso criar uma nova tabela `GUITARS` e adicionar colunas para classificar cada item da minha coleção:

![](https://cdn-images-1.medium.com/max/2000/1*k155K86xBs6dnauO-uhSdw.png)

A sintaxe também é relativamente simples: `CREATE TABLE` cria uma nova tabela, `SELECT TABLE` seleciona uma tabela para realizarmos as pesquisas, ou ***queries***.

Tudo isso foi dito para situar o caro leitor, porque nós não utilizaremos SQL. Ao invés disso, **criaremos a nossa database em um modelo NoSQL**, que significa **Not Only SQL.**

E faremos assim por um ótimo motivo. É mais fácil para quem vem do front-end. O modelo relacional não só possui certas peculiaridades e conceitos (como a criação de relações entre as chaves da tabelas) que merecem artigos individuais, como ele utiliza… SQL. Outra linguagem. Com modelos NoSQL, como veremos, podemos fazer uso de algo que já conhecemos muito bem: Javascript.

E o que são modelos NoSQL? Eles surgiram no fim década de 2000 como alternativas ao modelo tradicional. Não são organizados em tabelas e linhas, mas possuem outros tipos de estrutura, como Documents e Graphs.

Nós utilizaremos o modelo de **Documents**, que nada mais é que o velho conhecido objeto JSON (você vai ouvir falar em BSON em alguns casos, inclusive no nosso, mas saiba que as maiores diferenças são, em favor do BSON, melhor performance e maior suporte a certos tipos de valores. A sintaxe é a mesma).

Como esse modelo funciona? Ao invés de tabelas com linhas e colunas, temos Collections com Documents. Cada Collection é como uma tabela, e cada linha como um Document. Se acima criamos uma tabela para organizar as guitarras, com o novo modelo criaremos uma Collection. Se cada linha na tabela correspondia a uma guitarra individual, no novo modelo cada guitarra corresponderá a um Document. Ao final, os mesmos dados ficariam organizados assim:

    //Collection Guitars
    //Você vai entender o que são esses ObjectId mais à frente.
    
    {
     "id": 1,
     "name": "SG",
     "brand": "Gibson",
     "color": "Cherry",
    },
    {
     "id": 2,
     "name": "Telecaster",
     "brand": "Fender",
     "color": "Sunburst",
    }

Assim é fácil visualizar, para quem já conhece a sintaxe de objetos do Javascript, os dados de cada entidade no banco de dados.

Nós criaremos a nossa database com a MongoDB, que utiliza o modelo NoSQL de Document e oferece uma opção gratuita adequada para a nossa aplicação. Cada usuário do app será um objeto como o exemplificado acima, com propriedades para e-mail, senha etc.


#### 2. Uma ponte entre a aplicação e a database

Ótimo! Sabemos que precisamos de um banco de dados. E daí? *Como eu boto as coisa lá?*

Para estabelecer comunicação entre a nossa aplicação e a database, poderíamos escrever as ***queries*** com o próprio cliente da Mongo. Mas utilizaremos uma alternativa para facilitar o nosso trabalho. É aqui que entra o Prisma.

Prisma é um ORM (Object Relational Mapper), isto é, uma ferramenta que fará, por trás dos panos, o trabalho de construir, acessar e manipular os dados da database. É mais ou menos o que o React é para o Javascript em relação à manipulação do DOM. Com o Prisma, poderemos escrever códigos mais declarativos e menos imperativos. Ele não funciona apenas com a MongoDB, porque a função do Prisma é justamente abstrair as especificidades de cada database e fazê-las funcionar com a própria sintaxe.

Ele desempenhará, na nossa aplicação, duas funções:

* Declaração dos esquemas dos documentos, para definir, por exemplo, que o Document de usuário será composto de um e-mail (string), uma senha (string) e uma lista de tarefas (array de strings). Pense em um **schema** como uma ***blueprint*** do Document.

* Servirá como cliente para manipular a database, disponibilizando funções para facilmente alterar, criar e remover dados do banco.


#### 3. Um meio de verificar se o usuário está logado e se pode acessar as informações da própria conta.

Agora, só precisamos lidar com a autenticação do usuário para proteger as nossas páginas e obter os dados do usuário logado.

Nós utilizaremos uma lib chamada NextAuth para este serviço. O NextAuth oferece meios de suprir as duas necessidades acima com funções que nos ajudam a registrar um novo usuário e a verificar o status de autenticação. Além disso, o NextAuth possui suporte para o Prisma, o que significa que ele usará o cliente e os esquemas do Prisma para essas tarefas.


## Começando os trabalhos

Crie o seu projeto Next. Eu utilizei a versão 13.2.4 para escrever este artigo. Vamos instalar as dependências fundamentais do projeto.


### Instalando o Prisma

Aqui vai uma dica: instale a extensão do **Prisma** para o VSCode se quiser dicas de sintaxe e linting no seu código.

1. Começamos instalando as dependências necessárias para rodar o Prisma com `npm i -D prisma`. “-D” porque nós a instalaremos apenas como uma dependência de desenvolvimento.

1. Depois, iniciaremos o prisma com `npx prisma init`. Isto criará uma pasta chamada `prisma` com um arquivo chamado `schema.prisma` na raiz do app.

1. Por fim, instalaremos o pacote do PrismaClient com `npm i @prisma/client`.

No `schema.prisma`, modificaremos o provider para `provider = "mongodb"`. O arquivo deve estar assim:

    generator client {
      provider = "prisma-client-js"
    }
    
    datasource db {
      provider = "mongodb"
      url      = env("DATABASE_URL")
    }

Agora, criaremos uma pasta chamada `lib` na pasta raiz do projeto. Em `lib`, colocaremos alguns códigos reutilizáveis em diversos lugares na aplicação. O primeiro deles será a criação de uma instância do PrismaClient. Como usaremos o Prisma várias vezes, é útil deixar o código pronto para importá-lo conforme as nossas necessidades.

Para o Next especificamente há um adendo. Cada instância do Prisma é uma nova conexão com o servidor. Por conta do modo como funciona o hot reload do Next, precisamos definir essa instância em uma variável global para evitar que, durante o desenvolvimento, novas instâncias sejam criadas a cada compilação e gerem um erro.

O snippet abaixo serve justamente para isso: se estivermos em um abiente de desenvolvimento, utilizaremos uma instância global do Prisma. É uma solução que encontrei [nesta issue](https://github.com/prisma/prisma/issues/5103) levantada no repositório do Prisma no Github.

Deste modo, em um novo arquivo chamado `prismadb.js`, na pasta `lib`, inserimos:

    import { PrismaClient } from "@prisma/client";
    
    let prismadb;
    if (process.env.NODE_ENV === "production") {
     prismadb = new PrismaClient();
    } else {
     if (!global.prismadb) {
      global.prismadb = new PrismaClient();
     }
     prismadb = global.prismadb;
    }
    export default prismadb;

A partir de agora, utilizaremos o PrismaClient com a variável `prismadb`.


### Criando a database com MongoDB

Vá até o [site da MongoDB](https://www.mongodb.com/) e crie uma conta. Uma vez logado, procure pela opção de criar database. Escolha a opção **free.** Crie um usuário e senha para acessar a database, selecione a opção de conexão de ambiente local e insira o seu IP.

Na próxima página, você deve procurar pelo botão “connect”. Escolha a opção de conexão pelo VSCode. O link providenciado será utilizado para que o Prisma se conecte à database.

Quando instalamos o Prisma, ele deve ter criado um arquivo `.env` no diretório raiz da aplicação. Se não criou, você mesmo pode criar. O propósito deste arquivo é possibilitar declaração de variáveis disponíveis apenas no servidor, as chamadas **variáveis de ambiente.** Perceba que o link gerado possui o seu usuário e que você deve preencher <password> com a sua senha. Não queremos mostrar este link ao cliente (browser), por isso nós os colocamos no `.env`, que ficará assim:

    DATABASE_URL="<link_disponibilizado>"

Para acessar esta variável na aplicação, basta colocarmos, no lugar do valor, process.env.DATABASE_URL. No momento da compilação ou do build do projeto, o Next se encarregará de substituir a variável pelo que foi definido no `.env`.

Durante o desenvolvimento, faremos este processo para todas as variáveis de ambiente. Na produção, após o deploy, lembre-se de que você deve configurar todas as variáveis no seu serviço de hosting. Normalmente é fácil localizar esta opção.

Ah! Provavelmente o seu projeto já possui isso, mas é bom checar. No arquivo `.gitignore`, verifique se `.env `está declarado, para evitar que as suas variáveis sejam guardadas no Github.

## Criando os models

Os models serão a estrutura das entidades que queremos guardar na database. Definiremos aqui que um usuário, por exemplo, tem e-mail, senha, lista de tarefas etc. O que faremos é **definir os esquemas** da database.
> *Esquemas, modelos, qual a diferença?*

Basicamente, esquemas são a estrutura na database, e modelos a estrutura na aplicação. Porque o Prisma cria os esquemas segundo os modelos definidos na aplicação, criar um o modelo é criar o esquema.

Os modelos serão criados no arquivo `schema.prisma`, criado pelo Prisma na raiz do projeto. Certifique-se de que o seu arquivo esteja assim:

    generator client {
      provider = "prisma-client-js"
    }
    
    datasource db {
      provider = "mongodb"
      url      = env("DATABASE_URL")
    }
    // Os modelos vão aqui.

### User model

Começamos criando um model para User. Tenha em mente que o definido aqui se traduzirá em um objeto BSON como o visto acima, no exemplo das guitarras.

    model User {
    
    }

A primeira coisa que faremos é definir um ID para identificar usuários únicos. O Prisma gerará automaticamente um ID com o seguinte código, que poderá ser reutilizado em todos os modelos:

    id String @id @default(auto()) @map("_id") @db.ObjectId

Note que a definição do modelo é tipada. O Prisma utiliza Typescript para garantir a precisão dos dados inseridos. Se tentássemos registrar um id como número, obteríamos um erro.

Vamos delinear o código acima.

* id: é o nome da propriedade.

* String: o tipo da propriedade.

* @id: é um atributo do Prisma que define esta propriedade como **chave primária** do documento. Chaves primárias são identificadores únicos para uma entidade. No nosso caso, estamos dizendo que cada User terá o ***id*** como seu identificador único. Podemos definir mais de uma propriedade como única, mas apenas uma pode ser a chave primária.

* @default(auto()): é um atributo do Prisma que gera com esta função auto() um id único como valor padrão para esta propriedade.

* @map(”_id”): por este atributo o Prisma está indicando que o campo “id” deverá gerar, na MongoDB, um campo “_id”, porque “_id” é o nome padrão da chave primária na MongoDB.

* @db.ObjectId: na MongoDB, o id possui um tipo “ObjectId”. Este atributo apenas diz que o campo “_id” usará o tipo específico “ObjectId”.

Ufa! O lado bom é que só escreveremos essa linha uma vez. Todos os models poderão usar o mesmo modo de geração para um id único.

Agora temos a liberdade de decidir outras propriedades para o nosso user. Quero um nome, um e-mail, uma foto de perfil, uma lista de tarefas… vamos adicioná-las:

    model User {
      id String @id @default(auto()) @map("_id") @db.ObjectId
      name String? 
      email String? @unique
      image String?
      hashedPassword String?
      createdAt DateTime @default(now())
      updatedAt DateTime @updatedAt
      todoList String[] 
      doneList String[]
      accounts Account[]
    }

Definir um tipo com “?” ao final significa que aquele campo não é obrigatório. Pode parecer que qualquer usuário registrado deve ter ao menos e-mail e senha, mas mais à frente também disponibilizaremos registro por OAuth (login através do Google, Github, Facebook etc.). Nesses casos, o usuário não precisa preencher esses campos.

Você também deve ter percebido que inserimos um campo “accounts”, tipado como array de Account, no nosso modelo de User. O que fizemos foi referenciar o modelo Account, que ainda não criamos, mas que servirá para guardar os dados de quem se registrar por OAuth. É uma array porque podemos, se quiser, permitir que um único usuário logue por mais de um método.

### Account model

Este modelo cuidará dos casos de autenticação OAuth. Não é preciso se preocupar com os detalhes aqui. O código abaixo é fornecido pela documentação do NextAuth para utilizar a library com o Prisma.

    model Account {
        id String @id @default(auto()) @map("_id") @db.ObjectId
        userId String @db.ObjectId
        type String
        provider String
        providerAccountId String
        refresh_token String? @db.String
        access_token String? @db.String
        expires_at Int? 
        token_type String?
        scope String?
        id_token String? @db.String
        session_state String?
    
        user User @relation(fields: [userId], references: [id], onDelete: Cascade) 
        
        @@unique([provider, providerAccountId])
    }

O único ponto que merece comentário é a relação que criamos entre User e Account. Perceba como a linha `user` cria esta relação. Definimos que `user` é um dado do tipo User. Quem é este user? Para obter a resposta, definimos um campo (`userId`) no próprio modelo Account que referenciará outro campo (`id`) no modelo User. `userId `deverá ter o mesmo valor que id no modelo User. Assim, o Prisma sabe que `user `é um objeto do tipo User cujo `id `é igual a `userId`.

A opção `onDelete `está marcada como `Cascade`, o que significa que, se deletarmos uma Account, deletaremos o User.

Terminamos de definir os modelos! Para finalizar esta estapa, execute `npx prisma db push`, e os nossos modelos já estarão na MongoDB. Sugiro entrar na database e verificar se existem duas novas Collections com os nomes User e Account.

## Configurações do NextAuth

Começamos instalando o Next Auth com `npm i next-auth`.

Vamos instalar também o Prisma Adapter do NextAuth com `npm i @next-auth/prisma-adapter`

Toda a “máquina” do NextAuth é uma API criada com o próprio Next. Os passos a seguir são exigidos pela documentação do NextAuth.

Crie, dentro da pasta “api” do Next, uma pasta chamada “auth”. Dentro de auth, crie um arquivo chamado `[...nextauth].js`. Este é o arquivo de configuração do NextAuth. Nele definiremos as configurações globais e os providers (meios de registro).

A base deste arquivo será assim (não se esqueça dos imports):

    import NextAuth from "next-auth"
    import { PrismaAdapter } from "@next-auth/prisma-adapter";
    import prismadb from "@/lib/prismadb";
    
    export const authOptions = {
     adapter: PrismaAdapter(prismadb),
      providers: [],
    }
    
    export default NextAuth(authOptions)

Apenas uma nota: os meus imports que começam com ‘@’, como o do prismadb, são [custom paths](https://nextjs.org/docs/advanced-features/module-path-aliases) adicionados no `jsconfig.json`. Isto é opcional. Importe os seus arquivos conforme as rotas da **sua aplicação.**

O primeiro provider que adicionaremos será Credentials, isto é, autenticação por credenciais (usuário e senha).

1. Importe Credentials: `` import Credentials from "next-auth/providers/credentials"`` ;

2. Adicione Credentials como um provider.

   

```
providers: [
      Credentials({
       id: "credentials",
       name: "credentials",
       credentials: {
        email: {
         label: "E-mail",
         type: "text",
        },
        password: {
         label: "Senha",
         type: "password",
        },
       },
```

Os campos `id` e `name` servirão para identificar o provider quando fizermos uma request para esta API. Mais tarde, diremos “tente logar com estes dados utilizando ‘credentials’. Os campos `label`e `type` são utilizados pelo NextAuth para criar um formulário padrão de login. Nós vamos criar um formulário próprio, mas é bom fazer essa configuração por segurança.

### Lógica de login

O que faremos agora é definir a lógica de login com credenciais. Criaremos, neste mesmo objeto de configuração de Credentials, uma função assíncrona do NextAuth chamada `authorize()` para encontrar o usuário na database.

Como o encontraremos? Utilizando o PrismaClient, cuja instância criamos acima. O PrismaClient possui um método chamado `findUnique`. Com ele, dizemos “onde” procurar (where) e o que procurar, e então ele retorna o objeto encontrado:

    const user = await prismadb.user.findUnique({
    	where: {
    		email: credentials.email,
    	},
    });

A função `auhtorize()` é basicamente um handler de API, ao qual enviaremos os dados de login (email e senha) e do qual obteremos uma resposta, que será o usuário.

Você notará que estamos utilizando uma função chamada `compare()` para verificar a senha do usuário ao declarar a variável `isCorrectPassword`. Quando criarmos a lógica de registro, usaremos um pacote chamado `bcrypt`para criptografar as senhas. A função compare() é uma função do próprio `bcrypt`para fazer a validação com base na senha criptografada.

Assim, instale bcrypt com `npm i bcrypt` e importe compare: `import { compare } from "bcrypt"`.

A função `auhtorize()` deverá ser adicionada como outro objeto de Credentials:

    // ...
    // type: "password",
    //    },
    //   },
    
    async authorize(credentials) {
        try {
    
         //Lidamos com o caso de ausência das credenciais
         if (!credentials.email || !credentials.password) {
          throw new Error("É preciso preencher o usuário e a senha.");
         }
    
         //Busca um usuário na db
         const user = await prismadb.user.findUnique({
          where: {
           email: credentials.email,
          },
         });
    
         //Se não encontra, retorna um erro.
         if (!user) {
          throw new Error("O usuário não existe.");
         }
    
         //Se o usuário existe, mas não possui senha, significa que ele se registrou com outro método.
         if (!user.hashedPassword) {
          throw new Error(
           "Parece que você se registrou com um método diferente."
          );
         }
    
         // Verifica se o password enviado no login bate com o password criptografado na database.
         const isCorrectPassword = await compare(
          credentials.password,
          user.hashedPassword
         );
    
         if (!isCorrectPassword) {
          throw new Error("Senha incorreta.");
         }
    
         return user;
        } catch (error) {
         throw new Error(error.message);
        }
       },

Você pode estar se perguntando: “ok, mas como eu enviarei essas informações? De onde elas sairão?”. O próprio NextAuth oferece uma função para enviar os dados de login chamada `signIn`. Nós a criaremos mais tarde, e passaremos `credentials` e os dados de login como argumentos para que a lógica criada acima seja utilizada.

Depois do snippet acima, falta definirmos a estratégia para manter a sessão do usuário. Nós utilizaremos JWT, que significa *JSON Web Token*. Quando o usuário logar, a aplicação criará este token como um cookie no browser. O NextAuth lerá esse cookie a cada acesso e decidirá se a sessão é válida ou não.

Como tudo o que deve permanecer em segredo, definiremos a chave de geração do JWT nas variáveis de ambiente. Você decide o que será esse segredo. Uma boa sugestão é bater a cabeça no teclado e gerar uma chave segura, ou utilizar outros geradores confiáveis.

    // ...
    // throw new Error(error.message);
    //    }
    //   },
    
    session: {
      strategy: "jwt",
     },
     jwt: {
      secret: process.env.NEXTAUTH_JWT_SECRET,
     },
     secret: process.env.NEXTAUTH_SECRET,

E então iremos ao arquivo `.env` e declararemos as chaves criadas.

    NEXTAUTH_JWT_SECRET="c56C+XzTJicxQZBdP3bViDf+ARmHJpFYy7prOg3Ji/wdx9uIxdpMLaL+JYgOkU"
    NEXTAUTH_SECRET="h393jmE)_-o3mopdmnh#*4jh0m3JSlasl,*5nksp08nmjls@,lsjdej3pa9#çsj932"

Por fim, vamos definir uma página padrão para onde o usuário será redirecionado caso tente acessar uma rota protegida, ou fazer o login e o servidor não validar a sessão. Você pode definir a página que quiser aqui (/signin, por exemplo):

    //...
    // secret: process.env.NEXTAUTH_SECRET,
    pages: {
    	signIn: "/signin",
    },

Ah! Se quiser depurar o seu código de forma que os logs apareçam no prompt local, pode adicionar esta propriedade também, depois de pages:

    debug: process.env.NODE_ENV === "development",

O nosso `[...nextauth].js` ficará assim (por enquanto):

    import NextAuth from "next-auth";
    import Credentials from "next-auth/providers/credentials";
    import { PrismaAdapter } from "@next-auth/prisma-adapter";
    
    import { compare } from "bcrypt";
    import prismadb from "@/lib/prismadb";
    
    export const authOptions = {
     adapter: PrismaAdapter(prismadb),
     providers: [
      Credentials({
       id: "credentials",
       name: "credentials",
       credentials: {
        email: {
         label: "E-mail",
         type: "text",
        },
        password: {
         label: "Senha",
         type: "password",
        },
       },
       async authorize(credentials) {
        try {
         if (!credentials.email || !credentials.password) {
          throw new Error("É preciso preencher o usuário e a senha.");
         }
    
         const user = await prismadb.user.findUnique({
          where: {
           email: credentials.email,
          },
         });
    
         if (!user) {
          throw new Error("O usuário não existe.");
         }
    
         if (!user.hashedPassword) {
          throw new Error(
           "Parece que você se registrou com um método diferente."
          );
         }
    
         const isCorrectPassword = await compare(
          credentials.password,
          user.hashedPassword
         );
    
         if (!isCorrectPassword) {
          throw new Error("Senha incorreta.");
         }
    
         return user;
        } catch (error) {
         throw new Error(error.message);
        }
       },
      }),
     ],
    
     pages: {
      signIn: "/signin",
    
     },
    
     debug: process.env.NODE_ENV === "development",
    
     session: {
      strategy: "jwt",
     },
     jwt: {
      secret: process.env.NEXTAUTH_JWT_SECRET,
     },
     secret: process.env.NEXTAUTH_SECRET,
    };
    export default NextAuth(authOptions);

### Lógica de registro

Para registrar um usuário, vamos criar uma API na nossa aplicação. Se você não sabe criar APIs com Next, saiba que é como criar APIs com Node, com o adendo de que as rotas e controles são definidos no mesmo arquivo dentro da pasta API. Se você não sabe criar APIs com Node, sugiro que dê uma olhada no assunto antes de continuar.

Na pasta API, então, criaremos uma rota para o registro com um arquivo chamado `register.js`. Utilizaremos o cliente do Prisma para verificar a existência do usuário e registrá-lo, caso ele não exista, com o método `create`, que recebe um objeto `data{}` como argumento (os nossos dados):

    const user = await prismadb.user.create({
     data: {
      name,
      email,
      hashedPassword,
     },
    });

Faremos uso, também, do `bcrypt` mencionado acima, para enviar a senha já criptografada à database.

Esta criptografia será feita com a função `hash` do pacote, que recebe uma string e um número como argumentos. A string é o que será criptografado, e o número a quantidade de vezes que a função será executada sobre essa string para criptografá-la. Valores maiores podem significar maior segurança, mas também significam mais recursos computacionais utilizados. O valor 12 nos dá um bom equilíbrio entre segurança e performance.

Nesta parte, devemos decidir **o que** será registrado. Se você fez como eu e adicionou apenas e-mail, senha e nome como dados relevantes para o modelo User, então queremos registrar aqui apenas estes campos.

No arquivo `register.js`, portanto, criaremos a seguinte API:

    import bcrypt from "bcrypt";
    import prismadb from "@lib/prismadb";
    
    export default async function handler(req, res) {
     // Se o método não for POST, retorna 405 (método não permitido).
     if (req.method !== "POST") {
      return res.status(405).end();
     }
    
     try {
     // Desestrutura os dados enviados no body da requisição
      const { name, email, password } = req.body;
    
     // Se, por algum acaso, a validação do formulário não funcionar, a API garante que pelo menos o Nome, a Senha e o Email estão preenchidos para o registro.
      if (!name || !password || !email) {
       return res.status(401).json({ error: "É preciso preencher todos os campos." });
      }
    
     // findUnique seleciona, dentro da collection User, o usuário cujo e-mail seja igual ao e-mail passado na requisição. Definir apenas "email" é uma abreviação de "email: email".
      const existingUser = await prismadb.user.findUnique({
       where: {
        email,
       },
      });
    
      if (existingUser) {
       return res.status(422).json({ error: "Este e-mail já foi cadastrado." });
      }
    
     // Criptografa a senha
      const hashedPassword = await bcrypt.hash(password, 12);
    
     // Cria o usuário na database
      const user = await prismadb.user.create({
       data: {
        name,
        email,
        hashedPassword,
       },
      });
    
      return res.status(200).json(user);
     } catch (error) {
      return res.status(400).json({ error: "Algo deu errado :(" });
     }
    }

## Criando o formulário

Já configuramos o login e o registro, mas ainda não proporcionamos um meio para que o usuário realize essas ações.

Como o propósito deste artigo é ensiná-lo a manejar o login e o registro de dados em databases, não me preocuparei em explicar os detalhes de criação de formulários com React, como funciona o controle de states etc., mas apontarei o que for relevante para a sua criação. Eu também providenciei o código completo de um formulário abaixo para você ter uma base.

A primeira coisa de que precisaremos é um state para definir se o formulário é de registro ou login. Você pode fazer um formulário para cada situação. Eu prefiro criar um só e mudar este state para definir qual das funções será chamada:

    const [formType, setFormType] = useState("login");

O essencial mesmo é um state para armazenar os valores dos inputs (se você está seguindo o artigo passo a passo, bastam esses campos):

    const [formData, setFormData] = useState({
    	name: "",
    	email: "",
    	password: "",
    });

E uma função para onChange de cada input, de forma que os campos preenchidos tenham o mesmo valor de formData.

     function handleChange(e) {
      setFormData((prev) => {
       return { ...prev, [e.target.id]: e.target.value };
      });
     }

Por fim, um botão para enviar os dados às funções de registro e login (que criaremos a seguir):

    
     function handleSubmit(e) {
      // Não se esqueça de definir este preventDefault
      // para evitar o reload da página quando o usuário clicar no botão
      e.preventDefault();
      if (formType === "register") {
       register();
      } else if (formType === "login") {
       login();
      }
     }

Se quiser, pode criar desde já um botão que registrará o usuário com o Google e outro com o Github. Nós lidaremos com essas opções mais à frente.

Eis um formulário que criei, se você quiser uma representação visual:

![](https://cdn-images-1.medium.com/max/2000/1*GV7XFimFjR7LdkoylwwcYQ.png)

O código completo ficaria mais ou menos assim (sem estilos):

    
    export default function Form() {
    
     const [warning, setWarning] = useState("warning");
    
     // State que define o tipo do formulário
     const [formType, setFormType] = useState("login");
    
     // State que guarda os valores dos inputs
     const [formData, setFormData] = useState({
      name: "",
      email: "",
      password: "",
     });
    
     function handleChangeFormType(type) {
      setWarning("warning");
      setFormType(type);
     }
    
     // Função que muda o state para guardar os inputs
     function handleChange(e) {
      setFormData((prev) => {
       return { ...prev, [e.target.id]: e.target.value };
      });
     }
    
     // Função que decide a função a ser chamada
     function handleSubmit(e) {
      e.preventDefault();
      setIsLoading(true);
      if (formType === "register") {
       register();
      } else if (formType === "login") {
       login();
      }
     }
    
     // JSX do formulário
     return (
      <>
       <div>
    
        <div>
         <button onClick={() => handleChangeFormType("login")}>
          Login
         </button>
         <button onClick={() => handleChangeFormType("register")}>
          Cadastro
         </button>
        </div>
        <form onSubmit={handleSubmit}>
         {formType === "register" && (
          <input
           type="text"
           placeholder="Nome"
           id="name"
           onChange={handleChange}
           required
          />
         )}
         <input
          type="email"
          placeholder="E-mail"
          id="email"
          onChange={handleChange}
          required
         />
         <input
          type="password"
          placeholder="Senha"
          id="password"
          onChange={handleChange}
          required
         />
         <p>
          {warning}
         </p>
         <button type="submit">
          {formType === "login" ? "Login" : "Cadastro"}
         </button>
        </form>
        <div>
         <button onClick={() => handleOAuth("google")}>
          <FcGoogle />
         </button>
         <button onClick={() => handleOAuth("github")}>
          <IoLogoGithub />
         </button>
        </div>
        <p>
         * Esta é uma aplicação de estudos. A senha é criptografada. Todos os dados
         podem ser deletados sem aviso prévio.
        </p>
       </div>
      </>
     );
    }

### Função de registro

Vamos criar a função de registro por credenciais neste mesmo componente do formulário. Esta função será assíncrona, mais especificamente uma request do tipo POST feita à API `register.js` que criamos anteriormente.

Nós a declararemos dentro de um useCallBack porque ela receberá variáveis do formulário, e só queremos que a função seja recriada se o usuário escrever algo nos inputs.

A base dela será assim:

    const register = useCallBack(async()=> {
    
    
    }, [formData])

Para fazer a request eu utilizarei Axios, que é uma library criada para facilitar manejo de APIs em Javascript. Instalá-lo não é necessário e você pode usar a função `fetch()` nativa sem problemas. Se quiser seguir os mesmos passos que eu, instale `npm i axios` e o importe no componente com `import axios from "axios"`.

O que faremos, então, é uma simples request do tipo POST para a nossa API de registro. Lá nós dissemos que o body da request conteria name, password e email. Portanto, enviaremos estes campos no body da nossa request. Como o meu state `formData` contém esses campos já em um objeto ( `{name: "nome", email: "email", password: "senha"}`), posso enviá-lo diretamente.

    const register = useCallback(async () => {
    
    try {
       await axios.post("/api/register", formData);
       login();
      } catch (error) {
       console.log(error);
       setWarning(error.response.data.error);
      }
     }, [formData, login]);

Note que dentro do bloco `catch` estou alterando um state do componente para alertar ao usuário a ocorrência de algum erro. O objeto `error.response.data.error`, é como acesso o texto do erro retornado pelo Axios, e o texto deste erro definimos lá na API. Assim, se o usuário já existe, por exemplo, o formulário mostrará a mensagem "Este usuário já existe.”.

Você também deve ter notado que chamei a função `login()` logo após a request. Fiz isto para que o usuário seja automaticamente autenticado caso o registro ocorra. Ainda não criamos esta função, mas já é possível testar o nosso registro. Remova a call de login() por enquanto e tente se registrar pela sua aplicação, verificando se um novo Document de usuário aparece na MongoDB. Se tudo estiver funcionando, vamos ao próximo passo.

### Função de login

Para criar a função de login, utilizaremos uma função fornecida pelo NextAuth chamada `signIn()`.

Começamos importando-a: `import {signIn} from "next-auth/react"`

O método `signIn()` recebe dois argumentos: o id do provider e um objeto com os dados de login. Lembre-se que nós definimos no arquivo de configuração do NextAuth “credentials” como o id do *provider* de credenciais. Quanto ao segundo argumento, passaremos email e password, retirados do nosso state que guarda os valores dos inputs.

Também passaremos a opção `redirect: false` para evitar que o usuário seja redirecionado na tentativa de login. Vamos nós mesmos lidar com esse redirecionamento usando o roteamento do Next.

    const login = useCallback(async () => {
      try {
       const res = await signIn("credentials", {
        email: formData.email.trim(),
        password: formData.password.trim(),
        redirect: false,
       });
    
       if (res.ok) {
        router.push("/user");
       } else {
        throw new Error(res.error);
       }
    
      } catch (error) {
       setWarning(error.message);
      }
     }, [formData, routerl]);

Deixe-me explicar o snippet acima. A função `signIn()` retorna um objeto. Se este objeto contiver a propriedade `ok`, significando que a request foi bem sucedida, vamos redirecioná-lo para a página de usuário na nossa aplicação, que eu criei no path “/user”. Se a resposta não for `ok`, jogará um erro que será lido pelo bloco `catch` e mudará o state `warning` que eu criei para mostrar eventuais erros de login.

Pronto! Agora o usuário já pode logar! Crie a página para a qual o usuário será redirecionado na sua aplicação e teste!

Quero fazer apenas um adendo aqui. Certifique-se de ter declarado a função `login` antes de `register`. Como estamos chamando a função de login dentro da função de registro, ela precisa ser inicializada antes.

    //primeiro login
    const login = useCallback...
    
    //depois register
    const register = useCallback...

## Adicionando outros tipos de autenticação

* Esta parte é opcional. Se você não pretende adicionar login com métodos diferentes, pode pular para a próxima seção.

Nós fizemos a parte mais difícil, que é criar autenticação com credenciais. Vamos adicionar alternativas OAuth para o usuário, como Google e Github. OAuth significa **Open Authorization.** É uma forma de reutilizar os dados já registrados em uma aplicação terceirizada para registrar o usuário na sua própria aplicação. Nestes casos, a senha utilizada na aplicação terceirizada não é exposta.

Começamos incluindo lá no `[...nextauth].js` os providers do Github e do Google.

Importamos os providers:

    import GithubProvider from "next-auth/providers/github";
    import GoogleProvider from "next-auth/providers/google";

E, dentro da array de providers, onde definimos Credentials, definiremos os novos providers:

    providers: [
      GithubProvider({
       clientId: process.env.GITHUB_ID || "",
       clientSecret: process.env.GITHUB_SECRET || "",
      }),
      GoogleProvider({
       clientId: process.env.GOOGLE_ID || "",
       clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
      }),
     // Credentials({
     // ...

Só isso não basta. Precisamos agora pedir autorização desses providers para utilizar os seus serviços. Com a autorização, obteremos as variáveis de ambiente indicadas acima para `clientId` e `clientSecret`.

### Github

Primeiro, devemos criar uma chave de autenticação no Github. No site do Github, encontramos esta opção em **Settings > Developer Settings > OAuth Apps**. Preenchemos o nome da nossa aplicação (que aparecerá ao usuário). Os campos de *Homepage URL* e *Authorization callback URL* podemos preencher com “[http://localhost:3000/](http://localhost:3000/)” (ou a URL local que você está utilizando) já que estamos em um ambiente de desenvolvimento.

Guardamos, então, as chaves de ID e o Secret do cliente nas nossas variáveis de ambiente com os nomes de variável definidos nas configurações do NextAuth (GITHUB_ID e GITHUB_SECRET, no caso do nosso exemplo).

### Google

A única diferença será o modo de criação das chaves. Para o Google, entramos na plataforma da [Google Cloud](https://console.cloud.google.com/) e criamos um **Novo Projeto**. Certificamo-nos de que este projeto está selecionado, e procuramos pela opção **APIs e serviços** (pode-se utilizar o campo de pesquisa do site). No menu lateral, selecionamos **Tela de permissão OAuth.** Escolhemos uso **Externo** e depois preenchemos o formulário com as informações da nossa aplicação.

Como estamos em um ambiente de desenvolvimento, na parte de *Escopos* e *Usuários de teste* não precisamos preencher nada.

Agora, no menu lateral selecionamos Credenciais e clicamos em *Criar Credenciais > ID do Cliente OAuth*. Selecionamos Aplicação Web e definimos o nome da aplicação.

Agora, o único campo que precisamos preencher será *Adicionar URI.* Nele, devemos adicionar este link, utilizado pelo NextAuth:

`http://localhost:3000/api/auth/callback/google`

Lembrando sempre que utilizamos “[localhost](http://localhost):3000” por estarmos em um ambiente local de desenvolvimento. Se você fizer deploy do seu site em algum momento, será preciso alterar todas as configurações dos providers e das variáveis de ambiente para a URL de produção.

### Lidando com o login por OAuth

Vamos criar a última função do nosso formulário para lidar com o caso autenticação por OAuth.

Se você ainda não criou um botão para o Google, Github ou outros providers no seu formulário, a hora é agora.

Será uma função simples função assíncrona que chamará `signIn` com o id do provider como argumento. Também passaremos um objeto como segundo argumento, com a propriedade `callBackUrl` definindo a URL de redirecionamento caso a autorização seja um sucesso.

Um bom modo de criar esta função é passar `provider` como parâmetro e alterar o argumento na call da função conforme o provider, deste modo:

    async function handleOAuth(provider) {
      try {
       await signIn(provider, { callbackUrl: "/user" });
      } catch (error) {
       console.log(error);
      } 
     }

Segundo a documentação do NextAuth, o id do provider do Google é `google`, e do Github é `github`. Para outros casos, cheque a [documentação](https://next-auth.js.org/configuration/providers/oauth).

Finalmente, para chamar esta função definimos um onClick nos nossos botões, por exemplo: `onClick={() => handleOAuth("google")}`.

Pronto, agora o usuário poderá logar com Credenciais, Github e Google na nossa aplicação!

## Protegendo as nossas rotas

Precisamos nos certificar de que apenas usuários autenticados consigam acessar certas rotas. Para isso, vamos utilizar o `getServerSideProps`do Next e uma função chamada `getServerSession` do NextAuth.

Como sabemos, o Next fornece a possibilidade de se executar funções no servidor antes da renderização da página com `getServerSideProps`. Não queremos passar props para o componente, mas utilizaremos a função para verificar se o cliente possui uma sessão ativa antes de mostrar qualquer conteúdo. Se não houver, o usuário será redirecionado para a nossa página de registro ou login.

Como verificar se há uma sessão ativa? Obteremos a resposta através de getServerSession. Esta função retorna um objeto com algumas informações do usuário da sessão atual, como nome e e-mail. Precisamos passar a ela `req` e `res` como argumentos, que nós acessaremos a partir do `context` do `getServerSideProps`. O terceiro argumento são as opções do NextAuth, que foram exportadas no arquivo `[…nextauth].js`.

Nas páginas que queremos proteger (aquelas em que o usuário **deve** estar logado para visualizar), vamos fazer a verificação no lado do servidor. A função a seguir deve ser declarada após os imports e antes do componente da página (como todo `getServerSideProps`):

    import { getServerSession } from "next-auth";
    import { authOptions } from "@/pages/api/auth/[...nextauth]";
    
    export async function getServerSideProps(context) {
     const session = await getServerSession(context.req, context.res, authOptions);
    
     if (!session) {
      return {
       redirect: {
        destination: "/signin",
       },
      };
     }
    
     return {
      props: {},
     };
    }

Deste modo, se não houver `session` válida, o usuário será redirecionado para a página escolhida.

## Obtendo os dados do usuário

Já temos uma lógica para registro e login, e já estabelecemos um meio de proteger as nossas rotas. O que precisamos, agora, é obter os dados do usuário atual para renderizá-los no cliente.

Esta parte pode ficar um pouco confusa, então preste atenção: para obter esses dados, vamos criar mais uma API. E que lógica ela precisa conter? Algo que nos permita selecionar o usuário atual na database. Poderíamos criar esta lógica diretamente na API, mas pense lá na frente: toda vez que quisermos alterar alguma informação sobre o usuário precisaremos selecioná-lo antes. Obter os dados? Selecione o usuário e retorne. Adicionar uma tarefa? Selecione o usuário e adicione.

Vamos, por isso, criar uma outra lib, que retornará o nosso usuário atual para utilizá-lo em todas as nossas APIs.

Depois, vamos criar uma API apenas para obter o usuário atual. Ela será nossa rota `current`.

Após estes passos, criaremos finalmente um meio para utilizar os dados obtidos no cliente.

### Função para ser usada nas APIs

Na pasta `lib` vamos criar um arquivo chamado `serverAuth.js` e escrever uma função que retornará o usuário selecionado.

Nesta função, utilizaremos mais uma vez `getServerSession` do NextAuth para obter o e-mail do usuário. Os parâmetros `req` e `res` serão supridos porque chamaremos serverAuth apenas dentro das nossas APIs, que já possuem essas informações como parâmetros. A call da API poderá enviar `req` e `res` como argumentos para que o NextAuth cuide de obter os dados da sessão.

Obtida a sessão, temos o e-mail do usuário logado. Com este e-mail, utilizaremos `findUnique` do PrismaClient para selecionar o usuário da database. Se esse usuário for encontrado, vamos retorná-lo. A função ficará assim:

    
    import prismadb from "./prismadb";
    import { getServerSession } from "next-auth/next";
    import { authOptions } from "@/pages/api/auth/[...nextauth]";
    
    const serverAuth = async (req, res) => {
     
     //obtém dados da sessão
     const session = await getServerSession(req, res, authOptions);
     
     //se não há sessão, retorna um erro
     if (!session.user.email) {
      throw new Error("O usuário não está logado.");
     }
    
     // seleciona o usuário com o cliente do Prisma
     const currentUser = await prismadb.user.findUnique({
      where: {
       email: session.user.email,
      },
     });
    
     //se assegura de que um usuário foi selecionado
     if (!currentUser) {
      throw new Error("O usuário não existe.");
     }
    
     //finalmente, retorna o nosso usuário
     return { currentUser };
    };
    
    export default serverAuth;

### Criando a API

Na pasta `api` do Next, vamos criar uma nova rota com o nome `current.js`. Ela apenas retornará o `currentUser` de serverAuth, conforme as explicações acima.

    export default async function handler(req, res) {
     if (req.method != "GET") {
      return res.status(405).end(); //incorrect request type
     }
    
     try {
      const { currentUser } = await serverAuth(req);
      return res.status(200).json(currentUser);
     } catch (error) {
      console.log(error);
      return res.status(400).end();
     }
    }

## Utilizando useSWR e um custom hook

A lógica da API está criada. É hora de criar a lógica para obter os dados no cliente. Para isto, utilizaremos SWR (*Stale-While-Revalidate*), uma lib de data fetching criada pela Vercel (a mesma companhia por trás do Next) que nos permitirá utilizar os dados obtidos com facilidade.

Funciona da seguinte maneira: o hook `useSWR` faz uma request para uma API e armazena o resultado no cache do browser. A URL passada como argumento para o hook (a rota da nossa API, no caso) se torna uma ‘key’ daquela request compartilhada por toda a aplicação. Se este hook for utilizado com a mesma chave em outro local do app, os dados serão obtidos diretamente do cache e não a partir de outra request. É uma forma de compartilhar o usuário por toda a aplicação sem precisar usar contextos.

O hook possui muitas opções e é bastante otimizado. É possível definir, por exemplo, que a revalidação será feita em um determinado intervalo de tempo ou a cada mudança de tela no browser. Ele também fornece uma variável que indica o estado da request (`isLoading`) e uma função para modificar os dados manualmente, sem a necessidade de outra request (a função `mutate`), o que é muito útil para renderizar automaticamente dados modificados na database.

E por que estou falando em “custom hook”? Vamos criar um hook **para** utilizar useSWR. Pode parecer exagero, mas fazer isto economizará várias linhas quando quisermos acessar os dados do usuário na aplicação.

### Criando o hook

Crie uma nova pasta chamada hooks. Dentro dela, crie um arquivo chamado `useCurrentUser.js`.

Um custom hook do React nada mais é que um componente que pode agrupar o resultado de outros hooks e funções em um único arquivo para ser usado em vários lugares da aplicação.

Vamos instalar o pacote do SWR com `npm i swr`, e importá-lo com `import useSWR from "swr"`.

`useSWR` recebe como argumentos a URL da nossa API e uma função para fazer a request. A URL é a rota `/api/current` que criamos. A função é um fetch GET simples para a API, que nós definiremos com Axios. Importe, portanto, o Axios no seu arquivo.

O hook ficará assim:

    import useSWR from "swr";
    import axios from "axios";
    
    const useCurrentUser = () => {
     const fetcher = (url) => axios.get(url).then((res) => res.data);
    
     const { data, error, isLoading, mutate } = useSWR("/api/current", fetcher);
    
     return {
      data,
      error,
      isLoading,
      mutate,
     };
    };
    
    export default useCurrentUser;

Perceba que useSWR retorna `data`, `error`, `isLoading` e `mutate`, e nós estamos retornando esses valores no nosso próprio hook.

* data: é o objeto do usuário na database. Este valor contém tudo o que o modelo User contém.

* error: objeto que contém eventuais erros retornados da request.

* isLoading: state que é `true` durante a request e `false` quando ela é finalizada.

* mutate: função que permite a revalidação dos dados no cache e a sua manipulação.

### Testando a request

É a hora da verdade. Suponho que você já tenha se registrado e esteja logado na aplicação. Vá até a página que você protegeu com `getServerSideProps` e importe `useCurrentUser`.

Chame o hook desta forma:

    const { data: user, error, isLoading, mutate } = useCurrentUser()

Ele retorna o objeto `data`, que é o nome padrão do `useSWR`. O que fizemos acima foi apenas renomear `data` para `user`, mas isto não é obrigatório.

Dê um log para verificar **o que** é o objeto user e renderize, em um HTML qualquer, o nome do usuário:

    <p> Olá, {user?.name}!</p>

Note que adicionamos uma condicional `?` a `user`. A sintaxe acima é o equivalente a `user && user.name`, isto é, “se user for truthy, renderize [user.name](http://user.name)”. Isto garante que o Next não vai tentar renderizar uma variável indefinida.

Se você quer adicionar outra camada de segurança ou sinalizar visualmente ao usuário que os dados ainda não retornaram, pode renderizar seus componentes de acordo com o valor de `isLoading` e `error`:

    export default function UserPage() {
    const { data: user, error, isLoading, mutate } = useCurrentUser()
    
    if (error) {
     return <p>Oops, algo deu errado!</p>
    }
    
     return (
      <>
       {isLoading  ? <p>Carregando...</p> : <p> Olá, {user?.name}!</p>}
       <button onClick={() => signOut()}>Sair</button>
      </>
     );
    }

Adicionei no snippet acima um botão para deslogar. Ela utiliza a função `signOut` do NextAuth. Basta chamá-la para que a library cuide de remover a sessão atual.

## Adicionando uma tarefa

Os fundamentos da aplicação estão construídos! O usuário já pode se registrar e logar, já protegemos nossas páginas de usuários não autenticados e já estamos renderizando os dados da database no cliente.

Falta apenas adicionar um método de manipulação dos dados. Criaremos, então, uma API para que o usuário possa adicionar uma tarefa na database (lembrando que estou utilizando uma lista de tarefas apenas como plano de fundo deste artigo).

### Criando a API

Já temos tudo o que precisamos para incluir essa finalidade. Utilizaremos o cliente do Prisma para adicionar a tarefa.

Na pasta `api`, criaremos uma rota chamada `todo.js` (de ***to do***, isto é, uma tarefa “para fazer”) e escreveremos o esqueleto do handler. Como estamos **adicionando** uma tarefa, vamos criar nossa lógica para o método POST:

    import prismadb from "@/lib/prismadb";
    import serverAuth from "@/lib/serverAuth";
    
    export default async function handler(req, res) {
    
     try {
      if (req.method === 'POST') {
    
      }
    
     } catch (error) {
      console.log(error);
      return res.status(400).json("Oops! Algo deu errado.");
     }
    }

Dentro da condicional vamos obter o usuário atual com o `serverAuth` que criamos para utilizar em todas as APIs (lembre-se que serverAuth retorna o currentUser):

    const { currentUser } = await serverAuth(*req*, *res*);

Quando criarmos um formulário para adicionar a tarefa, passaremos uma tarefa (string) no body da request. Vamos obter essa tarefa desestruturando o body:

    const { todo } = req.body;

Vamos adicionar a tarefa na lista do usuário, utilizando o método `update` do Prisma, que nos permite adicionar diretamente um elemento em uma array com `push`:

    const response = await prismadb.user.update({
       where: {
        email: currentUser.email,
       },
       data: {
        todoList: {
         push: todo,
        },
       },
      });
    
    return res.status(200).json("Adicionado com sucesso.");

Ficará assim:

    import prismadb from "@/lib/prismadb";
    import serverAuth from "@/lib/serverAuth";
    
    export default async function handler(req, res) {
    
     try {
      const { todo } = req.body;
    
      if (req.method === 'POST') {
       const { currentUser } = await serverAuth(req, res);
    
       const response = await prismadb.user.update({
          where: {
           email: currentUser.email,
          },
          data: {
           todoList: {
            push: todo,
           },
          },
         });
       
       return res.status(200).json("Adicionado com sucesso.");
      }
    
     } catch (error) {
      console.log(error);
      return res.status(400).json("Oops! Algo deu errado.");
     }
    }

## Lógica no cliente

Agora, vá até a sua página e crie uma função assíncrona para fazer uma request do tipo POST à rota (estou utilizando Axios):

    async function addTodo(todo) {
      try {
       const res = await axios.post("/api/todo", {
        todo: todo,
       });
      } catch (error) {
       console.log(error.message);
      }
     }

Depois, crie um input e um botão simples para que o usuário digite a tarefa e chame a função com o valor do input. Aproveite e faça uma `ul` para mostrar as tarefas adicionadas na database, utlizando o campo `todoList` de `user`, que já obtivemos através do hook `useCurrentUser`. Com base na página que criamos anteriormente, as adições a deixarão mais ou menos assim:

    
    
    export default function UserPage() {
    const { data: user, error, isLoading, mutate } = useCurrentUser()
    const [todo, setTodo] = useState('');
    
    async function addTodo(todo) {
      try {
       const res = await axios.post("/api/todo", {
        todo: todo,
       });
       console.log(res.data);
      } catch (error) {
       console.log(error.message);
      }
     }
    
    if (error) {
     return <p>Oops, algo deu errado!</p>
    }
    
     return (
      <>
       {isLoading  ? <p>Carregando...</p> : <p> Olá, {user?.name}!</p>}
       <button onClick={() => signOut()}>Sair</button>
    
       <input type="text" value={todo} onChange={(e) => setTodo(e.target.value)} />
       <button onClick={() => addTodo(todo)}>Adicionar tarefa</button>
    
       <ul>
         {user.todoList.map((todo, index) => (
          <li key={index}>{todo}</li>
         ))}
       </ul>
    
    
      </>
     );
    }
> *Eu adicionei a tarefa, mas ela não aparece na minha página!*

A tarefa foi adicionada à database. Se você recarregar a página, verá que ela será renderizada. O problema é que estamos apenas atualizando a database sem avisar a aplicação.

Nós poderíamos criar um state apenas para armazenar a lista de tarefas, e incluir nesse state cada nova tarefa adicionada, mas o SWR nos fornece uma boa alternativa.

Como mencionado anteriormente, chamar `mutate` força o SWR a revalidar os dados no cache e re-renderizar o componente. Poderíamos, então, simplesmente chamar `mutate()` na função `addTodo`, logo após a request (ainda dentro de `try`, é claro).

No entanto, fazer isto pode comprometer a performance, pois uma nova request será feita cada vez que um dado for alterado. Graças à engenhosidade da Vercel, `mutate` pode receber os dados novos como argumento incluí-los no cache sem realizar outra request. Vamos atualizar a função `addTodo`:

    async function addTodo(todo) {
      try {
       const newTodos = {...user, todoList: [...user.todoList, todo]}
       const res = await axios.post("/api/todo", {
        todo: todo,
       });
       mutate(newTodos, false)
       console.log(res.data);
      } catch (error) {
       console.log(error.message);
      }
     }

Perceba que os dados que nós tinhamos compunham o objeto `user`. Logo, precisamos atualizar todo o objeto, declarando que `todoList` será a lista de tarefas antiga mais a nova tarefa. O segundo argumento de mutate, que definimos como false, é na verdade um objeto que guarda as opções da função, como a de revalidar os dados. Usar `false` é declarar que não queremos os dados revalidados, apenas adicionados ao cache.

Lembro apenas que este mutate é o retornado do useCurrentUser, que utiliza, por sua vez, o mutate do useSWR com a key `‘/api/current’`. Se você quiser usar SWR para outras requests na sua aplicação, lembre-se de renomear o mutate de acordo com o caso, como fizemos com ***user***, que antes era simplesmente **data**.

## Removendo uma tarefa

Falta lidarmos apenas com um caso essencial. Vamos até a nossa API `todo` para criar a lógica de remoção de itens.

Diremos o seguinte: se o método for DELETE, selecionaremos a lista da database. Com a lista obtida, vamos filtrá-la para remover o item e, depois, substituir a lista antiga pela lista filtrada:

    if (req.method === "DELETE") {
     const list = await prismadb.user.findUnique({
        where: {
         email: currentUser.email,
        },
        select: {
         todoList: true,
        },
       });
    
     const filteredList = list.todoList.filter(el => el != todo);
    
     const response = await prismadb.user.update({
        where: {
         email: currentUser.email,
        },
    
        data: {
         todoList: {
          set: filteredList,
         },
        },
       });
    
       res.status(200).json("Tarefa removida com sucesso.");
    
    }

Se você logar `list` verá que é um objeto que contém a lista como propriedade, por isso usamos `list.todoList` para manipular a lista com o método `filter`.

Obviamente filtrar uma array de strings não é um bom método de exclusão de itens, já que duas tarefas idênticas seriam removidas e o usuário poderia querer remover apenas uma delas. Poderíamos criar, ao invés de uma simples array de strings, uma array de objetos em que cada tarefa possui um ID, e filtrar este ID ao invés da própria “todo”. Mas o método atual serve ao propósito de demonstração do artigo.

O importante é entender que, para remover um item de uma array, selecionamos a lista, modificamos o necessário e recolocamos a nova lista no lugar.

Agora, para deletar a tarefa, criaremos uma função na aplicação que, ao ser chamada, faz uma request do tipo DELETE à API, passando a tarefa como argumento:

    async function removeTodo(todo) {
      try {
       const filteredList = user.todoList.filter(el => el != todo);
       const newTodos = {...user, todoList: filteredList}
       const res = await axios.delete("/api/todo", {
        data: {todo: todo}
       });
       mutate(newTodos, false);
       console.log(res.data);
      } catch (error) {
       console.log(error.message);
      }
     }

Mais uma vez a nossa função criará a lista atualizada e a colocará no cache para não precisar fazer outra request com SWR. Para chamar a função, criamos um botão e passamos `todo` como argumento:

    <ul>
      {user.todoList.map((todo, index) => (
       <li key={index}>
        <p>{todo}</p>
        <button onClick={() => removeTodo(todo)}>Remover</button>
       </li>
      ))}
    </ul>


## Considerações finais

Pronto! É isso. Final anticlimático, né? Deixe-me melhorar.

Meus sinceros parabéns! A aplicação está criada e funcionando. Nosso usuário pode se registrar, logar, adicionar e remover tarefas. O que mais queremos? Talvez uma função para editar as tarefas? Outra para marcar tarefas como realizadas (movendo-as para a nossa `doneList`)? Deixar a página bonita para ninguém querer filtrar os olhos para fora da cabeça? Isso tudo deixo com você.

Espero que você tenha compreendido meu propósito, que não era criar uma aplicação específica do início ao fim, mas usar o tempo que outros tutorais gastam com aspectos individuais de cada projeto para explicar o que eles não explicam. O que construímos aqui foi um esqueleto, e o que tentei fazer foi capacitá-lo a colocar nele a pele que quiser.

Se você ficou com alguma dúvida, tem alguma sugestão ou feedback sobre o tutorial, por favor, não hesite em entrar em contato.

Um abraço! E *happy coding* :)