Introdução ao Deno 2.0 e MongoDB
Jesse Hall13 min read • Published Jan 21, 2022 • Updated Oct 22, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
Deno, o tempo de execução "Modern" para JavaScript e Typescript criado em Rust, lançaram recentemente a versão 2.0. Esta atualização principal traz melhorias significativas e novos recursos que tornam o Deno uma opção ainda mais chamativa para os desenvolvedores.
Se você estiver familiarizado com o Node.js, achará o Deno bastante semelhante, mas com algumas melhorias importantes. Do mesmo criador de Node.js, Ryan Dahl, Deno foi projetado para ser um sucessor mais seguro e moderno do Node.js
Curiosidade: Deno é um anagrama. Reorganizar as letras no nó para escrever Deno.
O Deno agora oferece suporte a gerenciadores de pacote como npm e JSR, mantendo a capacidade de importar diretamente de URLs sem um gerenciador de pacote . Ele usa módulos ES, tem suporte de primeira classe
await
, tem testes integrados e implementa APIs padrão da web sempre que possível, como fetch
e localStorage
integrados. Essa flexibilidade no gerenciamento de dependências permite que os desenvolvedores escolham a abordagem que melhor se adequa às necessidades do projeto .Além disso, também é muito seguro. É completamente bloqueado por padrão e exige que você habilite cada método de acesso especificamente. Isso faz com que o Deno emparelhe bem com o MongoDB , pois ele também é super seguro por padrão.
Aprenda assistindo
Aqui está uma versão em vídeo deste artigo, se você preferir assistir.
O Deno 2.0 apresenta vários recursos e melhorias interessantes:
- Compatibilidade npm aprimorada: o Deno agora suporta uma variedade mais ampla de pacotes npm, incluindo o driver oficial do MongoDB .
- Melhor desempenho: Melhorias significativas na velocidade em várias operações.
- Executador de testes integrado: não há necessidade de estruturas de testes externas.
- servidor HTTP nativo: construa aplicativos web simples sem frameworks de terceiros.
- Modelo de segurança aprimorado: permissões mais granulares e recursos de segurança aprimorados.
Neste tutorial, exploraremos alguns desses novos recursos ao criar um aplicação CRUD simples usando o MongoDB.
- Conhecimento básico do Typescript
- Noções básicas sobre conceitos MongoDB
- Familiaridade com APIs RESTful
Para começar a usar o Deno 2.0, você precisará instalar ou atualizar o Deno no seu sistema.
- Para macOS e Linux:
curl -fsSL https://deno.land/install.sh | sh
- Para Windows (usando o PowerShell):
irm https://deno.land/install.ps1 | iex
Se você estiver usando o VS Code, é altamente recomendável instalar a extensão oficial do Denoand. Esta extensão permite a verificação de tipo, integração com IntelliSense, Deno CLI e muito mais.
Com o Deno 2.0, podemos criar um servidor HTTP simples com recursos de roteamento usando apenas recursos integrados. Vamos começar criando um arquivo
server.ts
:1 const PORT = 3000; 2 3 async function handler(req: Request): Promise<Response> { 4 const url = new URL(req.url); 5 const path = url.pathname; 6 7 if (req.method === "GET" && path === "/") { 8 return new Response("Hello, World!"); 9 } else if (req.method === "POST" && path === "/api/todos") { 10 // Handle POST /api/todos 11 } else if (req.method === "GET" && path === "/api/todos") { 12 // Handle GET /api/todos 13 } else if (req.method === "GET" && path === "/api/todos/incomplete/count") { 14 // Handle GET /api/todos/incomplete/count 15 } else if (req.method === "GET" && path.startsWith("/api/todos/")) { 16 // Handle GET /api/todos/:id 17 } else if (req.method === "PUT" && path.startsWith("/api/todos/")) { 18 // Handle PUT /api/todos/:id 19 } else if (req.method === "DELETE" && path.startsWith("/api/todos/")) { 20 // Handle DELETE /api/todos/:id 21 } else { 22 return new Response("Not Found", { status: 404 }); 23 } 24 } 25 26 console.log(`HTTP webserver running. Access it at: http://localhost:${PORT}/`); 27 Deno.serve({ port: PORT }, handler);
Isso configura um servidor HTTP básico usando a função
Deno.serve
integrada do Deno. A funçãohandler
processa as solicitações recebidas, roteando-as com base no método HTTP e no caminho da URL . Ele inclui espaços reservados para lidar com várias operações CRUD em um recurso "todos", bem como uma rota especial para contar todos incompletos. Se nenhuma rota correspondente for encontrada, ela retornará uma resposta 404 "Não encontrado". O servidor escuta na porta 3000 e uma mensagem do console é registrada para indicar que o servidor está em execução.Agora, podemos iniciar nosso servidor Deno executando o seguinte comando:
1 deno run --allow-env --allow-read --allow-net --allow-sys --env --watch server.ts
Isso iniciará nosso servidor e observará as alterações em nosso código. Se você fizer alterações, o servidor será reiniciado automaticamente. Lembre-se de que o Deno é muito seguro por padrão, portanto, precisamos usar os sinalizadores
--allow-*
apropriados para permitir que nosso servidor funcione.Observação: com o Deno 2.0, agora você pode usar sinalizadores abreviados para permitir as permissões necessárias.
1 deno -ERNS --env --watch server.ts
O sinalizador
-E
permite variáveis de ambiente, R
permite a leitura de arquivos, N
permite o acesso à rede, S
permite o acesso ao sistema e --env
permite o uso de variáveis de ambiente.Agora, podemos testar nosso servidor navegando até
http://localhost:3000
em nosso navegador. Devemos ver "Hello, World! exibido no navegador.Agora que temos um servidor básico, vamos configurar nossa conexão MongoDB . Usaremos o pacote oficial npm do MongoDB , que agora é totalmente compatível com o Deno 2.0.
Primeiro, vamos criar um novo arquivo chamado
db.ts
:1 import { MongoClient } from "npm:mongodb@5.6.0"; 2 3 const MONGODB_URI = Deno.env.get("MONGODB_URI") || ""; 4 const DB_NAME = Deno.env.get("DB_NAME") || "todo_db"; 5 6 if (!MONGODB_URI) { 7 console.error("MONGODB_URI is not set"); 8 Deno.exit(1); 9 } 10 11 const client = new MongoClient(MONGODB_URI); 12 13 try { 14 await client.connect(); 15 await client.db("admin").command({ ping: 1 }); 16 console.log("Connected to MongoDB"); 17 } catch (error) { 18 console.error("Error connecting to MongoDB:", error); 19 Deno.exit(1); 20 } 21 22 const db = client.db(DB_NAME); 23 const todos = db.collection("todos"); 24 25 export { db, todos };
Se você estiver familiarizado com o Node.js, notará que o Deno faz as coisas de forma um pouco diferente. Em vez de usar um arquivo
package.json
e baixar todos os pacotes para o diretório do projeto , o Deno usa caminhos de arquivo ou URLs para fazer referência às importações do módulo. Os módulos são baixados e armazenados em cache localmente, mas isso é feito globalmente e não por projeto. Isso elimina muito do bloat inerente ao Node.js e sua pastanode_modules
.Com o Deno 2.0, você pode optar por usar um gerenciador de pacote como npm ou JSR. Você também pode usar um projeto Node.js existente com o Deno e ele utilizará o arquivo
package.json
.Neste arquivo, importamos o
MongoClient
do pacote oficial npm do MongoDB e criamos uma nova instância dele. Em seguida, nos conectamos à nossa instância MongoDB usando a variável de ambienteMONGODB_URI
. Se a variável não estiver definida, registramos um erro e encerramos o processo.Uma vez conectado, ping no banco de dados de dados para garantir que nossa conexão esteja funcionando. Se o ping falhar, registramos um erro e encerramos o processo.
Definimos nosso banco de dados de dados e collection e os exportamos para que possamos usá-los em nosso aplicação.
Antes de podermos usar esse arquivo, precisaremos definir nossas variáveis de ambiente
MONGODB_URI
e DB_NAME
. Podemos fazer isso criando um arquivo.env
e adicionando o seguinte:1 MONGODB_URI="...your connection string..." 2 DB_NAME="todo_db"
A maneira mais fácil de obter sua string de conexão é usar a GUI do MongoDB Atlas . Se você ainda não tiver uma conta, inscreva-se em uma camada grátis para sempre. Confira a documentação doConnect to Your Cluster para obter mais informações sobre como obter sua string de conexão.
Neste ponto, precisamos configurar cada função para cada rota. Eles serão responsáveis pela criação, leitura, atualização e exclusão de documentos (CRUD) em nosso banco de banco de dados MongoDB .
Vamos criar uma nova pasta chamada
controllers
e um arquivo dentro dela chamado todoController.ts
.No arquivo
todoController.ts
, primeiro importaremos nossa collectiontodos
e o ObjectId
do pacotemongodb
npm :1 import { todos } from "../db.ts"; 2 import { ObjectId } from "npm:mongodb@5.6.0";
Agora, podemos começar a criar nossa primeira função de rota. Chamaremos essa função
addTodo
. Esta função adicionará um novo item de tarefa à nossa coleção de banco de dados de dados.1 // ... imports 2 3 async function addTodo(req: Request): Promise<Response> { 4 try { 5 const body = await req.json(); 6 const result = await todos.insertOne(body); 7 return new Response(JSON.stringify({ id: result.insertedId }), { 8 status: 201, 9 headers: { "Content-Type": "application/json" }, 10 }); 11 } catch (error) { 12 return new Response(JSON.stringify({ error: error.message }), { 13 status: 400, 14 headers: { "Content-Type": "application/json" }, 15 }); 16 } 17 } 18 19 export { addTodo };
A função
addTodo
pega um objetoRequest
, extrai e analisa seu corpo JSON e tenta inserir esses dados na coleçãotodos
. Em caso de sucesso, ele retorna um Response
com um código de status 201 e o novo ID do documento. Se ocorrer um erro, ele retornará um código de status 400 com a mensagem de erro. A função usa try-catch para tratamento de erros e retorna respostas JSON, aderindo às práticas da API RESTful.Vamos adicionar a função
addTodo
à nossa função handler
no arquivoserver.ts
.1 import { addTodo } from "./controllers/todoController.ts"; 2 // ... existing code 3 4 async function handler(req: Request): Promise<Response> { 5 // ... existing code 6 else if (req.method === "POST" && path === "/api/todos") { 7 return await addTodo(req); 8 } 9 // ... existing code 10 }
Agora, podemos testar nossa função
addTodo
. Usaremos o comandocurl
para enviar uma solicitação POST para nossa rota de criação. Como alternativa, você pode usar uma ferramenta como Postman, Insonia ou Cloud Client no VS Code para enviar a solicitação.1 curl -X POST http://localhost:3000/api/todos -H "Content-Type: application/json" -d '{"title": "Todo 1", "complete": false}'
Você deverá ver uma resposta com um código de status 201 e o ID da nova tarefa.
Vamos para as rotas de leitura. Começaremos com uma rota que recebe todos os nossos clientes, chamada
getTodos
. Isso Go para o arquivotodoController.ts
.1 // ... existing code 2 3 async function getTodos(): Promise<Response> { 4 try { 5 const allTodos = await todos.find().toArray(); 6 return new Response(JSON.stringify(allTodos), { 7 headers: { "Content-Type": "application/json" }, 8 }); 9 } catch (error) { 10 return new Response(JSON.stringify({ error: error.message }), { 11 status: 500, 12 headers: { "Content-Type": "application/json" }, 13 }); 14 } 15 } 16 17 export { addTodo, getTodos };
Esta função recupera todos os itens de tarefas da collection todos usando o método
find
do MongoDB . Se for bem-sucedido, ele retornará uma resposta JSON contendo todos os Todos com um código de status 200 . Se ocorrer um erro durante o processo, ele o detectará e retornará uma resposta JSON com a mensagem de erro e um código de status 500 . Finalmente, a função é exportada.Vamos adicionar isso à nossa função
handler
no arquivoserver.ts
.1 import { addTodo, getTodos } from "./controllers/todoController.ts"; 2 // ... existing code 3 4 async function handler(req: Request): Promise<Response> { 5 // ... existing code 6 else if (req.method === "GET" && path === "/api/todos") { 7 return await getTodos(); 8 } 9 // ... existing code 10 }
Agora, podemos testar nossa função
getTodos
. Usaremos o comandocurl
para enviar uma solicitação GET para nossa rota de leitura.1 curl http://localhost:3000/api/todos
Você deve ver uma resposta com um código de status 200 e uma array JSON de Todos. Observe o
id
na resposta. Vamos precisar disso para nossa próxima rota.Em seguida, configuraremos nossa função para ler um único documento. Vamos chamar isso
getTodo
e, novamente, colocaremos isso no arquivotodoController.ts
.1 // ... existing code 2 3 async function getTodo(id: string): Promise<Response> { 4 try { 5 const todo = await todos.findOne({ _id: new ObjectId(id) }); 6 if (!todo) { 7 return new Response(JSON.stringify({ error: "Todo not found" }), { 8 status: 404, 9 headers: { "Content-Type": "application/json" }, 10 }); 11 } 12 return new Response(JSON.stringify(todo), { 13 headers: { "Content-Type": "application/json" }, 14 }); 15 } catch (error) { 16 return new Response(JSON.stringify({ error: error.message }), { 17 status: 500, 18 headers: { "Content-Type": "application/json" }, 19 }); 20 } 21 } 22 23 export { addTodo, getTodos, getTodo };
Esta função recupera um único item de tarefa da coleção todos usando o método
findOne
do MongoDB com base no id
fornecido. Se o item de tarefas for encontrado, ele retornará uma resposta JSON com os dados de tarefas e um código de status 200 . Se o item de tarefa não for encontrado, ele retornará um código de status 404 com uma mensagem de erro. Se ocorrer um erro durante o processo, ele o detectará e retornará uma resposta JSON com a mensagem de erro e um código de status 500 . Finalmente, a função é exportada.Vamos adicionar isso à nossa função
handler
no arquivoserver.ts
.1 import { addTodo, getTodos, getTodo } from "./controllers/todoController.ts"; 2 // ... existing code 3 4 async function handler(req: Request): Promise<Response> { 5 // ... existing code 6 else if (req.method === "GET" && path.startsWith("/api/todos/")) { 7 const id = path.split("/")[3]; 8 return await getTodo(id); 9 } 10 // ... existing code 11 }
Nesta rota, precisamos extrair o
id
do caminho da URL e passá-lo para nossa funçãogetTodo
. Podemos fazer isso dividindo o caminho no /
e pegando o quarto elemento (índice 3).Agora, podemos testar nossa função
getTodo
. Usaremos o comandocurl
para enviar uma solicitação GET para nossa rota única de leitura. Lembre-se daquele _id
que tiramos do teste anterior? Precisamos usar isso aqui.1 curl http://localhost:3000/api/todos/<...id here...> 2 # example: 3 # curl http://localhost:3000/api/todos/67005eef3bf67a631efc95f6
Você deverá ver uma resposta com um código de status 200 e um objeto JSON representando a tarefa.
Agora que temos documentos, vamos configurar nossa rota de atualização para nos permitir fazer alterações nos documentos existentes. Chamaremos essa função
updateTodo
.1 // ... existing code 2 3 async function updateTodo(id: string, req: Request): Promise<Response> { 4 try { 5 const body = await req.json(); 6 const result = await todos.updateOne( 7 { _id: new ObjectId(id) }, 8 { $set: body }, 9 ); 10 if (result.matchedCount === 0) { 11 return new Response(JSON.stringify({ error: "Todo not found" }), { 12 status: 404, 13 headers: { "Content-Type": "application/json" }, 14 }); 15 } 16 return new Response(JSON.stringify({ updated: result.modifiedCount }), { 17 headers: { "Content-Type": "application/json" }, 18 }); 19 } catch (error) { 20 return new Response(JSON.stringify({ error: error.message }), { 21 status: 400, 22 headers: { "Content-Type": "application/json" }, 23 }); 24 } 25 } 26 27 export { addTodo, getTodos, getTodo, updateTodo };
Esta função atualiza um item de tarefa na coleção todos com base no
id
fornecido. Ele extrai os dados atualizados do corpo da solicitação, usa o updateOne
método MongoDBmethod para modificar o documento existente e retorna uma resposta JSON indicando o número de documentos modificados. Se o item de tarefa não for encontrado, ele retornará um código de status 404 com uma mensagem de erro. Se ocorrer um erro durante o processo, ele o detectará e retornará uma resposta JSON com a mensagem de erro e um código de status 400 . Finalmente, a função é exportada.Vamos adicionar isso à nossa função
handler
no arquivoserver.ts
.1 import { addTodo, getTodos, getTodo, updateTodo } from "./controllers/todoController.ts"; 2 // ... existing code 3 4 async function handler(req: Request): Promise<Response> { 5 // ... existing code 6 else if (req.method === "PUT" && path.startsWith("/api/todos/")) { 7 const id = path.split("/")[3]; 8 return await updateTodo(id, req); 9 } 10 // ... existing code 11 }
Desta vez, passaremos o
id
e a solicitação para nossa funçãoupdateTodo
.Agora, podemos testar nossa função
updateTodo
. Usaremos o comandocurl
para enviar uma solicitação PUT para nossa rota de atualização. Você pode usar o _id
dos testes anteriores.1 curl -X PUT http://localhost:3000/api/todos/<...id here...> -H "Content-Type: application/json" -d '{"title": "Updated Todo", "complete": true}'
Você deverá ver uma resposta com um código de status 200 e um objeto JSON indicando o número de documentos modificados.
Em seguida, configuraremos nossa rota de exclusão. Chamaremos isso
deleteTodo
.1 // ... existing code 2 3 async function deleteTodo(id: string): Promise<Response> { 4 try { 5 const result = await todos.deleteOne({ _id: new ObjectId(id) }); 6 if (result.deletedCount === 0) { 7 return new Response(JSON.stringify({ error: "Todo not found" }), { 8 status: 404, 9 headers: { "Content-Type": "application/json" }, 10 }); 11 } 12 return new Response(JSON.stringify({ deleted: result.deletedCount }), { 13 status: 200, 14 headers: { "Content-Type": "application/json" }, 15 }); 16 } catch (error) { 17 return new Response(JSON.stringify({ error: error.message }), { 18 status: 400, 19 headers: { "Content-Type": "application/json" }, 20 }); 21 } 22 } 23 24 export { addTodo, getTodos, getTodo, updateTodo, deleteTodo };
Esta função exclui um item de tarefa da coleção todos com base no
id
fornecido. Ele usa o métododeleteOne
MongoDB para remover o documento e retorna um código de status 200 em caso de sucesso, junto com uma resposta JSON indicando o número de documentos excluídos. Se o item de tarefa não for encontrado, ele retornará um código de status 404 com uma mensagem de erro. Se ocorrer um erro durante o processo, ele o detectará e retornará uma resposta JSON com a mensagem de erro e um código de status 400 . Finalmente, a função é exportada.Vamos adicionar isso à nossa função
handler
no arquivoserver.ts
.1 import { addTodo, getTodos, getTodo, updateTodo, deleteTodo } from "./controllers/todoController.ts"; 2 // ... existing code 3 4 async function handler(req: Request): Promise<Response> { 5 // ... existing code 6 else if (req.method === "DELETE" && path.startsWith("/api/todos/")) { 7 const id = path.split("/")[3]; 8 return await deleteTodo(id); 9 } 10 // ... existing code 11 }
Desta vez, passaremos o
id
para nossa funçãodeleteTodo
.Agora, podemos testar nossa função
deleteTodo
. Usaremos o comandocurl
para enviar uma solicitação DELETE para nossa rota de exclusão.1 curl -X DELETE http://localhost:3000/api/todos/<...id here...>
Você deverá ver uma resposta com um código de status 204 .
Vamos criar mais uma rota de treinamento. Este demonstrará um pipeline de agregação básico. Chamaremos isso
getIncompleteTodos
.1 // ... existing code 2 3 async function getIncompleteTodos(): Promise<Response> { 4 try { 5 const pipeline = [ 6 { $match: { complete: false } }, 7 { $count: "incomplete" }, 8 ]; 9 const result = await todos.aggregate(pipeline).toArray(); 10 const incompleteCount = result[0]?.incomplete || 0; 11 return new Response(JSON.stringify({ incompleteCount }), { 12 headers: { "Content-Type": "application/json" }, 13 }); 14 } catch (error) { 15 return new Response(JSON.stringify({ error: error.message }), { 16 status: 500, 17 headers: { "Content-Type": "application/json" }, 18 }); 19 } 20 } 21 22 export { addTodo, deleteTodo, getIncompleteTodos, getTodo, getTodos, updateTodo };
Essa função executa um pipeline de agregação na collection Todos para contar o número de Todos incompletos. Ele usa o estágio
$match
para filtrar todos em que complete
é false
, e o estágio$count
para contar o número de documentos que correspondem a esses critérios. O resultado é retornado como uma resposta JSON com um código de status 200 . Se ocorrer um erro durante o processo, ele o capturará e retornará uma resposta JSON com a mensagem de erro e um código de status 500 .Vamos adicionar isso à nossa função
handler
no arquivoserver.ts
.1 import { addTodo, deleteTodo, getTodo, getTodos, updateTodo, getIncompleteTodos } from "./controllers/todoController.ts"; 2 // ... existing code 3 4 async function handler(req: Request): Promise<Response> { 5 // ... existing code 6 else if (req.method === "GET" && path === "/api/todos/incomplete/count") { 7 return await getIncompleteTodos(); 8 } 9 // ... existing code 10 }
Observe que esta rota é posicionada acima da rota
getTodo
na funçãohandler
. Isso ocorre porque a rotagetTodo
usa um caminho que corresponde ao início da rotagetIncompleteTodos
. Se colocarmos getIncompleteTodos
abaixo getTodo
na função handler
, o servidor não conseguiria distinguir entre as duas rotas e sempre corresponderia à rotagetTodo
.Como alternativa, poderíamos usar uma expressão regular para corresponder ao caminho e colocar
getIncompleteTodos
abaixo getTodo
na função handler
. Isso nos permitiria usar um caminho mais específico para nossa rota de agregação .Agora, podemos testar nossa função
getIncompleteTodos
. Usaremos o comandocurl
para enviar uma solicitação GET para nossa rota de agregação .1 curl http://localhost:3000/api/todos/incomplete/count
Você deve ver uma resposta com um código de status 200 e um objeto JSON contendo a contagem de Todos incompletos.
Neste tutorial, criamos um servidor Deno que usa o driver MongoDB para criar, ler, atualizar e excluir documentos (CRUD) em nosso banco de banco de dados MongoDB . Adicionamos uma rota de desconto para demonstrar o uso de um pipeline de agregação com o driver MongoDB . E agora?
O código completo pode ser encontrado no repositórioComeçando com Deno e MongoDB . Você deve ser capaz de usar isso como ponto de partida para seu próximo projeto e modificá-lo para atender às suas necessidades.