Integre o MongoDB às funções do Vercel para a experiência sem servidor
Avalie esse Tutorial
{ Você está trabalhando com o Functions as a Service (FaaS), geralmente chamado de serverless, mas não consegue fazer seu banco de dados funcionar? Dada a natureza dessas funções sem servidor, interagir com um banco de dados é uma experiência um pouco diferente do que se você fosse criar seu próprio back-end totalmente hospedado.
Por que é uma experiência diferente, embora?
Os bancos de dados em geral, não apenas o MongoDB, podem ter uma quantidade finita de conexões simultâneas. Quando você hospeda seu próprio aplicativo da web, esse aplicativo normalmente se conecta ao seu banco de dados uma vez e enquanto o aplicativo estiver em execução, o mesmo acontece com a conexão com o banco de dados. No entanto, as funções oferecem uma experiência diferente. Em vez de um aplicativo sempre disponível, agora você está trabalhando com um aplicativo que pode ou não estar disponível no momento da solicitação para economizar recursos. Se você tentar se conectar ao seu banco de dados em sua lógica de função, correrá o risco de muitas conexões. Se a função desligar, hibernar ou semelhante, você correrá o risco de que sua conexão com o banco de dados não esteja mais disponível.
Neste tutorial, vamos ver como usar o driverdoMongoDB Node.js com funções do Vercel, algo bastante comum ao desenvolver aplicativos Next.js.
Há alguns requisitos que devem ser atendidos antes de começar a usar este tutorial, dependendo de até onde você deseja ir.
- Você deve ter um MongoDB Atlas cluster implantado, camada grátis (M0) ou melhor.
- Uma versão recente do Node.js e NPM deve estar disponível.
Embora entraremos em detalhes do MongoDB Atlas mais tarde neste tutorial, você já deve ter uma conta do MongoDB Atlas e um cluster implantado. Se precisar de ajuda com qualquer um deles, considere conferir este tutorial.
A grande coisa que você precisará é o Node.js. Nós o usaremos para desenvolver e testar nosso aplicativo Next.js.
Criar um novo projeto Next.js é fácil quando se trabalha com a CLI. Em uma linha de comando, supondo que o Node.js esteja instalado, execute o seguinte comando:
1 npx create-next-app@latest
Você será solicitado a fornecer algumas informações que resultarão na criação do seu projeto. A qualquer momento deste tutorial, você pode executar
npm run dev
para criar e atender seu aplicativo localmente. Você também poderá testar suas funções do Vercel!Antes de prosseguirmos, vamos adicionar a dependência do driver MongoDB Node.js:
1 yarn add mongodb
Não vamos explorá-lo neste tutorial, mas o Vercel oferece um modelo inicial com a integração do MongoDB Atlas já configurada. Se você quiser saber mais, confira o tutorial de Jesse Hall: How to Connect MongoDB Atlas to Vercel usando a nova integração. Em vez disso, vamos ver como fazer as coisas manualmente para ter uma ideia do que está acontecer em cada estágio do ciclo de desenvolvimento.
Neste ponto, você já deve ter uma conta do MongoDB Atlas com um projeto e cluster criados. O nível gratuito é adequado para este tutorial.
Em vez de usar nossa mente para criar um novo conjunto de dados para esse exemplo, vamos usar um dos bancos de dados de amostra disponíveis para os usuários do MongoDB.
No painel do MongoDB Atlas, clique no menu de reticências de um dos clusters e, em seguida, escolha carregar os conjuntos de dados de amostra. Pode levar alguns minutos, então dê algum tempo.
Para este tutorial, usaremos o banco de dadossample_restaurants, mas, na verdade, isso não importa, pois o foco deste tutorial é a instalação e a configuração, e não os dados reais.
Com o conjunto de dados de amostra carregado, Go e crie um novo usuário na aba "Acesso ao banco de dados" do painel, seguido pela adição de seu IP às regras de "Acesso à rede". Você precisará fazer isso para se conectar ao MongoDB Atlas a partir do seu aplicativo Next.js. Se você optar por implantar seu aplicativo, precisará adicionar uma regra
0.0.0.0
de acordo com adocumentação do Vercel.Next.js é uma dessas tecnologias em que existem algumas maneiras de resolver o problema. Poderemos interagir com o MongoDB no tempo de construção, criando um site 100% gerado estático, mas há muitos motivos pelos quais podemos querer manter as coisas adhoc em uma função sem servidor.
Dentro do seu projeto Next.js, crie um .env.local arquivo com as seguintes variáveis:
1 NEXT_ATLAS_URI=YOUR_ATLAS_URI_HERE 2 NEXT_ATLAS_DATABASE=sample_restaurants 3 NEXT_ATLAS_COLLECTION=restaurants
Lembre-se de que estamos usando o banco de dadossample_restaurants neste exemplo, mas você pode ser ousado e usar o que quiser. Não se lembre de trocar as informações de conexão no arquivo.env.local pelas suas.
Em seguida, crie um arquivolib/mongodb.js em seu projeto. É aqui que trataremos das etapas reais de conexão. Preencha o arquivo com o seguinte código:
1 import { MongoClient } from "mongodb"; 2 3 const uri = process.env.NEXT_ATLAS_URI; 4 const options = { 5 useUnifiedTopology: true, 6 useNewUrlParser: true, 7 }; 8 9 let mongoClient = null; 10 let database = null; 11 12 if (!process.env.NEXT_ATLAS_URI) { 13 throw new Error('Please add your Mongo URI to .env.local') 14 } 15 16 export async function connectToDatabase() { 17 try { 18 if (mongoClient && database) { 19 return { mongoClient, database }; 20 } 21 if (process.env.NODE_ENV === "development") { 22 if (!global._mongoClient) { 23 mongoClient = await (new MongoClient(uri, options)).connect(); 24 global._mongoClient = mongoClient; 25 } else { 26 mongoClient = global._mongoClient; 27 } 28 } else { 29 mongoClient = await (new MongoClient(uri, options)).connect(); 30 } 31 database = await mongoClient.db(process.env.NEXT_ATLAS_DATABASE); 32 return { mongoClient, database }; 33 } catch (e) { 34 console.error(e); 35 } 36 }
Pode não parecer muito, mas muitas coisas importantes estão acontecendo no arquivo acima, específicas para Next.js e funções sem servidor. Especificamente, dê uma olhada na função
connectToDatabase
:1 export async function connectToDatabase() { 2 try { 3 if (mongoClient && database) { 4 return { mongoClient, database }; 5 } 6 if (process.env.NODE_ENV === "development") { 7 if (!global._mongoClient) { 8 mongoClient = await (new MongoClient(uri, options)).connect(); 9 global._mongoClient = mongoClient; 10 } else { 11 mongoClient = global._mongoClient; 12 } 13 } else { 14 mongoClient = await (new MongoClient(uri, options)).connect(); 15 } 16 database = await mongoClient.db(process.env.NEXT_ATLAS_DATABASE); 17 return { mongoClient, database }; 18 } catch (e) { 19 console.error(e); 20 } 21 }
O objetivo da função acima é fornecer-nos uma conexão de cliente para trabalhar, bem como um banco de dados. No entanto, os detalhes mais refinados sugerem que só precisamos estabelecer uma nova conexão se não existir uma e não enviar spam para nosso banco de dados com conexões se estivermos no modo de desenvolvimento para Next.js. O servidor de desenvolvimento local se comporta de forma diferente da que você teria na produção, por isso a necessidade de verificar.
Lembre-se, as quantidades de conexão são finitas e só devemos nos conectar se ainda não estivermos conectados.
Então, o que estamos fazendo na função é que primeiro verificamos se essa conexão existe. Em caso afirmativo, retorne-o e permita que o que estiver chamando a função use essa conexão. Se a conexão não existir e estivermos no modo de desenvolvimento, verificamos se temos uma sessão em cache e a usamos, se tivermos. Caso contrário, precisamos criar uma conexão e armazená-la em cache para o modo de desenvolvimento ou produção.
Se você entender alguma coisa do código acima, entenda que estamos apenas criando conexões se as conexões ainda não existirem.
Já fizemos a parte difícil. Temos um sistema de gerenciamento de conexões em vigor para o MongoDB ser usado em todo o nosso aplicativo Vercel. A próxima parte envolve a criação de endpoints de API, de forma quase idêntica ao Express Framework, e consumi-los a partir do frontend do Next.js.
Então, com o que isso se parece exatamente?
Em seu projeto, crie um arquivopages/api/list.js com o seguinte código JavaScript:
1 import { connectToDatabase } from "../../lib/mongodb"; 2 3 export default async function handler(request, response) { 4 5 const { database } = await connectToDatabase(); 6 const collection = database.collection(process.env.NEXT_ATLAS_COLLECTION); 7 8 const results = await collection.find({}) 9 .project({ 10 "grades": 0, 11 "borough": 0, 12 "restaurant_id": 0 13 }) 14 .limit(10).toArray(); 15 16 response.status(200).json(results); 17 18 }
As funções do Vercel existem no diretóriopages/api. Neste caso, estamos criando uma função com o objetivo de listar dados. Especificamente, vamos listar dados de restaurantes.
Em nosso código acima, estamos aproveitando a função
connectToDatabase
de nosso código de gerenciamento de conexão. Quando executamos a função, estamos obtendo uma conexão sem nos preocuparmos se precisamos criar ou reutilizar uma. O código da função subjacente cuida disso para nós.Com uma conexão, podemos encontrar todos os documentos dentro de uma coleção. Nem todos os campos são importantes para nós, então estamos usando uma projeção para excluir o que não queremos. Em vez de retornar todos os documentos dessa grande coleção, estamos limitando os resultados a apenas alguns.
Os resultados são retornados para qualquer código ou cliente externo que os solicite.
Se quiséssemos consumir o endpoint de dentro do aplicativo Next.js, poderíamos fazer algo como o seguinte no arquivopages/index.js:
1 import { useEffect, useState } from "react"; 2 import Head from 'next/head' 3 import Image from 'next/image' 4 import styles from '../styles/Home.module.css' 5 6 export default function Home() { 7 8 const [restaurants, setRestaurants] = useState([]); 9 10 useEffect(() => { 11 (async () => { 12 const results = await fetch("/api/list").then(response => response.json()); 13 setRestaurants(results); 14 })(); 15 }, []); 16 17 return ( 18 <div className={styles.container}> 19 <Head> 20 <title>Create Next App</title> 21 <meta name="description" content="Generated by create next app" /> 22 <link rel="icon" href="/favicon.ico" /> 23 </Head> 24 25 <main className={styles.main}> 26 <h1 className={styles.title}> 27 MongoDB with <a href="https://nextjs.org">Next.js!</a> Example 28 </h1> 29 <br /> 30 <div className={styles.grid}> 31 {restaurants.map(restaurant => ( 32 <div className={styles.card} key={restaurant._id}> 33 <h2>{restaurant.name}</h2> 34 <p>{restaurant.address.street}</p> 35 </div> 36 ))} 37 </div> 38 </main> 39 </div> 40 ) 41 }
Ignorando o código Next.js clichê, adicionamos um
useState
e useEffect
como o seguinte:1 const [restaurants, setRestaurants] = useState([]); 2 3 useEffect(() => { 4 (async () => { 5 const results = await fetch("/api/list").then(response => response.json()); 6 setRestaurants(results); 7 })(); 8 }, []);
O código acima consumirá a API quando o componente carregar. Podemos então renderizá-lo na seção a seguir:
1 <div className={styles.grid}> 2 {restaurants.map(restaurant => ( 3 <div className={styles.card} key={restaurant._id}> 4 <h2>{restaurant.name}</h2> 5 <p>{restaurant.address.street}</p> 6 </div> 7 ))} 8 </div>
Não há nada fora do comum acontecendo no processo de consumo ou renderização. O trabalho pesado que era importante estava na própria função, bem como em nosso arquivo de gerenciamento de conexões.
Você acabou de ver como usar o MongoDB Atlas com funções do Vercel, que é uma solução sem servidor que exige um tipo de abordagem diferente. Lembre-se, ao lidar com serverless, a disponibilidade de suas funções está no ar. Você não quer gerar muitas conexões e não quer tentar usar conexões que não existem. Resolvemos isso armazenando nossas conexões em cache e usando a conexão em cache, se disponível. Caso contrário, crie uma nova conexão.