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.
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 dinossauro de mascote.
Instalação
Vou sair um pouco do convencional e mostrar uma alternativa pra instalar o Deno - ou qualquer outro runtime que queira.
asdf é um gerenciador de versão universal. Após instalá-lo com o guia oficial rode os seguintes comandos:
$ 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 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.
{
"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.
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
.
{
"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, 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 - 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.
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
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, 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, 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.
{
"$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
{
"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 que já existia no Node.
Dentro do arquivo deps.ts
vamos adicionar esta nova dependência
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.
Entities
A primeira coisa que devemos fazer é criar as entidades. Dentro da pasta src/database/entities
crie os seguintes arquivos
buyer.entity.ts
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
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
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 campoid
é 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()
ouManyToMany()
neste campo. Sempre lembrando de fazer tanto a ida quanto a volta para ambas as entidades. Repare que na entidadeRating
não criamos aforeign key
paraProduct
nemBuyer
, o TypeORM faz isso pra gente e nos deixa trabalhar direto com a classe. Muito massa isso 😃
Há diversos outros decorators e opções para os mais diversos cenários dentro do TypeORM. Recomendo olhar a documentação 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:
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 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
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
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
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
.
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
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.
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
dodenon.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 que está sendo cogitada nas issues do GitHub.
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 oserver.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 otype
no arquivoconnection.ts
caso mude de banco;
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 dodocker-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 oapp
suba após odatabase
. Isso irá evitar o erro doapp
subir primeiro e tentar conectar com odatabase
que ainda não existe;
Em database
:
-
image
base dopostgres
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 dosvolumes
. 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 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:
{
"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:
{
"comment": "new rate",
"rate": 4,
"productId": 1,
"buyerId": 1
}
A resposta deve ser algo parecido com isso:
{
"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 na sua máquina, configure seu acesso pelo terminal e instale o docker-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
Top comments (5)
Ótimo artigo =)
Valeu mann! tmj 👊
Parabens 👏👏👏
Obrigado Rubens!!!
I intend to translate some of my articles to english soon, keep watch.
Dinosaurs show is so nostalgic, "Not The Momma" lol