Construindo uma API com Deno: Da instalação ao Deploy
Tags: deno, typescript, postgres, docker
Published: 2020-06-18 21:47:06

---
title: Construindo uma API com Deno: Da instalação ao Deploy
published: true
description: 
tags: deno, typescript, postgres, docker

---

Criei minha primeira API com **Deno** recentemente e decidi compartilhar tudo que aprendi sobre ele neste artigo. 

Esta API é parte de um projeto open-source que estou desenvolvendo. Meu objetivo com ele é implementar um "e-commerce" seguindo a arquitetura de microservices, com a stack JS, e aprender outras tecnologias no processo. Todo o código que usei está no meu [GitHub](https://github.com/guisfits/easy-commerce/tree/master/services/rating).

## O que é o Deno

Deno ~~como todos já estão cansados de ouvir~~ é o novo runtime JS/TS, feito pelo mesmo criador do Node, Ryan Dahl. Ele possui fortes restrições de segurança, é construído com a linguagem Rust, em cima da V8 do Google e tem ~~uma meia~~ um __dino__ssauro de mascote.

![logo](https://deno.land/images/deno_logo_4.gif)

## Instalação

Vou sair um pouco do convencional e mostrar uma alternativa pra instalar o Deno - ou qualquer outro runtime que queira. 

[asdf](https://github.com/asdf-vm/asdf) é um gerenciador de versão universal. Após instalá-lo com o [guia oficial](https://asdf-vm.com/#/core-manage-asdf-vm) rode os seguintes comandos:

```shell
$ asdf plugin-add deno https://github.com/asdf-community/asdf-deno.git
$ asdf install deno 1.0.5
$ asdf global deno 1.0.5
```
> Como estou usando a versão 1.0.5 nos exemplos, deixei fixo aqui, mas você pode repetir os últimos dois comandos com "latest" e usar a versão mais atual depois.

Para conferir se deu tudo certo é só rodar `$ deno --version`.

## Setup Environment 

### VS Code extension

VS Code se tornou o ambiente padrão para muitas linguagens, acredito que JS/TS seja a maior delas. Já temos uma [extensão](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno) criada pela equipe do Deno que nos dará um suporte melhor durante o desenvolvimento. 

### VS Code settings

Após instalar a extensão do Deno precisamos _setar_ a versão do Typescript que o VS Code usa para a última versão. Se ainda não instalou - e considerando que você já tenha Node instalado na sua máquina - rode `$ npm i -g typescript`. Agora precisamos obter o caminho para os executáveis. No terminal rode `$ npm list -g typescript`, ele irá nos dizer o caminho até a pasta raiz onde o TS foi instalado, navegue até lá e vá para a pasta "lib", rode `$ pwd`, copie todo o caminho e cole na propriedade `"typescript.tsdk"` no `settings.json` global do VS Code ou do workspace. 

No meu caso estou usando o settings do workspace com algumas outras configurações, recomendo usar as mesmas.

```javascript
{
  "typescript.tsdk": "~/.asdf/installs/nodejs/12.18.0/.npm/lib/node_modules/typescript/lib",
  "[typescript]": {
    "editor.defaultFormatter": "vscode.typescript-language-features",
  },
  "deno.enable": true,
  "deno.unstable": true,
}
```

### Variáveis de ambiente

O Deno usa uma pasta global na sua máquina para salvar algumas coisas. Caso não tenha sido criada ainda rode `$ mkdir -p ~/.deno/bin`. Este será o caminho onde ficará alguns executáveis que instalaremos com o Deno. Agora precisamos que nosso terminal olhe para esta pasta quando for procurar pelos binários. Abra o arquivo de configuração do seu terminal (`.bashrc`, `.zshrc`, ...) e cole isso no início do arquivo.

```shell
export DENO_DIR=$HOME/.deno
export PATH=$PATH:$DENO_DIR/bin
``` 

> Recomendo que feche e abra uma nova instância do terminal para que reflita as alterações.

### Typescript

O projeto será full typescript. Você pode usar JS puro se preferir, mas prefiro TS, gosto muito do _tooling_ que ele me dá e ele me ajuda a evitar erros bobos. Além disso, TS é a linguagem recomendada para desenvolver com Deno. 

Apesar de não ser necessário geralmente, neste projeto iremos utilizar _decorators_ e para isso precisamos dizer ao TS para aceitar essa sintaxe. Para isso temos que criar o arquivo `tsconfig.json` dentro da pasta `src`.

```javascript
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}
```

## Estrutura do projeto

Esta API será responsável por cadastrar as avaliações dos usuários para os produtos que eles comprarem. 

Só teremos duas rotas:
- **POST** /ratings
- **GET** /ratings   

A estrutura final do projeto será esta:
```
.
├── .env
├── .env.example
├── .vscode
│   ├── launch.json
│   └── settings.json
├── Dockerfile
├── denon.json
├── docker-compose.yml
└── src
    ├── controllers
    │   └── rating.controller.ts
    ├── database
    │   ├── connection.ts
    │   ├── entities
    │   │   ├── buyer.entity.ts
    │   │   ├── product.entity.ts
    │   │   └── rating.entity.ts
    │   └── repositories
    │       ├── buyer.repository.ts
    │       ├── product.repository.ts
    │       └── rating.repository.ts
    ├── deps.ts
    ├── env.ts
    ├── routes.ts
    ├── server.ts
    └── tsconfig.json
``` 

## Framework Web

O Deno tem uma ótima [standard library](https://deno.land/std), porém não queremos trabalhar com a classe de http diretamente, pois teríamos que fazer muita coisa na mão, é por isso que usamos frameworks. No Node o comum é o _express_,  no Deno o framework mais estável e com mais uso até o momento é o [oak](https://deno.land/x/oak) - ele é baseado no _koa_ ~~pessoal do Deno adoram fazer anagramas~~. 

A primeira coisa que precisamos criar é o arquivo `server.ts`, que será o arquivo de entrada para a aplicação. 

```javascript
import { Application } from 'https://deno.land/x/oak/mod.ts'

const app = new Application();
const port = 8001

console.log(`Running on port ${port}`);
await app.listen({ port: port});
```

Agora podemos rodar usando `$ deno run --allow-net server.ts`.

### Imports

A primeira coisa que vemos de diferente em comparação com o Node é que no Deno usamos _ES Modules_ ao invés do _CommonJS_ e estamos dando `import` de uma URL direto no nosso código. Isso pode parecer meio estranho - Deno se inspirou na linguagem **Go** neste aspecto - porém no browser isso sempre foi comum. É como a tag `script` do **HTML** funciona: `<script src="URL">`

### node_modules / package.json

- Então se estamos importando as dependências via URL, toda vez que quisermos rodar a aplicação teremos de ter acesso à internet?
- Não!

Assim que usamos uma nova dependência o Deno baixa o código desta URL para um cache em uma pasta global na sua máquina - o caminho padrão é `~/.deno/deps/https`. Na próxima vez que rodarmos esta aplicação, ou outra que tenha a mesma dependência, o Deno irá obter a dependência desta pasta. 

Dito isso, não precisamos mais de uma pasta na raiz do nosso projeto como o `node_modules`. Se, por exemplo, temos duas APIs com Deno que usam a mesma versão do _oak_, não teremos ele duplicado duas vezes na nossa máquina. 

Como **não instalamos as dependências**, só fazemos referência, também não precisamos mais do arquivo `package.json`. A única coisa que precisamos é do próprio arquivo JS/TS.

### deps.ts

O ponto ruim de fazer referência via URL é que, dependendo do tamanho do projeto, você ficará com URLs espalhadas por toda a aplicação. E caso queira atualizar uma dependência para outra versão, remover ou até mesmo saber quais libs estão sendo usadas, você não terá isso em um ponto centralizado. 

Pensando nisso podemos centralizar essas referências em um arquivo e importar o que precisamos deste arquivo.

Dentro de `src` crie o arquivo `deps.ts`

```javascript
export { 
  Application,
  Router,
  Context
} from "https://deno.land/x/oak/mod.ts"
```

### Top level await

Outra coisa interessante que estamos usando é `await` sem uma função `async`. Deno tem esta _feature_ [graças ao V8](https://v8.dev/features/top-level-await), onde podemos usar `await` direto na raiz do arquivo.

## Denon

Assim como no Node usamos o _Nodemon_ para que a aplicação reinicie toda vez que um arquivo é alterado, no Deno usamos o _denon_. Após instalá-lo seguindo o [readme](https://deno.land/x/denon), vamos criar um arquivo chamado `denon.json` na raiz do projeto. Ele é muito útil, podemos fazer varias configurações de como nossa aplicação deve rodar.

```javascript
{
  "$schema": "https://deno.land/x/denon/schema.json",
  "allow": [
    "env",
    "net"
  ],
  "tsconfig": "src/tsconfig.json",
  "unstable": true,
  "scripts": {
    "start": {
      "cmd": "src/server.ts"
      "env": {
        "APP_PORT": "8001",
      }
    }
  },
  "logger": {
    "debug": true
  }
}
``` 

- `allow` é onde damos as permissões do que o Deno pode executar na nossa máquina.
- `tsconfig` é pra dizermos pro Deno considerar nossas configurações extras do TS; 
- `unstable` pois nem todas as funcionalidades do deno e de algumas libs estão prontas;
- `scripts` é onde criamos configurações específicas. Podemos dar qualquer nome, coloquei _start_ por convenção mesmo; 
- `cmd` temos só nome do arquivo de _entry-point_ para aplicação, mas ele irá executar `$ deno run...` antes;  
- `env` para exportar variáveis de ambientes que só esta aplicação irá usar;
- `logger` para termos um log mais detalhado no terminal;

Assim que rodarmos `$ denon start` você notará que o que ele realmente executa é `$ deno run --allow-env --allow-net --config src/tsconfig.json --unstable src/server.ts`. Imagina ter que lembrar e digitar tudo isso toda vez? _Denon_ é opcional, mas extremamente útil.

## Debuging 

Pra melhorar ainda mais nossa experiência com Deno podemos debugar a aplicação pelo VS Code.

Basta criar o arquivo `.vscode/launch.json`
```javascript
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Deno",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "deno",
      "runtimeArgs": [
        "run",
        "--unstable",
        "--config",
        "src/tsconfig.json",
        "--inspect-brk",
        "-A",
        "src/server.ts"
      ],
      "port": 9229
    }
  ]
}
```

## Camada de dados

### TypeORM

Acho ORM uma ferramenta indispensável. Trabalhar direto com SQL no código - além de deixar tudo mais sujo, extenso e complexo - abre brechas de segurança e possibilidade de vários bugs. ORMs encapsulam toda essa complexidade e nos oferecem uma interface simples, testada e a possibilidade de usar a mesma estrutura da linguagem com o restante da aplicação. 

Não foi fácil encontrar um ORM que funcionasse direito no Deno, testei alguns e o que acabei mais gostando foi um [fork do TypeORM](https://github.com/denolib/typeorm) que já existia no Node.

Dentro do arquivo `deps.ts` vamos adicionar esta nova dependência

```javascript
export { 
  createConnection, 
  Connection, 
  Entity, 
  PrimaryGeneratedColumn, 
  Column, 
  OneToMany,
  ManyToOne,
  Repository
} from "https://denolib.com/denolib/typeorm@v0.2.23-rc4/mod.ts";
```

> Até o momento da publicação deste artigo este fork está em _release candidate_ ainda, muitas coisas podem não funcionar corretamente. __Paciência você deve ter.__

![yoda](https://media.giphy.com/media/26mfgpNiKHawv3wDS/giphy.gif)

### Entities

A primeira coisa que devemos fazer é criar as entidades. Dentro da pasta `src/database/entities` crie os seguintes arquivos

#### buyer.entity.ts
```javascript
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "../../deps.ts";
import Rating from "./rating.entity.ts";

@Entity("buyers")
export default class Buyer {

  @PrimaryGeneratedColumn()
  id!: number;

  @Column({ type: "varchar", length: 256 })
  fullName!: string;

  @OneToMany(type => Rating, _ => _.buyer)
  ratings!: Rating[];
}
```

#### product.entity.ts
```javascript
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "../../deps.ts";
import Rating from "./rating.entity.ts";

@Entity("products")
export default class Product {

  @PrimaryGeneratedColumn()
  id!: number;

  @Column({ type: "varchar", length: 256 })
  name!: string;

  @OneToMany(type => Rating, x => x.product)
  ratings!: Rating[];
}
```

#### rating.entity.ts
```javascript
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "../../deps.ts";
import Product from "./product.entity.ts";
import Buyer from "./buyer.entity.ts";

@Entity("ratings")
export default class Rating {

  @PrimaryGeneratedColumn()
  id!: number;

  @Column({ type: "int" })
  rate!: number;

  @Column({ type: "text" })
  comment!: string;

  @ManyToOne(type => Product, _ => _.ratings)
  product!: Product;

  @ManyToOne(type => Buyer, _ => _.ratings)
  buyer!: Buyer;
}
```

Como pode ver, toda configuração referente ao banco de dados é feita com _decorators_ dentro do TypeORM. Isso é muito legal pois podemos utilizar a mesma classe em outro cenários sem sujá-la com propriedades referente a como os dados estão armazenados. 

- O _decorator_ `@Entity()` torna nossa classe uma tabela dentro do banco, caso queira você pode dizer o nome que esta tabela terá; 
- `@PrimaryGeneratedColumn` diz que o campo `id` é a chave-primária e será auto-incrementável;
- Com `@Column()` fazemos as configurações que a coluna terá no banco;
- Para fazer relacionamento no TypeORM mantemos um campo com o tipo da classe relacionada como referência e usamos o _decorator_ `OneToOne()`, `OneToMany()`, `ManyToOne()` ou `ManyToMany()` neste campo. Sempre lembrando de fazer tanto a ida quanto a volta para ambas as entidades. Repare que na entidade `Rating` não criamos a `foreign key` para `Product` nem `Buyer`, o TypeORM faz isso pra gente e nos deixa trabalhar direto com a classe. Muito massa isso :smiley:

Há diversos outros _decorators_ e opções para os mais diversos cenários dentro do TypeORM. Recomendo olhar a [documentação](https://github.com/denolib/typeorm/tree/master/docs) para casos mais complexos. 

### Connection

Entidades criadas, agora vamos criar a conexão com o banco. 

Dentro da pasta `src/database` crie o arquivo `connection.ts` com essas configurações:

```javascript
import { createConnection, Connection } from "../deps.ts";

import Buyer from "./entities/buyer.entity.ts";
import Product from "./entities/product.entity.ts";
import Rating from "./entities/rating.entity.ts";
import env from "../env.ts";

class RatingConnection {

  connection!: Connection;

  async connect() {
    try {
      this.connection = await createConnection({
        type: "postgres",
        host: env.DB_HOST,
        port: env.DB_PORT,
        username: env.DB_USERNAME,
        password: env.DB_PASSWORD,
        database: env.DB_DATABASE,
        entities: [
          Rating,
          Product,
          Buyer,
        ],
        synchronize: true,
        logging: true,
      });

      return this.connection;

    } catch (err) {
      console.error(err);
    }
  }
}

export default new RatingConnection();
```

- `syncroniz` irá deixar o banco igual nossas entidades, criando as tabelas e removendo as que não existem;
- `logger` para vermos as _queries_ que a aplicação fará no terminal;

> Irei falar sobre _postgres_ e o `env.ts` logo abaixo. 

### Repositories

Com TypeORM conseguimos usar um _query-builder_ muito poderoso, podemos fazer _queries_ complexas com código JS/TS. Fazemos essas _queries_ a partir da `connection`, porém o TypeORM oferece a opção de criar um [repository](https://martinfowler.com/eaaCatalog/repository.html) padrão com muitos métodos comuns. A partir da `connection` chamamos o método `getRepository` passando o tipo da `Entity` no parâmetro. Isso irá retornar um novo objeto do tipo `Repository<TEntity>` que possui métodos como: `getId`, `create`, `update`, `delete`, `find`, `findOne`, entre outros. 

Dentro da pasta `src/database/repositories` vamos criar os seguintes arquivos:

#### buyer.repository.ts

```javascript
import { Repository } from '../../deps.ts';
import RatingConnection from '../connection.ts';
import Buyer from '../entities/buyer.entity.ts';

class BuyerRepository {

  private _instance: Repository<Buyer> | undefined;

  get instance() {
    if (!this._instance) {
      this._instance = RatingConnection.connection?.getRepository(Buyer);
    }

    return this._instance;
  }
}

export default new BuyerRepository();
```

#### product.repository.ts

```javascript
import { Repository } from '../../deps.ts';
import Product from '../entities/product.entity.ts';
import RatingConnection from '../connection.ts';

class ProductRepository {

  private _instance: Repository<Product> | undefined;

  get instance() {
    if (!this._instance) {
      this._instance = RatingConnection.connection?.getRepository(Product);
    }

    return this._instance;
  }
}

export default new ProductRepository();
```

#### rating.repository.ts

```javascript
import { Repository } from '../../deps.ts';
import Rating from '../entities/rating.entity.ts';
import RatingConnection from '../connection.ts';

class RatingRepository {

  private _instance: Repository<Rating> | undefined;

  get instance() {
    if (!this._instance) {
      this._instance = RatingConnection.connection?.getRepository(Rating);
    }

    return this._instance;
  }
}

export default new RatingRepository();
```

Basicamente estamos criando uma classe com um método _singleton_ que irá retornar uma instância do _repository_. 

> No TypeORM podemos usar o _decorator_ `@EntityRepository(TEntity)` para criar esses _repositories_ de maneira automática, porém essa versão apresentou alguns problemas. Uma alternativa seria usar uma lib de _dependency injection_, mas não vi a necessidade disso pra este projeto.

### Controllers

Camada de dados pronta, agora vamos criar os _endpoints_.

Dentro da pasta `src/controllers` crie o arquivo `rating.controller.ts`. 

```javascript
import { Context } from "../deps.ts";
import RatingRepository from '../database/repositories/rating.repository.ts';
import BuyerRepository from '../database/repositories/buyer.repository.ts';
import ProductRepository from '../database/repositories/product.repository.ts';
import Rating from '../database/entities/rating.entity.ts';

class RatingController {

  async index({ response }: Context) {

    const ratings = await RatingRepository.instance
      .createQueryBuilder('ratings')
      .select(['ratings', 'product.name', 'buyer.fullName'])
      .leftJoin('ratings.product', 'product')
      .leftJoin('ratings.buyer', 'buyer')
      .getMany();

    response.body = {
      success: true,
      data: ratings.map((rating: Rating) => {
        return {
          id: rating.id,
          comment: rating.comment,
          rate: rating.rate,
          buyer: rating.buyer.fullName,
          product: rating.product.name
        }
      })
    };
  }

  async create({ response, request }: Context) {
    const body = await request.body();
    const { comment, rate, buyerId, productId } = body.value;

    if (rate < 0 || rate > 5) {
      response.status = 400;
      response.body = {
        success: false,
        message: 'invalid rate'
      }

      return;
    }

    const product = await ProductRepository.instance.findOne(productId)
    const buyer = await BuyerRepository.instance.findOne(buyerId);

    if (!product || !buyer) {
      response.status = 404;
      response.body = {
        success: false,
      }

      return;
    }

    const rating = new Rating();
    rating.buyer = buyer;
    rating.product = product;
    rating.comment = comment;
    rating.rate = rate;
    await RatingRepository.instance.save(rating);

    response.status = 201;
    response.body = {
      success: true,
      data: rating
    }
  }
}

export default new RatingController();
```

Primeiro importamos os _repositories_. O método `index` irá retornar um array de `Rating`, a partir do `RatingRepository` fazemos a _query_ dando _join_ nas tabelas relacionadas e _select_ nos campos que iremos usar. O método `create` irá cadastrar um novo `Rating`, após pegar os dados do `body` fazemos uma validação, depois pegamos as entidades relacionadas e, caso não forem nulas, salvamos um novo `Rating`. Bem simples. 

### Rotas

Agora dentro de `src` crie `router.ts`

```javascript
import { Router } from './deps.ts'

import RatingController from './controllers/rating.controller.ts';

const router = new Router();

router
  .get('/ratings', RatingController.index)
  .post('/ratings', RatingController.create);

export default router;
```

### Server.ts

Fizemos várias alterações. Agora precisamos voltar ao `server.ts` e atualizá-lo com as novas classes. 

```javascript
import { Application } from './deps.ts'
import env from './env.ts';
import router from './routes.ts'
import RatingConnection from './database/connection.ts';

await RatingConnection.connect();

const app = new Application();

app.use(router.routes());
app.use(router.allowedMethods());

console.log(`Running on port ${env.APP_PORT}`);
await app.listen({ port: env.APP_PORT_INTERNAL || env.APP_PORT });
```

## Deploy

### .env

Toda aplicação tem dados sensíveis que mudam de ambiente para ambiente. É prática comum exportar esses valores quando rodamos a aplicação e obtê-los durante o runtime. Para sabermos quais dados devemos passar criamos um arquivo chamado `.env.example`. 

```
# Application
APP_PORT=
APP_PORT_INTERNAL=
APP_ENVIRONMENT=

# Database
DB_HOST=
DB_PORT=
DB_USERNAME=
DB_PASSWORD=
DB_DATABASE=
```

Com esse template copiamos todos as variáveis e criamos outro arquivo chamado `.env` com os devidos valores. **Não devemos versionar este último, ele deve existir somente no ambiente na qual a aplicação irá rodar.**

> Podemos também passar esses valores dentro da chave `env` do `denon.json` ou usar alguma lib que lê este tipo de arquivo e o carrega no environment do runtime.

### Dockerfile

Docker é uma das ferramentas mais usadas no desenvolvimento de software atualmente. Me atrevo a dizer que saber Docker hoje em dia é quase tão importante quanto saber Git. Ele é muito prático e nos ajuda em várias coisas. 

Não existe uma imagem oficial do Deno até o momento, o mais próximo disso é [esta imagem](https://hub.docker.com/r/hayd/deno) que está sendo cogitada nas [issues do GitHub](https://github.com/denoland/deno/issues/3356).

```dockerfile
FROM hayd/deno:alpine-1.0.5

EXPOSE 3333

WORKDIR /app

USER deno

COPY src/deps.ts /app
RUN deno cache --unstable deps.ts

ADD /src /app
RUN deno cache -c tsconfig.json --unstable server.ts

CMD ["run", "--allow-env", "--allow-net", "--config", "tsconfig.json", "--unstable", "server.ts" ]
```

Cada comando no Docker é como uma camada, onde a anterior se torna a base para as próximas. Com isso em mente:
- Primeiro obtemos a imagem base com **FROM**. Estamos usando a versão _alpine_, que é uma versão mais leve do linux. Estamos usando a versão 1.0.5 do Deno também. É boa prática sempre dizer a versão que sua aplicação usa para não atualizar de uma hora pra outra e, possivelmente, quebrar o sistema;
- Em seguida expomos a porta 3333 onde podemos receber requisições;
- **WORKDIR** é basicamente um `$ cd` dentro do container. Ele cria a pasta, caso ainda não exista;
- Mudamos de usuário _root_ com **USER**;
- Aqui um ponto importante. Copiamos o arquivo `deps.ts` e fazemos o cache dele. Este comando só será executado novamente caso as dependências mudem;
- Depois copiamos o restante da pasta `src` para dentro do container e _cacheamos_ o `server.ts` para que não seja compilado toda vez que rodarmos uma nova instância do container. 
- E por último, **CMD** irá executar os argumentos dentro do array toda vez que o container for iniciado.

Fazendo uma analogia com OOP: O `Dockerfile` é como uma classe - onde definimos o _shape_ que nosso container terá - e o container é o objeto instanciado.

### docker-compose

Para instanciar um container precisamos passar diversos argumentos pelo terminal. Lembrando também que um sistema geralmente não funciona sozinho - no BackEnd, por exemplo, temos a aplicação e o banco de dados. Pra conectar ambos pelo docker precisamos criar uma `network`. No caso do banco de dados, precisamos passar o `volume` para manter os dados persistidos. Além disso podemos passar as variáveis de ambiente, expor portas, entre outras coisas.

Para gerenciar tudo isso temos o `docker-compose`. Sua função é configurar todos os containers que precisamos, com todos os argumentos, e conectá-los.

Iremos criar dois containers
- **app** que fará o build da aplicação e configurar seus parâmetros de entrada;
- **database** onde iremos configurar um banco de dados da nossa escolha. No meu caso escolhi o `postgres`, mas pode ser qualquer outro de sua preferência. Só lembre de alterar o `type` no arquivo `connection.ts` caso mude de banco;

```yml
version: "3.1"

services:
  app:
    build: .
    env_file:
      - .env
    restart: always
    environment: 
      APP_PORT: ${APP_PORT}
      APP_PORT_INTERNAL: 3333
      DB_HOST: ${DB_HOST}
      DB_PORT: 5432
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      DB_DATABASE: ${DB_DATABASE}
    ports:
      - ${APP_PORT}:3333
    depends_on: 
      - database
  database:
    image: postgres
    env_file:
      - .env
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DB_DATABASE} 
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    ports:
      - ${DB_PORT}:5432
    volumes:
      - "ec.rating.data:/var/lib/postgresql/data"

volumes:
  ec.rating.data:
```

Em `app`:
- Primeiro dizemos em qual diretório o `Dockerfile` se encontra. Como está no mesmo do `docker-compose.yml`, utilizamos `.`;
- `env_file` irá ler o `.env` e carregar os valores pra dentro do container. Usamos a sintaxe `${...}` para obter esses valores;
- `restart` caso o container pare queremos que ele reinicie sem precisar de intervenção humana;
- `environment` passamos para a aplicação todas as variáveis de ambiente que ela precisará;
- `ports` mapeamos a porta do nosso _host_ para dentro do container;
- `depends_on` para que o `app` suba após o `database`. Isso irá evitar o erro do `app` subir primeiro e tentar conectar com o `database` que ainda não existe;

Em `database`:
- `image` base do `postgres` disponível no DockerHub
- (...) mesma coisa do `app`
- Como um container não mantêm estado precisamos mapear um diretório do `host` para dentro do container. Fazemos isso através dos `volumes`. Assim, independente de qual container esteja rodando, isso manterá os arquivos salvos já que ele sempre olhará para a mesma pasta. 

Quando criamos containers pelo `docker-compose` eles fazem parte automaticamente da mesma `network`. Dentro do `app`, por exemplo, podemos fazer referência ao `database` pelo seu _alias_.

A imagem do `app` não irá compilar a menos que você diga. Antes de mais nada rode `$ docker-compose build app` para que a imagem seja criada. Feito isso é só rodar `$ docker-compose up` e deve estar tudo funcionando :).

### Testando os endpoints

Eu costumo utilizar o [Insomnia](https://insomnia.rest/) para fazer requisições http, mas qualquer um serve.

#### GET

Utilizando a porta dentro do `.env`, fazemos uma requisição GET para `http://localhost:$PORT/ratings` e ele deve retornar um json com esta mesma estrutura: 

```javascript
{
  "success": true,
  "data": [
    {
      "id": 1,
      "comment": "best console",
      "rate": 4,
      "buyer": "Bruce Wayne",
      "product": "PS4"
    },
    {
      "id": 2,
      "comment": "xbox one is better",
      "rate": 2,
      "buyer": "Bruce Wayne",
      "product": "PS4"
    }
  ]
}
```

#### POST

Agora para criar um novo registro envie uma requisição POST para a mesma URL. Com um body nessa estrutura:

```javascript
{
	"comment": "new rate",
	"rate": 4,
	"productId": 1,
	"buyerId": 1
}
```

A resposta deve ser algo parecido com isso:

```javascript
{
  "success": true,
  "data": {
    "buyer": {
      "id": 1,
      "fullName": "Bruce Wayne"
    },
    "product": {
      "id": 1,
      "name": "PS4"
    },
    "comment": "new rate",
    "rate": 4,
    "id": 3
  }
}
```

### AWS

Irei publicar este projeto na AWS, porém como estamos usando Docker, qualquer provedor irá servir e a mudança na publicação é mínima.

Instale o [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv1.html) na sua máquina, [configure seu acesso pelo terminal](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) e instale o [docker-machine](https://docs.docker.com/machine/install-machine/). Agora, se foi tudo configurado certo, rodamos `$ docker-machine create --driver amazonec2 aws01` e isso irá criar e configurar uma máquina com tudo que precisamos para rodar os containers na AWS. Se rodarmos `$ docker-machine env aws01` iremos ver alguns valores referentes a máquina criada. Na última linha terá um comando como este: `$ eval $(docker-machine env aws01)`. Assim que rodá-lo o CLI do Docker **irá referenciar esta máquina remota como o Host**, desta forma podemos rodar `$ docker-compose up -d` e isso criará toda a aplicação e o banco de dados neste servidor. Feito isso é só liberar as portas no Dashboard da AWS e pronto. 

> Para voltar a referenciar sua máquina como _host_ no Docker rode `$ eval $(docker-machine -u)`.

Com Docker conseguimos criar todo um ambiente de maneira automatizada e fazer o Deploy de maneira muito simples. O próximo passo seria criar um processo de CI/CD e fazer este deploy de maneira automática também.

## Considerações finais

Vimos neste artigo a criação de uma API com Deno de ponta-a-ponta utilizando práticas de uma aplicação real.

Dado esta experiência posso concluir que Deno é uma promessa para um futuro próximo ainda. Definitivamente não recomendo criar nenhuma aplicação para produção com ele! Mas como bons programadores que somos, é muito legal aprender sobre algo novo, testar e ver tudo funcionando no final. Quem sabe daqui 1 ou 2 anos estaremos com um ambiente estável e discutindo sobre novas features dele. Quem quiser contribuir com a comunidade, este é o melhor momento. 

Aprenda, teste e ensine.
**DEVELOPERS TOGETHER STRONG**