Construindo uma API Simples com Deno
Deno é uma plataforma JavaScript avançada estabelecida com base no V8, que serve como o principal mecanismo para a execução do Google Chrome. Desenvolvido pelo inventor do Node.js, o Deno foi projetado para lidar com certas limitações e apreensões de segurança associadas ao Node.js.
Deno, apesar de ser uma tecnologia introduzida recentemente, conquistou um apelo considerável devido à sua reputação de segurança e design contemporâneo. O fato de priorizar a segurança, incorporar capacidades de programação de ponta e oferecer recursos de desenvolvimento amigáveis torna-o uma opção atraente. Ele permite que os desenvolvedores construam aplicativos do lado do cliente e do lado do servidor, incluindo utilitários de linha de comando e APIs diretas criadas com JavaScript ou TypeScript.
Instalando o Deno
Para utilizar o Deno, você deve primeiro obter e configurar o software. O processo de instalação do Deno difere com base no sistema operacional específico que está sendo usado.
Para instalar o Deno em um sistema operacional Mac ou Linux, pode-se executar o seguinte comando:
curl -fsSL https://deno.land/x/install/install.sh | sh
Para instalar o Deno em um sistema operacional Windows utilizando o PowerShell, pode-se executar o seguinte comando:
irm https://deno.land/install.ps1 | iex
Para verificar o sucesso de sua instalação, você pode executar o comando fornecido abaixo:
deno --version
A instrução acima mencionada destina-se a exibir a instalação atual do Deno no console do sistema por meio da impressão de suas informações de versão.
Se você usa o VS Code como um IDE, pode baixar a extensão Deno’s VS Code para adicionar o IntelliSense, aprimorando sua produtividade e experiência de desenvolvimento ao trabalhando com projetos Deno.
Após a instalação perfeita da extensão, por favor, crie uma pasta.vscode dentro do diretório raiz do projeto e, posteriormente, gere um arquivo settings.json dentro dessa pasta.
Para habilitar o IntelliSense no Visual Studio Code, você pode seguir estas etapas:1. Abra a pasta do seu projeto no VSCode.2. Clique em “Arquivo” > “Preferências” (ou pressione Ctrl
+ P
).3. Na barra de pesquisa no canto superior direito da janela, digite “configurações”.4. Selecione “Configurações” no menu suspenso que aparece.5. Role para baixo até encontrar a configuração “Python: Specify IntelliSense Host Path” e clique nela.6. Insira o caminho para o interpretador Python para seu ambiente virtual ou instalação global. Por exemplo, se você estiver usando um ambiente virtual chamado “myenv”, digite C:\path\to\myenv\Scripts\python.exe
. Se você tiver uma instalação global
{
"deno.enable": true,
"deno.unstable": true,
}
Conectando a um banco de dados
Para acompanhar este tutorial, é necessário utilizar o MongoDB como banco de dados escolhido para armazenar os dados obtidos por meio de uma API.
Para estabelecer uma conexão entre uma aplicação Deno e um banco de dados MongoDB, é necessário criar um arquivo “db.js” dentro do diretório raiz do projeto e inserir nele o seguinte trecho de código:
// db.js
import { MongoClient } from "https://deno.land/x/[email protected]/mod.ts";
const client = new MongoClient();
try {
await client.connect("mongodb://localhost:27017/todo");
console.log("Connected to database");
} catch (err) {
console.log("Error connecting to database", err);
}
const db = client.database("todo");
export default db;
O Deno se distingue do Node.js por apresentar uma solução integrada de gerenciamento de pacotes para obter e supervisionar dependências por meio de referências diretas de URL, enquanto o Node.js depende de gerenciadores de pacotes externos, como o Node Package Manager (npm) ou yarn.
Por exemplo, o bloco de código acima importa MongoClient da URL https://deno.land/x/[email protected]/mod.ts , que leva ao pacote.
Utilizando o driver Deno MongoDB, que é obtido através da importação da classe MongoClient, é estabelecida uma interface entre sua aplicação e uma instância local do MongoDB.
A utilização de um arquivo de configuração de variável de ambiente seguro, como “.env”, geralmente é considerada uma prática mais segura do que armazenar informações confidenciais, como credenciais de banco de dados, diretamente nos arquivos de código-fonte, que podem ser vulneráveis a acesso não autorizado ou exposição acidental devido ao manuseio inadequado.
Criando um modelo de banco de dados
Utilizar um modelo de banco de dados ao fazer interface com um repositório MongoDB pode evitar a geração de código desorganizado e difícil de manter.
Para evitar o problema em questão, uma solução é estabelecer um TodoModel
dentro da base da hierarquia do projeto. Isso pode ser obtido introduzindo um novo arquivo chamado TodoModel.ts
, que deve ser colocado bem no início da estrutura de diretórios do projeto. Depois de estabelecido, você pode adicionar o trecho de código fornecido ao arquivo recém-criado, organizando assim seus dados de maneira ordenada.
import db from "./db.ts";
interface Todo {
title: string;
description: string;
completed?: boolean;
}
const Todo = db.collection<Todo>("todos");
export default Todo;
O trecho de código fornecido descreve uma interface conhecida como “Todo”, que delineia a estrutura organizacional de um único item de tarefa pendente. Ao utilizar a interface Todo, o sistema constrói uma montagem Todo por meio da invocação do método de coleta oferecido pela instância preexistente do MongoDB.
Criando um servidor com Oak
O Oak serve como uma solução de middleware para o servidor HTTP integrado do Deno e se inspira no Koa, uma alternativa à estrutura Express.js amplamente usada.
Para estabelecer um servidor utilizando a estrutura Oak, comece gerando um arquivo “main.ts” no diretório principal do seu projeto e incorporando o trecho de código subsequente nele.
// main.ts
import { Application } from "https://deno.land/x/oak/mod.ts";
import router from "./router.ts";
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
console.log("Server running on port 8000");
O segmento de código acima mencionado importa a classe Application
do URL Oak
especificado, criando subseqüentemente uma instância da classe Application
chamada app
. Esta instância foi projetada para atender ao tráfego de rede de entrada que chega na porta número 8000.
A utilização de app.use(router.routes())
dentro da aplicação Oak serve para registrar as rotas especificadas pelo roteador como middleware. Consequentemente, qualquer pedido de entrada recebido pela aplicação é submetido a comparação com as rotas pré-definidas, sendo que se existir correspondência entre elas, as respetivas funções de tratamento são executadas em conformidade.
A aplicação de app.use(router.allowedMethods())
serve para regular e gerenciar métodos HTTP que não foram explicitamente delineados dentro do sistema de roteamento. Nos casos em que um método indefinido ou não reconhecido é enviado, como uma solicitação PUT não documentada, esse mecanismo de middleware específico responde prontamente com uma notificação apropriada, incluindo, entre outros, um código de status 405 (Método não permitido).
Implementando a Funcionalidade CRUD
Este tutorial apresentará um programa aplicativo de tarefas simples que possui a capacidade de executar operações CRUD (Criar, Ler, Atualizar e Excluir) em seus dados.
Incorporando as instruções fornecidas, criei um arquivo router.ts
no diretório raiz do meu projeto e incluí o bloco de código especificado. O conteúdo atualizado é o seguinte: typescriptimport { NextApiRequest, NextApiResponse } from’next’;import { default as nextRouter } from’next/router’;import { parse } from’url’;import { getConfig } from’./config’;const router=nextRouter();//Redirecione para HTTPS se ainda não estiver usando itif (!process.env.NEXT_PUBLIC_OFFLINE) {router.redirectToHttps(‘https://’+ process.env.NEXT_PUBLIC_HOST);}exportar roteador padrão;
import { Router } from "https://deno.land/x/oak/mod.ts";
import Todo from "./todoModel.ts";
import { ObjectId } from "https://deno.land/x/[email protected]/mod.ts";
const router = new Router(); // Create Router
O segmento de código acima mencionado importa e instancia uma instância do roteador Oak. Utilizando essa instância, pode-se estabelecer manipuladores de rota para diferentes métodos HTTP invocando os nomes dos métodos correspondentes, como “get”, “post”, “put” ou “delete”.
Aqui está uma instância em que se pode ilustrar uma maneira pela qual um manipulador de rota GET pode ser implementado para recuperar todos os itens dentro de uma coleção Todo designada.
router
.get("/api/todos", (ctx: RouterContext<"/api/todos">) => {
ctx.response.body = Todo.find();
})
Para transmitir um objeto de resposta utilizando Deno, é necessário associar o corpo da resposta ao RouterContext e mapeá-lo no objeto de resposta. Este princípio também é válido para definir o código de status apropriado.
Para incorporar manipuladores de rota adicionais, pode-se anexá-los sucessivamente a um manipulador de rota preexistente.
Igual a:
.get("/api/todo/:id", async (ctx: RouterContext<"/api/todo/:id">) => {
try {
const todo = await Todo.findOne({ _id: new ObjectId(ctx.params.id) });
if (!todo) {
ctx.response.status = 404;
ctx.response.body = {
msg: "Todo not found",
};
return;
}
ctx.response.body = todo;
} catch (error) {
ctx.response.status = 500;
ctx.response.body = {
msg: "Error getting todo",
error,
};
}
})
O bloco de código fornecido descreve um manipulador de rota GET que recupera e retorna um item de tarefa específico cujo identificador está incluído nos parâmetros de URL.
Em seguida, vamos criar um manipulador de rotas CREATE
que adicionará novas entradas ao nosso banco de dados. O objetivo dessa função é aceitar solicitações HTTP POST recebidas e armazená-las nas coleções apropriadas no MongoDB.
.post("/api/todo/new", async (ctx: RouterContext<"/api/todo/new">) => {
const body = ctx.request.body();
const todo = await body.value;
if (!todo) {
ctx.response.status = 400;
ctx.response.body = { msg: "Invalid data. Please provide a valid todo." };
return;
}
const { title, description } = todo;
if (!(title && description)) {
ctx.response.status = 400;
ctx.response.body = {
msg: "Title or description missing. Please provide a valid todo.",
};
return;
}
try {
await Todo.insertOne({
title: todo.title,
description: todo.description,
completed: false,
});
ctx.response.status = 201;
ctx.response.body = {
msg: "Todo added successfully",
};
} catch (error) {
ctx.response.status = 500;
ctx.response.body = {
msg: "Error adding todo",
error,
};
}
})
Claro! Aqui está um exemplo de como implementar um manipulador de rota PUT
para atualizar um item de tarefa usando seu ID e os dados fornecidos no corpo da solicitação:javascriptapp.put(’/todos/:id’, (req, res)=> { const { id }=req.params;//Extrai o id da tarefa a partir dos parâmetros da URL//Encontra a tarefa com o id correspondente constfoundTodo=todos.find((todo)=> todo.id===id);if ( !foundTodo) {return res.status(404).json({ message:‘Todo not found.’});}//Atualize o todo com os novos dados enviados na requisição bodyconst updatedData=req.
.put("/api/todo/:id", async (ctx: RouterContext<"/api/todo/:id">) => {
try {
const body = ctx.request.body();
const todo = await body.value;
await Todo.updateOne(
{ _id: new ObjectId(ctx.params.id) },
{ $set: { title: todo.title, description: todo.description } }
);
ctx.response.status = 200;
ctx.response.body = {
msg: "Todo updated successfully",
};
} catch (error) {
console.log(error);
ctx.response.status = 500;
ctx.response.body = {
msg: "Error updating todo",
error: error.message,
};
}
})
Para remover com eficiência um item Todo de nosso banco de dados MongoDB, podemos implementar um manipulador de rota DELETE. Isso envolverá a especificação das ações necessárias a serem executadas quando o comando de exclusão for executado no endpoint da API. O código para esta operação seria mais ou menos assim:javascriptapp.delete(’/todos/:id’, function(req, res) {var id=req.params.id;//Obtém o ID do todo a ser excluído//Encontre e exclua o documento correspondente na coleção TodosTodo.remove({_id: new ObjectId(id)}, function() {if (Todo.findOne({_id: new ObjectId(id)})===null) {res.send(‘Deleted’);} else {res.status
.delete("/api/todo/:id", async (ctx: RouterContext<"/api/todo/:id">) => {
await Todo.deleteOne({ _id: new ObjectId(ctx.params.id) });
ctx.response.status = 200;
ctx.response.body = {
msg: "Todo deleted successfully",
};
});
Você pode iniciar um aplicativo Deno executando o seguinte comando:
deno run --allow-net --allow-read --allow-env --watch main.ts
Os scripts Deno, por padrão, não recebem permissão para acessar recursos além de seu próprio escopo, incluindo a rede e o sistema de arquivos. Para iniciar um aplicativo, é necessário incorporar sinalizadores específicos que autorizem o Deno com as permissões necessárias.
As opções de configuração mencionadas permitem funcionalidades específicas dentro do ambiente Deno, incluindo permissão para conectividade de rede (–allow-net), acesso ao sistema de arquivos para leitura de arquivos (–allow-read) e acesso a variáveis ambientais por meio da opção –allow-env. Além disso, o sinalizador –watch inicia o aplicativo Deno em um modo de observação.
Migrando de Node.js para Deno
A migração do Node.js para o Deno pode resultar em inúmeras vantagens, como recursos de segurança aprimorados, maior eficiência do desenvolvedor e gerenciamento simplificado de dependências ao construir serviços da Web RESTful. Aproveitando o ambiente de tempo de execução protegido do Deno, a compatibilidade interna do TypeScript e o manuseio direto de dependências, os desenvolvedores podem produzir facilmente interfaces RESTful resilientes e cheias de recursos.
Embora o Deno apresente uma alternativa promissora ao JavaScript, seu ecossistema nascente deve ser levado em consideração ao considerar a migração. É essencial avaliar minuciosamente as vantagens e desvantagens antes de tomar uma decisão.