Criando aplicativos modernos com Next.js e MongoDB
Avalie esse Tutorial
Este artigo está desatualizado. Confira o tutorial oficialdo Next.js com MongoDB para obter o guia mais recente sobre como integrar o MongoDB com o Next.js.
Os desenvolvedores têm mais opções do que nunca quando se trata de escolher a pilha de tecnologia para seu próximo aplicativo. A produtividade do desenvolvedor é um dos fatores mais importantes na escolha de uma pilha moderna e acredito que o Next.js, juntamente com o MongoDB, pode deixar você pronto para criar um ótimo aplicativo em pouco tempo. Vamos descobrir como e por quê!
Se quiser acompanhar este tutorial, você pode obter o código no repositório do Github. Além disso, certifique-se de se cadastrar em uma conta gratuita do MongoDB Atlas para facilitar a conexão ao seu MongoDB database.
Next.js é um framework baseado em React para criar aplicativos web modernos. O framework vem com muitos recursos avançados, como renderização do lado do servidor, divisão automática de código, exportação estática e muito mais, que facilitam a criação de aplicativos escaláveis e prontos para produção. Sua natureza opinativa significa que o framework é focado na produtividade do programador, mas ainda é flexível o suficiente para dar aos desenvolvedores muitas opções quando se trata de lidar com as grandes decisões de arquitetura.
Neste tutorial, presumirei que você já esteja familiarizado com o React e, em caso afirmativo, estará pronto para usar o Next.js num instante. Se você não conhece o React, sugiro consultar recursos como os documentos oficiais do React ou fazer um curso gratuito de React para se familiarizar com o framework primeiro.
O aplicativo que estamos criando hoje é chamado de Macro Compliance Tracker. Se você é como eu, provavelmente fez uma resolução de ano novo: "Vou entrar em forma!" Este ano, estou levando essa resolução a sério e contratei um personal trainer e um nutricionista. Uma coisa interessante que aprendi é que, embora a velha crença de que você precisa gastar mais calorias do que ingere para perder peso seja geralmente verdadeira, os macronutrientes também desempenham um papel importante na perda de peso.
Existem muitos aplicativos ótimos que ajudam a controlar suas calorias e macros. Infelizmente, a maioria dos aplicativos não permite rastrear uma faixa. Algo interessante que aprendi na minha jornada fitness este ano é que para muitos iniciantes tentar atingir as metas macro diárias é um desafio e muitas pessoas acabam desistindo quando não conseguem atingir as metas exatas de forma consistente. Por essa razão, meu instrutor sugere uma faixa alvo para calorias e macros, em vez de um número fixo.
Então é isso que estamos criando hoje. Usaremos o Next.js para criar todo a nossa aplicação e o MongoDB como banco de dados para armazenar nosso progresso. Vamos começar!
A maneira mais fácil de criar um aplicativo Next.js é usando o comando create-next-app npx oficial. Para fazer isso, basta abrir a janela do Terminal e digitar:
npx create-next-app mct
. "mct" será o nome do nosso aplicativo, bem como o diretório onde nosso código ficará.Execute este comando e um aplicativo padrão será criado. Depois que os arquivos forem criados, navegue até o diretório executando
cd mct
na janela do Terminal e execute npm run dev
. Isso iniciará um servidor de desenvolvimento para seu aplicativo Next.js que você poderá acessar em localhost:3000
.Navegue até
localhost:3000
e você deverá ver uma página muito semelhante à da captura de tela acima. Se você vir a página Bem-vindo ao Next.js, você está pronto para começar. Caso contrário, gostaria de sugerir seguir os documentos do Next.js e as dicas de solução de problemas para garantir a configuração adequada.Antes de começarmos a criar nosso aplicativo, vejamos rapidamente como o Next.js estrutura o aplicativo. A estrutura de diretório padrão é assim:
As áreas em que vamos nos concentrar são as páginas, os componentes e os diretórios públicos. O diretório .next contém os artefatos de compilação do nosso aplicativo e, em geral, devemos evitar fazer alterações diretas nele.
O diretório Pages conterá as páginas do nosso aplicativo. Outra forma de pensar nelas é que cada arquivo aqui representa uma única rota em nosso aplicativo. Nosso aplicativo padrão tem apenas a página index.js criada, que corresponde à nossa rota inicial. Se quisermos adicionar uma segunda página, por exemplo, uma página Sobre, podemos fazer isso facilmente criando um novo arquivo chamado about.js. O nome que dermos ao arquivo corresponderá à rota. Portanto, vamos criar um arquivo
about.js
no diretório Pages.Como mencionei anteriormente, Next.js é um framework baseado em React, então todo o seu conhecimento em React é totalmente transferível aqui. Você pode criar componentes utilizando como funções ou como classes. Usarei a abordagem baseada em funções. Acesse o repositório completo do GitHub se quiser acompanhar. Nosso componente About.js terá a seguinte aparência:
1 import React from 'react' 2 import Head from 'next/head' 3 import Nav from '../components/nav' 4 5 const About = () => ( 6 <div> 7 <Head> 8 <title>About</title> 9 <link rel="icon" href="/favicon.ico" /> 10 </Head> 11 12 <Nav /> 13 14 <div> 15 <h1>Macro Compliance Tracker!</h1> 16 <p> 17 This app will help you ensure your macros are within a selected range to help you achieve your New Years Resolution! 18 </p> 19 </div> 20 </div> 21 ) 22 23 export default About
Continue e salve esse arquivo. O Next.js recriará automaticamente o aplicativo e você poderá navegar para
http://localhost:3000/about
agora e ver o novo componente em ação.Next.js lidará automaticamente com todo o roteamento e garantirá que o componente certo seja carregado. Lembre-se de que o nome que você der ao seu arquivo no diretório Pages será o URL correspondente.
Nosso aplicativo está com boa aparência, mas, do ponto de vista do design, está bem vazio. Vamos adicionar oTailwind.css para aprimorar nosso design e torná-lo um pouco mais agradável aos olhos. O Tailwind é uma estrutura CSS muito avançada, mas, para sermos breves, importaremos apenas os estilos básicos de uma CDN e não faremos nenhuma personalização. Para fazer isso, basta adicionar
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet"/>
nos componentes Head de nossas páginas.Vamos fazer isso pelo nosso componente About e também adicionar algumas classes do Tailwind para melhorar nosso design. Nosso próximo componente deve ficar assim:
1 import React from 'react' 2 import Head from 'next/head' 3 import Nav from '../components/nav' 4 5 const About = () => ( 6 <div> 7 <Head> 8 <title>About</title> 9 <link rel="icon" href="/favicon.ico" /> 10 <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" /> 11 </Head> 12 13 <Nav /> 14 15 <div className="container mx-auto text-center"> 16 <h1 className="text-6xl m-12">Macro Compliance Tracker!</h1> 17 <p className="text-xl"> 18 This app will help you ensure your macros are within a selected range to help you achieve your New Years Resolution! 19 </p> 20 </div> 21 </div> 22 ) 23 24 export default About
Se atualizarmos nosso navegador, a página Sobre deverá ficar assim:
Por enquanto está bom assim. Se você quiser saber mais sobre o Tailwind, confira os documentos oficiais aqui.
Observação: se, ao fazer alterações no aplicativo Next.js, como adicionar
className
ou outras alterações, e elas não forem refletidas quando você atualizar a página, reinicie o servidor de desenvolvimento.Agora que temos nossa configuração de aplicativo Next.js e nos familiarizamos com a criação de componentes e páginas, vamos começar a criar nosso aplicativo Macro Compliance Tracker. Para nossa primeira implementação deste aplicativo, colocaremos toda a nossa lógica na página principal do index.js. Abra a página e exclua todo o boilerplate Next.js existentes.
Antes de escrevermos o código, vamos descobrir de quais recursos precisaremos. Queremos mostrar ao usuário suas metas diárias de calorias e macro, e se ele está ou não em conformidade com o objetivo. Além disso, queremos permitir que o usuário atualize suas informações todos os dias. Por fim, queremos que o usuário possa ver os dias anteriores para fazer comparações.
Primeiro vamos criar a interface do usuário para isso. Faremos tudo isso no componente Home e, em seguida, começaremos a dividi-lo em componentes individuais menores. Nosso código será mais ou menos assim:
1 import React from 'react' 2 import Head from 'next/head' 3 import Nav from '../components/nav' 4 5 const Home = () => ( 6 <div> 7 <Head> 8 <title>Home</title> 9 <link rel="icon" href="/favicon.ico" /> 10 <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" /> 11 </Head> 12 13 <div className="container mx-auto"> 14 15 <div className="flex text-center"> 16 <div className="w-full m-4"> 17 <h1 className="text-4xl">Macro Compliance Tracker</h1> 18 </div> 19 </div> 20 21 <div class="flex text-center"> 22 <div class="w-1/3 bg-gray-200 p-4">Previous Day</div> 23 <div class="w-1/3 p-4">1/23/2020</div> 24 <div class="w-1/3 bg-gray-200 p-4">Next Day</div> 25 </div> 26 27 <div class="flex mb-4 text-center"> 28 <div class="w-1/4 p-4 bg-green-500 text-white"> 29 <h2 className="text-3xl font-bold">1850 30 <div class="flex text-sm p-4"> 31 <div class="w-1/3">1700</div> 32 <div class="w-1/3 font-bold">1850</div> 33 <div class="w-1/3">2000</div> 34 </div> 35 </h2> 36 <h3 className="text-xl">Calories</h3> 37 </div> 38 <div class="w-1/4 p-4 bg-red-500 text-white"> 39 <h2 className="text-3xl font-bold">195 40 <div class="flex text-sm p-4"> 41 <div class="w-1/3">150</div> 42 <div class="w-1/3 font-bold">160</div> 43 <div class="w-1/3">170</div> 44 </div> 45 </h2> 46 <h3 className="text-xl">Carbs</h3> 47 </div> 48 <div class="w-1/4 p-4 bg-green-500 text-white"> 49 <h2 className="text-3xl font-bold">55 50 <div class="flex text-sm p-4"> 51 <div class="w-1/3">50</div> 52 <div class="w-1/3 font-bold">60</div> 53 <div class="w-1/3">70</div> 54 </div> 55 </h2> 56 <h3 className="text-xl">Fat</h3> 57 </div> 58 <div class="w-1/4 p-4 bg-blue-500 text-white"> 59 <h2 className="text-3xl font-bold">120 60 <div class="flex text-sm p-4"> 61 <div class="w-1/3">145</div> 62 <div class="w-1/3 font-bold">160</div> 63 <div class="w-1/3">175</div> 64 </div> 65 </h2> 66 <h3 className="text-xl">Protein</h3> 67 </div> 68 </div> 69 70 <div className="flex"> 71 <div className="w-1/3"> 72 <h2 className="text-3xl p-4">Results</h2> 73 <div className="p-4"> 74 <label className="block">Calories</label> 75 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 76 </div> 77 <div className="p-4"> 78 <label className="block">Carbs</label> 79 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 80 </div> 81 <div className="p-4"> 82 <label className="block">Fat</label> 83 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 84 </div> 85 <div className="p-4"> 86 <label className="block">Protein</label> 87 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 88 </div> 89 <div className="p-4"> 90 <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> 91 Save 92 </button> 93 </div> 94 </div> 95 <div className="w-1/3"> 96 <h2 className="text-3xl p-4">Target</h2> 97 <div className="p-4"> 98 <label className="block">Calories</label> 99 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 100 </div> 101 <div className="p-4"> 102 <label className="block">Carbs</label> 103 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 104 </div> 105 <div className="p-4"> 106 <label className="block">Fat</label> 107 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 108 </div> 109 <div className="p-4"> 110 <label className="block">Protein</label> 111 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 112 </div> 113 <div className="p-4"> 114 <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> 115 Save 116 </button> 117 </div> 118 </div> 119 <div className="w-1/3"> 120 <h2 className="text-3xl p-4">Variance</h2> 121 <div className="p-4"> 122 <label className="block">Calories</label> 123 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 124 </div> 125 <div className="p-4"> 126 <label className="block">Carbs</label> 127 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 128 </div> 129 <div className="p-4"> 130 <label className="block">Fat</label> 131 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 132 </div> 133 <div className="p-4"> 134 <label className="block">Protein</label> 135 <input type="number" className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"></ input> 136 </div> 137 <div className="p-4"> 138 <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> 139 Save 140 </button> 141 </div> 142 </div> 143 </div> 144 </div> 145 </div> 146 ) 147 148 export default Home
E isso fará com que nossa IU fique assim:
Precisamos examinar algumas coisas. Então vamos dar uma olhada parte por parte. No topo, temos um cabeçalho simples que apenas exibe o nome do nosso aplicativo. Em seguida, temos nossas informações do dia e opções de seleção. Depois disso, temos nossos resultados diários mostrando se estamos em conformidade ou não para o dia selecionado. Se estivermos dentro do intervalo sugerido, o segundo plano é verde. Se estivermos acima do intervalo, o que significa que tivemos muito de uma macro específica, o segundo plano é vermelho e, se consumimos pouco uma macro específica, o segundo fundo é azul. Finalmente, temos o nosso formulário que nos permite atualizar os nossos resultados diários, as nossas calorias alvo e macros, bem como a variação para o nosso intervalo.
Nosso código agora está todo em um componente gigante e bastante estático. Em seguida, vamos dividir nosso componente gigante em partes menores e adicionar nossa funcionalidade de frontend para que, pelo menos, trabalhemos com dados não estáticos. Criaremos nossos componentes no diretório de componentes e, em seguida, os importaremos para o componente da página index.js. Os componentes que criamos no diretório de componentes podem ser usados em várias páginas com facilidade, o que nos permite a reutilização se adicionarmos várias páginas ao nosso aplicativo.
O primeiro componente que criaremos é o componente de resultado. O componente de resultado é o bloco verde, vermelho ou azul que exibe nosso resultado, bem como nossas faixas alvo e de variação. Nosso componente ficará assim:
1 import React, {useState, useEffect} from 'react' 2 const Result = ({results}) => { 3 let [bg, setBg] = useState(""); 4 5 useEffect(() => { 6 setBackground() 7 }); 8 9 const setBackground = () => { 10 let min = results.target - results.variant; 11 let max = results.target + results.variant; 12 13 if(results.total >= min && results.total <= max) { 14 setBg("bg-green-500"); 15 } else if ( results.total < min){ 16 setBg("bg-blue-500"); 17 } else { 18 setBg("bg-red-500") 19 } 20 } 21 22 return ( 23 <div className={bg + " w-1/4 p-4 text-white"}> 24 <h2 className="text-3xl font-bold">{results.total} 25 <div className="flex text-sm p-4"> 26 <div className="w-1/3">{results.target - results.variant}</div> 27 <div className="w-1/3 font-bold">{results.target}</div> 28 <div className="w-1/3">{results.target + results.variant}</div> 29 </div> 30 </h2> 31 <h3 className="text-xl">{results.label}</h3> 32 </div> 33 ) 34 } 35 36 export default Result
Isso nos permitirá alimentar esse componente com dados dinâmicos e, com base nos dados fornecidos, exibiremos o background correto, bem como os intervalos de destino para nossas macros. Agora podemos simplificar nosso componente de página index.js removendo todo o código padrão e substituindo-o por:
1 <div className="flex mb-4 text-center"> 2 <Result results={results.calories} /> 3 <Result results={results.carbs} /> 4 <Result results={results.fat} /> 5 <Result results={results.protein} /> 6 </div>
Também criaremos alguns dados fictícios por enquanto. Em breve, vamos recuperar dados em tempo real do MongoDB, mas, por enquanto, apenas criaremos alguns dados na memória da seguinte forma:
1 const Home = () => { 2 let data = { 3 calories: { 4 label: "Calories", 5 total: 1840, 6 target: 1840, 7 variant: 15 8 }, 9 carbs: { 10 label: "Carbs", 11 total: 190, 12 target: 160, 13 variant: 15 14 }, 15 fat: { 16 label: "Fat", 17 total: 55, 18 target: 60, 19 variant: 10 20 }, 21 protein: { 22 label: "Protein", 23 total: 120, 24 target: 165, 25 variant: 10 26 } 27 } 28 29 const [results, setResults] = useState(data); 30 31 return ( ... )}
Se observarmos nosso aplicativo agora, ele não parecerá muito diferente. E está tudo bem. Tudo o que fizemos até agora foi mudar a forma como nossa IU é renderizada, transferindo-a de valores estáticos codificados para um objeto na memória. Agora, vamos em frente e fazer nosso formulário funcionar com esses dados na memória. Como nossos formulários são muito semelhantes, também podemos criar um componente aqui e reutilizar o mesmo componente.
Criaremos um novo componente chamado MCTForm, e nesse componente passaremos nossos dados, um nome para o formulário e um manipulador onChange que atualizará os dados dinamicamente à medida que alteramos os valores nas caixas de entrada. Além disso, para simplificar, removeremos o botão Salvar e o moveremos para fora do formulário. Isso permitirá que o usuário faça alterações nos dados na IU e, quando quiser bloquear as alterações e salvá-las no banco de dados, ele pressionará o botão Salvar. Então nosso componente Home agora ficará assim:
1 const Home = () => { 2 let data = { 3 calories: { 4 label: "Calories", 5 total: 1840, 6 target: 1850, 7 variant: 150 8 }, 9 carbs: { 10 label: "Carbs", 11 total: 190, 12 target: 160, 13 variant: 15 14 }, 15 fat: { 16 label: "Fat", 17 total: 55, 18 target: 60, 19 variant: 10 20 }, 21 protein: { 22 label: "Protein", 23 total: 120, 24 target: 165, 25 variant: 10 26 } 27 } 28 29 const [results, setResults] = useState(data); 30 31 const onChange = (e) => { 32 const data = { ...results }; 33 34 let name = e.target.name; 35 36 let resultType = name.split(" ")[0].toLowerCase(); 37 let resultMacro = name.split(" ")[1].toLowerCase(); 38 39 data[resultMacro][resultType] = e.target.value; 40 41 setResults(data); 42 } 43 44 return ( 45 <div> 46 <Head> 47 <title>Home</title> 48 <link rel="icon" href="/favicon.ico" /> 49 <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" /> 50 </Head> 51 52 <div className="container mx-auto"> 53 54 <div className="flex text-center"> 55 <div className="w-full m-4"> 56 <h1 className="text-4xl">Macro Compliance Tracker</h1> 57 </div> 58 </div> 59 60 <div className="flex text-center"> 61 <div className="w-1/3 bg-gray-200 p-4">Previous Day</div> 62 <div className="w-1/3 p-4">1/23/2020</div> 63 <div className="w-1/3 bg-gray-200 p-4">Next Day</div> 64 </div> 65 66 <div className="flex mb-4 text-center"> 67 <Result results={results.calories} /> 68 <Result results={results.carbs} /> 69 <Result results={results.fat} /> 70 <Result results={results.protein} /> 71 </div> 72 73 <div className="flex"> 74 <MCTForm data={results} item="Total" onChange={onChange} /> 75 <MCTForm data={results} item="Target" onChange={onChange} /> 76 <MCTForm data={results} item="Variant" onChange={onChange} /> 77 </div> 78 79 <div className="flex text-center"> 80 <div className="w-full m-4"> 81 <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> 82 Save 83 </button> 84 </div> 85 </div> 86 </div> 87 </div> 88 )} 89 90 export default Home
Além de limpar o código da IU, também adicionamos uma função onChange que será chamada toda vez que o valor de uma das caixas de entrada for alterado. A função onChange determinará qual caixa foi alterada e atualizará o valor de dados de acordo, bem como renderizará novamente a IU para mostrar as novas alterações.
Em seguida, vamos dar uma olhada em nossa implementação do componente
MCTForm
.1 import React from 'react' 2 3 const MCTForm = ({data, item, onChange}) => { 4 return( 5 <div className="w-1/3"> 6 <h2 className="text-3xl p-4">{item}</h2> 7 <div className="p-4"> 8 <label className="block">Calories</label> 9 <input type="number" name={item + " Calories"} className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" onChange={(e) => onChange(e)}></input> 10 </div> 11 <div className="p-4"> 12 <label className="block">Carbs</label> 13 <input type="number" name={item + " Carbs"} className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" onChange={(e) => onChange(e)}></input> 14 </div> 15 <div className="p-4"> 16 <label className="block">Fat</label> 17 <input type="number" name={item + " Fat"} className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" onChange={(e) => onChange(e)}></input> 18 </div> 19 <div className="p-4"> 20 <label className="block">Protein</label> 21 <input type="number" name={item + " Protein"} className="bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" onChange={(e) => onChange(e)}></input> 22 </div> 23 </div> 24 ) 25 } 26 27 export default MCTForm
Como você pode ver, este componente é responsável por renderizar nossos formulários. Como as caixas de entrada são as mesmas para os três tipos de formulário, podemos reutilizar o componente várias vezes e apenas alterar o tipo de dados com os quais estamos trabalhando.
Novamente, se olharmos para nosso aplicativo no navegador agora, não parece muito diferente. Mas agora o formulário funciona. Podemos substituir os valores e o aplicativo será atualizado dinamicamente mostrando nosso novo total de calorias e macros e se estamos ou não em conformidade com nossas metas. Vá em frente e brinque com ele um pouco para ter certeza de que tudo funciona.
Nosso aplicativo parece bom e está funcionando. Mas os dados estão todos na memória. Assim que atualizamos nossa página, todos os dados são redefinidos para os valores padrão. Nesse sentido, nosso aplicativo não é muito útil. Portanto, nossa próxima etapa será conectar o aplicativo a um banco de dados para que possamos começar a ver nosso progresso ao longo do tempo. Usaremos o MongoDB e o MongoDB Atlas para fazer isso.
Antes de podermos salvar nossos dados, precisaremos de um banco de dados. Para isso, usarei o MongoDB e o MongoDB Atlas para hospedar meu banco de dados. Se você ainda não tem o MongoDB Atlas, pode se cadastrar e usá-lo gratuitamente aqui. Caso contrário, entre em um cluster existente e crie um novo banco de dados. Dentro do MongoDB Atlas, usaria um cluster existente e configuraria um novo banco de dados chamado MCT. Com este novo banco de dados criado, criarei uma nova coleção chamada diariamente que armazenará meus resultados diários, macros de destino, bem como variantes permitidas.
Com o banco de dados configurado, também adicionarei alguns dias de dados. Fique à vontade para adicionar seus próprios dados ou, se quiser o conjunto de dados que estou usando, você pode obtê-lo aqui. Usarei o MongoDB Compass para importar e visualizar os dados, mas você pode importar os dados como quiser: use a CLI, adicione manualmente ou use o Compass.
Graças ao modelo de documento do MongoDB, posso representar os dados exatamente como os tinha na memória. Os únicos campos adicionais que terei em meu modelo do MongoDB são um campo
_id
que será um identificador exclusivo para o documento e um campo de data que representará os dados de uma data específica. A imagem abaixo mostra o modelo de dados para um documento no MongoDB Compass.Agora que temos alguns dados reais para trabalhar, vamos em frente e conectar nosso aplicativo Next.js ao nosso MongoDB database. Como o Next.js é um framework baseado no React que executa no lado do servidor com Node, usaremos o ótimo driver Node do Mongo para facilitar essa conexão.
Nosso diretório de páginas e componentes renderiza tanto o lado do servidor no carregamento inicial quanto o lado do cliente nas alterações de página subsequentes. O MongoDB Node Driver funciona somente no lado do servidor e presume que estamos trabalhando no back-end. Sem mencionar que nossas credenciais para o MongoDB precisam ser seguras e nunca compartilhadas com o cliente.
Não se preocupe, pois é aqui que o Next.js se destaca. No diretório de páginas, podemos criar um diretório especial adicional chamado api. Nesse diretório de API, como o nome indica, podemos criar endpoints de API que são executados exclusivamente no back-end. A melhor maneira de ver como isso funciona é criar um, então vamos fazer isso a seguir. No diretório páginas, crie um diretório api e crie um novo arquivo chamado daily.js.
No arquivo
daily.js
, adicione o seguinte código:1 export default (req, res) => { 2 res.statusCode = 200 3 res.setHeader('Content-Type', 'application/json') 4 res.end(JSON.stringify({ message: 'Hello from the Daily route' })) 5 }
Salve o arquivo, vá ao seu navegador e navegue até
localhost:3000/api/daily
. O que você verá é a resposta JSON de {message:'Hello from the Daily route'}
. Esse código só é executado no lado do servidor e a única coisa que o navegador recebe é a resposta que enviamos. Este parece o lugar ideal para estabelecer nossa conexão com o MongoDB.Embora possamos definir a conexão neste arquivo daily.js, em um aplicativo do mundo real, é provável que tenhamos vários endpoints de API e, por esse motivo, provavelmente é melhor estabelecer nossa conexão com o banco de dados em uma função de middleware que possamos passar para todas as nossas rotas de API. Portanto, como prática recomendada, vamos fazer isso aqui.
Crie um novo diretório de middleware na raiz da estrutura do projeto, junto com as páginas e os componentes, e chame-o de middleware. O nome do middleware não é reservado, portanto, tecnicamente, você poderia chamá-lo do que quiser, mas vou usar middleware como nome. Neste novo diretório, crie um arquivo chamado database.js. É aqui que configuraremos nossa conexão com o MongoDB, bem como instanciaremos o middleware para podermos usá-lo em nossas rotas de API.
Nosso código de middleware
database.js
terá a seguinte aparência:1 import { MongoClient } from 'mongodb'; 2 import nextConnect from 'next-connect'; 3 4 const client = new MongoClient('{YOUR-MONGODB-CONNECTION-STRING}', { 5 useNewUrlParser: true, 6 useUnifiedTopology: true, 7 }); 8 9 async function database(req, res, next) { 10 req.dbClient = client; 11 req.db = client.db('MCT'); 12 return next(); 13 } 14 15 const middleware = nextConnect(); 16 17 middleware.use(database); 18 19 export default middleware;
Se você estiver acompanhando, certifique-se de substituir a variável
{YOUR-MONGODB-CONNECTION-STRING}
por sua string de conexão e verifique se o client.db corresponde ao nome que você deu ao seu banco de dados. Não deixe de executar npm install --save mongodb next-connect
para garantir que você tenha todas as dependências corretas. A propósito, os nomes dos bancos de dados diferenciam maiúsculas de minúsculas. Salve este arquivo e agora abra o arquivo daily.js localizado no diretório pages/api.Teremos que atualizar este arquivo. Como agora queremos adicionar um middleware à nossa função, não usaremos mais uma função anônima aqui. Usaremos o next-connect para nos fornecer uma cadeia de manipuladores, bem como nos permitir vincular o middleware à função. Vamos dar uma olhada em como isso será.
1 import nextConnect from 'next-connect'; 2 import middleware from '../../middleware/database'; 3 4 const handler = nextConnect(); 5 6 handler.use(middleware); 7 8 handler.get(async (req, res) => { 9 10 let doc = await req.db.collection('daily').findOne() 11 console.log(doc); 12 res.json(doc); 13 }); 14 15 export default handler;
Como você pode ver, agora temos um objeto que nos dá muito mais flexibilidade. Podemos usar diferentes verbos HTTP, adicionar middleware e muito mais. O que o código acima faz é se conectar ao nosso MongoDB Atlas cluster e, a partir do banco de dados MCT e da coleção diária, encontrar e retornar um item e, em seguida, renderizá-lo na tela. Se pressionarmos
localhost:3000/api/daily
agora no navegador, veremos isto:Eba! Temos nossos dados e o modelo de dados corresponde ao nosso modelo de dados na memória, portanto, nossa próxima etapa será usar esses dados reais em vez de nossa amostra na memória. Para fazer isso, abriremos a página index.js.
Atualmente, nosso componente principal é instanciado com um modelo de dados na memória sobre o qual o restante do nosso aplicativo age. Vamos mudar isso. Next.js nos oferece algumas maneiras diferentes de fazer isso. Sempre podemos obter os dados de forma assíncrona do nosso componente React e, se você já usou o React alguma vez, deve ser natural, mas como estamos usando o Next.js, acho que há uma maneira diferente e talvez melhor de fazer isso.
Cada componente de página Next.js nos permite buscar dados no lado do servidor graças a uma função chamada
getStaticProps
. Quando essa função é chamada, o carregamento inicial da página é renderizado no lado do servidor, o que é ótimo para SEO. A página não é renderizada até que essa função seja concluída. Em index.js
, faremos as seguintes alterações:1 import fetch from 'isomorphic-unfetch' 2 const Home = ({data}) => { ... } 3 4 export async function getStaticProps(context) { 5 const res = await fetch("http://localhost:3000/api/daily"); 6 const json = await res.json(); 7 return { 8 props: { 9 data: json, 10 }, 11 }; 12 } 13 14 export default Home
Instale a biblioteca
isomorphic-unfetch
executando npm install --save isomorphic-unfetch
e, em seguida, abaixo do componente Home, adicione o método getStaticProps
. Nesse método, estamos apenas fazendo uma chamada de busca para nosso endpoint de API diário e armazenando esses dados json em uma prop chamada dados. Como criamos uma prop de dados, nós a passamos para o nosso componente Home e, nesse ponto, podemos remover nossa variável de dados na memória. Faça isso, salve o arquivo e atualize o navegador.Parabéns! Seus dados agora estão chegando do MongoDB. Mas no momento, isso só nos dá um resultado. Vamos fazer alguns ajustes finais para que possamos ver os resultados diários, além de atualizar os dados e salvá-los no banco de dados.
A primeira coisa que faremos é adicionar a capacidade de pressionar os botões Dia anterior e Dia seguinte e exibir os dados correspondentes. Não criaremos um novo endpoint, pois acho que nosso endpoint de API diário pode fazer a tarefa, só teremos que fazer alguns aprimoramentos. Vamos fazer isso primeiro.
Nosso novo arquivo API daily.js terá a seguinte aparência:
1 handler.get(async (req, res) => { 2 const { date } = req.query; 3 4 const dataModel = { "_id": new ObjectID(), "date": date, "calories": { "label": "Calories", "total": 0, "target": 0, "variant": 0 }, "carbs": { "label": "Carbs", "total": 0, "target": 0, "variant": 0 }, "fat": { "label" : "Fat", "total": 0, "target": 0, "variant": 0 }, "protein": { "label" : "Protein", "total": 0, "target": 0, "variant": 0 }} 5 6 let doc = {} 7 8 if(date){ 9 doc = await req.db.collection('daily').findOne({date: new Date(date)}) 10 } else { 11 doc = await req.db.collection('daily').findOne() 12 } 13 if(doc == null){ 14 doc = dataModel 15 } 16 res.json(doc) 17 });
Fizemos algumas alterações aqui, então vamos executá-las uma a uma. A primeira coisa que fizemos foi procurar um parâmetro de query de data para ver se um foi passado para nós. Se um parâmetro de data não foi passado, escolheremos apenas um item aleatório usando o método
findOne
. Mas, se recebermos uma data, consultaremos nosso MongoDB database e retornaremos os dados para a data especificada.Em seguida, como nosso conjunto de dados não é exaustivo, se formos muito para a frente ou para trás, ficaremos sem dados para exibir, então criaremos um objeto vazio na memória que servirá como nosso modelo de dados. Se não tivermos dados para uma data especificada em nosso banco de dados, apenas definiremos tudo como 0 e serviremos isso. Dessa forma, não precisamos fazer muito tratamento de erros na frente e sempre podemos contar com nosso backend para fornecer algum tipo de dado.
Agora, abra a página
index.js
e vamos adicionar a funcionalidade para ver os dias anteriores e seguintes. Usaremos dayjs para lidar com nossas datas, portanto, instale-o executando npm install --save dayjs
primeiro. Em seguida, faça as seguintes alterações em sua página index.js
:1 // Other Imports ... 2 import dayjs from 'dayjs' 3 4 const Home = ({data}) => { 5 const [results, setResults] = useState(data); 6 7 const onChange = (e) => { 8 } 9 10 const getDataForPreviousDay = async () => { 11 let currentDate = dayjs(results.date); 12 let newDate = currentDate.subtract(1, 'day').format('YYYY-MM-DDTHH:mm:ss') 13 const res = await fetch('http://localhost:3000/api/daily?date=' + newDate) 14 const json = await res.json() 15 16 setResults(json); 17 } 18 19 const getDataForNextDay = async () => { 20 let currentDate = dayjs(results.date); 21 let newDate = currentDate.add(1, 'day').format('YYYY-MM-DDTHH:mm:ss') 22 const res = await fetch('http://localhost:3000/api/daily?date=' + newDate) 23 const json = await res.json() 24 25 setResults(json); 26 } 27 28 return ( 29 <div className="flex text-center"> 30 <div className="w-1/3 bg-gray-200 p-4"><button onClick={getDataForPreviousDay}>Previous Day</button></div> 31 <div className="w-1/3 p-4">{dayjs(results.date).format('MM/DD/YYYY')}</div> 32 <div className="w-1/3 bg-gray-200 p-4"><button onClick={getDataForNextDay}>Next Day</button></div> 33 </div> 34 35 )}
Adicionamos dois novos métodos, um para obter os dados do dia anterior e outro para obter os dados do dia seguinte. Em nossa IU, também tornamos o rótulo de data dinâmico para que ele seja exibido e nos diga o dia em que estamos olhando no momento. Com essas alterações, atualize seu navegador e você poderá ver os novos dados dos dias inseridos em seu banco de dados. Se uma data específica não existir, ela mostrará 0 para tudo.
Por fim, vamos encerrar este tutorial adicionando a funcionalidade final ao nosso aplicativo, que será fazer atualizações e salvar novos dados em nosso MongoDB database. Mais uma vez, achamos que não precisamos de um novo ponto de extremidade para isso, então usaremos nossa API daily.js existente. Como estamos usando a convenção do manipulador e atualmente só manipulamos o verbo GET, vamos acrescentar lógica para manipular um POST no ponto de extremidade.
1 handler.post(async (req, res) => { 2 let data = req.body; 3 data = JSON.parse(data); 4 data.date = new Date(data.date); 5 let doc = await req.db.collection('daily').updateOne({date: new Date(data.date)}, {$set:data}, {upsert: true}) 6 7 res.json({message: 'ok'}); 8 })
O código é bem simples. Vamos obter nossos dados no corpo da solicitação, analisá-los e, em seguida, salvá-los em nossa coleção do MongoDB diária usando o método
updateOne()
. Vamos dar uma olhada nos valores que estamos passando para o método updateOne()
.O primeiro valor que passaremos será aquele com o qual compararemos. Portanto, em nossa coleção, se descobrirmos que a data específica já contém dados, nós a atualizaremos. O segundo valor serão os dados que estamos configurando e, no nosso caso, apenas vamos definir o que o cliente front-end nos enviar. Por fim, estamos definindo o valor upsert como verdadeiro. O que isso fará é que, se não pudermos fazer a correspondência em uma data existente, ou seja, se ainda não tivermos dados para essa data, criaremos um novo registro.
Com nossa implementação de backend concluída, vamos adicionar a funcionalidade em nosso frontend para que, quando o usuário apertar o botão Salvar, os dados sejam atualizados corretamente. Abra o arquivo index.js e faça as seguintes alterações:
1 const Home = ({data}) => { 2 const updateMacros = async () => { 3 const res = await fetch('http://localhost:3000/api/daily', { 4 method: 'post', 5 body: JSON.stringify(results) 6 }) 7 } 8 9 return ( 10 <div className="flex text-center"> 11 <div className="w-full m-4"> 12 <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" onClick={updateMacros}> 13 Save 14 </button> 15 </div> 16 </div> 17 )}
Nosso novo método updateMacros fará uma solicitação POST para nosso ponto de extremidade diário da API com os novos dados. Experimente já! Você poderá atualizar macros existentes ou criar dados para novos dias para os quais ainda não possui dados. Conseguimos!
Vimos muito conteúdo no tutorial de hoje. Next.js é um framework poderoso para criar aplicativos web modernos, e ter um banco de dados flexível com base no MongoDB tornou possível criar um aplicativo completo num instante. Omitimos alguns itens por brevidade, como tratamento de erros e implantação, mas fique à vontade para clonar o aplicativo do GitHub, cadastrar-se no MongoDB Atlas gratuitamente e construir a partir dessa base.