Criando API com DENO e MongoBD
Tags: deno, typescript, api, mongodb
Published: 2020-06-18 15:06:21



Olá, uma das coisas divertidas que podemos começas a brincar com o Deno é criando uma API e integrar ela com o MongoDB.

Aqui vai o passo a passo de como faremos isso, mas para isso vamos usar:

[abc@v1.0.0-rc2](https://deno.land/x/abc) - For router(Similar Express)
[dotenvc@v1.0.0-rc2](https://deno.land/x/dotenv) - Variables globals in root file
[mongo@v0.6.0](https://deno.land/x/mongo) - MongoDB connection
[typescript@3.9](https://www.typescriptlang.org/) -  Language typed

Agora que temos nossas libs vamos para a estrutura da nossa API.
Eu pensei em algo como:

``` javascript
 - controllers
 -- Users
 - database
 - model
 - utils
 .env
 server.ts
```

## SHOW ME THE CODE

Vamos começar configurando nosso server, crie um arquivo chamado server.ts na raiz do projeto e nesse arquivo a idéia é ser o mais simples possível e passar a abertura do servidor e os métodos rest, vamos analizar o código.

``` javascript

import { Application } from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";

const app = new Application();

app
.get("/allusers", (c) => {
    return "Hello!";
  .start({ port: 4000 });

console.log(`server listening on http://localhost:4000`);

```

Basicamente temos uma rota com retorno "Hello!" sem segredo.

Para executar, rode no seu CLI o comando:

`deno run --allow-net server.ts`
Se tudo der certo vai receber mensagem "server listening on http://localhost:4000" no console e então só bater nessa URL com o [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/download/), [Postwoman](https://postwoman.io/pt-br) ou qualquer outra aplicação do tipo =)

## DB Connection

conexão com o banco de dados e pra isso vamos usar o [Mongodb.com](https://www.mongodb.com/) ele nos da a possibilidade de criar um banco gratuito com até 500mb de capacidade.

Criado o banco, vamos para o código e aqui vamo usar o TypeeScript, mas se não quiser tipagem só usar o .js e não colocar as tipagens, bem simples.

Dentro da pasta `database` crie um arquivo chamado `connections.ts` vamos concentrar nossa conexão aqui.
Feito isso vamos fazer o import das libs, abrir conexão e passar nossas variáveis de ambiente que serão `DATABASE_NAME` e `DATABASE_HOST`

_OBS. Estou utilizando a versão 0.6.0 porque a mais recente tive instabilidade para abrir conexão_

``` javascript
import "https://deno.land/x/dotenv/load.ts";
import { init, MongoClient } from "https://deno.land/x/mongo@v0.6.0/mod.ts";

// @ts-ignore
await init();

const dbName = Deno.env.get('DATABASE_NAME') || "deno";
const dbURI = Deno.env.get('DATABASE_HOST') || "mongodb://localhost:27017";

```

Aqui eu preferi trabalhar com conceito de `class` pelo motivo de não ter que instanciar varias vezes em memória os valores das credenciais do banco e assim deixamos global.

```javascript 

class DataBase {
  public client: MongoClient;
  constructor(
    public dbName: string,
    public url: string
  ) {
    this.dbName = dbName;
    this.url = url;
    this.client = {} as MongoClient;;
  }

  connect() {
    const client = new MongoClient();
    client.connectWithUri(this.url);
    this.client = client;
  }

```
Basicamente no código acima eu deixo exposto no método `MongoClient` e no construtor eu passo o que preciso popular que no caso vão ser o `dbName` e a `url (connectionString)`

Feito isso criamos um método chamado `connect()`que será responsável pela abertura da conexão do mongo (como o próprio nome sugere)

Por fim terminamos nossa classe passando um método para encontrar a database que vamos trabalhar:

``` javascript
  get findDatabase() {
    return this.client.database(this.dbName)
  }
}
```
Onde basicamente vai nos retornar o database que pedimos

E para finalizar vamos fazer nossa chamada de classe e expor ela como default para nossos arquivos externos conseguirem enxergar 

```javascript 

const connectionDatabase = new DataBase(dbName, dbURI);
connectionDatabase.connect()

export default connectionDatabase;

```
Feito isso você já poderá se conectar ao Mongo sem problemas.

## Models

Antes de criar nossos métodos vamos preparar nossa tipagem de dados, mas para a lib do Mongo precisamos tipar e passar o `$oid` e alguns dos métodos da controller.

``` javascript

export default interface User  {
    _id: {
        $oid: string;
      };
      name: string;
      middleName: string;
      profession: string;
}

```

## Handle Error

A ideia do Handler vai ser de normalizar o padrão de mensagens de erro na nossa aplicação 


``` javascript

import { MiddlewareFunc } from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";

// Você já sabe como uma class funciona o/
export class ErrorHandler extends Error {
  status: number;
  constructor(msg: string, status: number) {
    super(msg); // Super é responsável por dizer que essa é uma classe pai no nível l da hierarquia 
    this.status = status;
  }
}

export const ErrorMiddleware: MiddlewareFunc = (next) =>
  async (data) => {
    try {
// Se deu bom usamos o next() pra passar a info 
      await next(data);
    } catch (err) {
// Se deu ruim passamos no request só  o que precisamos ver, mensagem e status code
      const error = err as ErrorHandler;
      data.response.status = error.status || 500;
      data.response.body = error.message;
    }
  };
```

## Controllers

Nas controllers será onde colocaremos de fato a parte lógica da aplicação e aqui teremos arquivos para cada um dos métodos, achei melhor deixa-los separados para ficar fácil o manuseio, mas você  pode deixar em um único arquivo os 4 métodos `GET`,`POST`,`PUT` e `DELETE`.

Vamos lá:

### POST

Criaremos um arquivo chamado `createUser.ts` dentro da pasta Users na controller vou explicar dentro do arquivo o que cada coisa faz:


``` javascript

// esses imports são para tipagem dos métodos de contexto e de trafego de informação pelas rotas da lib ABC
import {
  HandlerFunc,
  Context,
} from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";

// Importamos a nossa connectionDatabase para abriemos conexão com o banco de dados
import connectionDatabase from "../../database/connection.ts";

// Importamos o handler de erros que criamos para termos um padrão na saída deles
import { ErrorHandler } from "../../utils/handleError.ts";

// Abriremos nossa conexão passando a nossa collection previamente criada
const database = connectionDatabase.findDatabase;
const user = database.collection("users");

export const createUser: HandlerFunc = async (data: Context) => {
// Vamos adotar a boa prática de passar um try...catch
  try {

// Nesse primeiro if verificamos se nós estamos passando no Headers o content-type como application/json, se não estiver ele dispara um erro
    if (data.request.headers.get("content-type") !== "application/json") {
      throw new ErrorHandler("Body invalido", 422);
    }

// Para pegar o que passamos no corpo da aplicação, vamos escrever uma request usando await de tudo que temos no data.body (data é o parâmetro dessa nossa função)
    const body = await (data.body());

// Esse nosso if usamos o Object.keys() para verificar se foi passado objetos no corpo da requisição
    if (!Object.keys(body).length) {
      throw new ErrorHandler("O body não pode estar vazio!!", 400);
    }

// Feito essas validações vou desestruturar o que preciso inserir no banco
    const { name, profession, middleName } = body;
// Depois usamos o método insertOne({}) do MongoDB para persistir esses dados
    await user.insertOne({
      name,
      middleName,
      profession,
    });
// Terminamos aqui com o return passando uma mensagem que deu bom e o statusCode 200 o/
    return data.json('Usuário cadastrado com sucesso', 201);
  } catch (error) {

// Se der ruim nosso ErrorHandler será encarregado de nos avisar 
    throw new ErrorHandler(error.message, error.status || 500);
  }
};

```

### GET

Criaremos um arquivo chamado `getAllUsers.ts` dentro da pasta Users na controller vou explicar dentro do arquivo o que cada coisa faz.

O **GET** é o mais simples, pois só usaremos o `find()` do mongo e retornaremos a lista de tudo que temos cadastrado

``` javascript

import {
    HandlerFunc,
    Context,
} from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";

import connectionDatabase from "../../database/connection.ts";
import { ErrorHandler } from "../../utils/handleError.ts";

// Importamos a nossa tipagem de dados de usuário 
import Users from '../../model/users.ts'

const database = connectionDatabase.findDatabase;
const user = database.collection("users");

export const getAllUsers: HandlerFunc = async (data: Context) => {
    try {
// Verificamos se existe o user com o método find() do Mongo
        const existUser: Users[] = await user.find();
// Se existir retornamos com o map(), se não retornamos array vazio
        if (existUser) {
            const list = existUser.length
                ? existUser.map((item: any) => {
                    const { _id: { $oid }, name, middleName, profession } = item;

                    console.log('item :>> ', item);
                    return { id: $oid, name, middleName, profession };
                }) : [];

            return data.json(list, 200);
        }
    } catch (error) {
        throw new ErrorHandler(error.message, error.status || 500);
    }
};

```

### GET ONE
Esse método será  responsável por trazer apenas um registro que será passado por queryString

``` javascript

import {
  HandlerFunc,
  Context,
} from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";

import connectionDatabase from "../../database/connection.ts";
import { ErrorHandler } from "../../utils/handleError.ts";

const database = connectionDatabase.findDatabase;
const user = database.collection("users");

export const getUser: HandlerFunc = async (data: Context) => {
  try {

// Vamos capturar o que será passado como parâmetro e usando o as do typescript para tipar o valor como string
    const { id } = data.params as { id: string };

// Usaremos o findOne() do mongo pra trazer uma bolleana de existUser ou não o valor
    const existUser = await user.findOne({ _id: { "$oid": id } });

    if (existUser) {

// Existindo, vamos desestruturar o resultado e retornar no data.json() junto com o status code
      const { _id: { $oid }, name, middleName, profession } = existUser;
      return data.json({ id: $oid, name, middleName, profession }, 200);
    }
// Caso não exista, receberemos a mensagem abaixo com o status 404
    throw new ErrorHandler("Usuário não encontrado", 404);
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};


```

### PUT (UPDATE)

Criaremos um arquivo chamado `updateUser.ts` dentro da pasta Users na controller vou explicar dentro do arquivo o que cada coisa faz.

Esse método tem mais validações por questão de segurança , se estaremos ou não passando as informações correta ou não, vamos analizar:

``` javascript

import {
  HandlerFunc,
  Context,
} from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";

import connectionDatabase from "../../database/connection.ts";
import { ErrorHandler } from "../../utils/handleError.ts";

const database = connectionDatabase.findDatabase;
const user = database.collection("users");

export const updateUser: HandlerFunc = async (data: Context) => {
  try {
    const { id } = data.params as { id: string };

    if (data.request.headers.get("content-type") !== "application/json") {
      throw new ErrorHandler("Invalid body", 422);
    }

// Capturamos o valor do corpo da aplicação e tipamos os dados passando o sinal de ? para sinalizar que é um campo opcional
    const body = await (data.body()) as {
      name?: string;
      middleName?: string;
      profession?: string;
    };

    if (!Object.keys(body).length) {
      throw new ErrorHandler("O body não pode estar vazio!", 400);
    }

// Aqui vamos usar o findOne() novamente e verificar se existe
    const existUser = await user.findOne({ _id: { "$oid": id } });

    if (existUser) {
      const { matchedCount } = await user.updateOne(
        { _id: { "$oid": id } },
        { $set: body },
      );

// Esse matchedCount que recebemos no resultado do findOne() é o responsável por nos dar a informação de se foi encontrado ou não no caso será 1 dado e finalizamos com return data.string()
      if (matchedCount) {
        return data.string("O usuário foi atualizado com sucesso!", 204);
      }

// Caso não der certo mandaremos essa mensagem para a requisição 
      return data.string("Não foi possível atualizar esse usuário");
    }
    throw new ErrorHandler("Usuário não encontrado", 404);
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};
```

### DELETE

Criaremos um arquivo chamado `deleteUser.ts` dentro da pasta Users na controller vou explicar dentro do arquivo o que cada coisa faz.

O nosso delete é bem simples com a carga das coisas que aprendemos nos anteriores, veja:

``` javascript 

import {
  HandlerFunc,
  Context,
} from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";

import connectionDatabase from "../../database/connection.ts";
import { ErrorHandler } from "../../utils/handleError.ts";

const database = connectionDatabase.findDatabase;
const user = database.collection("users");

export const deleteUser: HandlerFunc = async (data: Context) => {
  try {
    const { id } = data.params as { id: string };

    const existUser = await user.findOne({ _id: { "$oid": id } });

    if (existUser) {

// Usaremos o deleteOne() do banco para realizar a operação e ele vai retornar o valor de dado deletado, no caso 1
      const deleteCount = await user.deleteOne({ _id: { "$oid": id } });
      if (deleteCount) {

// Após receber o valor 1 vamos responder a requisição com a mensagem  abaixo
        return data.string("Usuário foi deletado!", 204);
      }
      throw new ErrorHandler("Não foi possivel excuir esse usuário", 400);
    }

    throw new ErrorHandler("Usuário não encontrado", 404);
  } catch (error) {
    throw new ErrorHandler(error.message, error.status || 500);
  }
};

```

Por fim vamos padronizar as exportações criando um arquivo index.ts dentro da pasta Users com os seguinte código:

```javascript

export { getAllUsers } from './getAllUsers.ts';
export { createUser } from './createUser.ts';
export { getUser } from './getOneUser.ts';
export { updateUser } from './updateUser.ts';
export { deleteUser } from './deleteUser.ts'; 
```

## .ENV 

O .ENV é onde vamos colocar as variáveis globais da aplicação 
Crie um arquivo chamado .env na raiz do projeto (mesmo nivel do arquivo server.ts) com a seguinte informação:

``` javascript 

DATABASE_NAME=deno
DATABASE_HOST=<_sua_url_do_mongo_>

```

Foi muita coisa agora, mas agora você sabe criar uma API para brincar nos seus projetos ou até mesmo aplicar com NodeJS

Feito tudo isso podemos executar nosso projeto com o comando

`deno run --allow-write --allow-read --allow-plugin --allow-net --allow-env --unstable ./server.ts
`

O código dessa API se encontra nesse [Repositório](https://github.com/DiegoTeles/deno-api)

Por enquanto é isso e nos vemos em breve, dúvidas ou sugestão deixem nos comentários ou nos procure nas redes Sociais!

Acompanhe nossos canais de conteúdo:
- [Youtube](https://www.youtube.com/channel/UCCnvFA6ZShN67V54UsytH0g)
- [Pagina Facebook](https://www.facebook.com/DenoLandBR)
- [Grupo Facebook](https://www.facebook.com/groups/denobr)
- [Discord](https://discord.gg/Z35yKzN)