Elixir: Criando uma API REST simples ( sem Phoenix ) | Parte 1
Published: 2022-08-21 20:04:00

Hoje Elixir já possui um framework web muito popular na comunidade chamado [Phoenix](https://www.phoenixframework.org/) , assim como [Ruby on Rails](https://rubyonrails.org/) no Ruby ou [Django](https://www.djangoproject.com/) no Python. 
Mas como forma de aprendizado, decidi construir essa API REST **sem usar** o Phoenix.

Como estou familiarizado com Node.js, tentarei traçar paralelos com Javascript e Node.js sempre que possível, mas lembre-se é apenas um paralelo, pois Elixir é bem diferente de Javascript, a começar pelo paradigma que é estritamente funcional.

Se você não possui nenhum conhecimento prévio sobre Elixir, eu tenho uma ótima dica: recomendo a [formação em Elixir gratuita da Stone ( Alquimia )](https://www.youtube.com/watch?v=WRGvPgCHMfI&list=PLv3nyCBtlWP8I9rknIrfcJWrO05yEzknD), ministrada por profissionais da empresa que atuam no dia a dia utilizando Elixir para construção dos produtos e soluções da Stone.co

Antes de iniciar nossa jornada deixo aqui o Repositório do projeto que construiremos ao longo deste post:

{% github BrunoSampaioDev/elixir-rest-api-no-phoenix %}

##Vamos começar nossa pequena jornada

Presumo que você já tenha o Elixir instalado; caso contrário, você pode seguir o [guia oficial de instalação da linguagem](https://elixir-lang.org/install.html), ou ainda melhor, assistir os primeiros módulos da formação Elixir citada acima, onde você aprenderá a instalar Elixir utilizando o nvm.

Feito isso, vamos criar nosso projeto. 
Abra seu terminal, e digite o seguinte comando:  
```
mix new rest_api --sup
```

**Vamos entender este comando.**

- `mix` é a ferramenta de compilação integrada que vem empacotada com Elixir, que pode ser usada para criar, compilar, testar e gerenciar dependências de projetos elixir, é semelhante ao `npm` do Node.js.
    
- `mix new` [comando](https://hexdocs.pm/mix/1.12/Mix.Tasks.New.html) usado para criar um novo projeto no diretório atual.
    
    
- `rest_api` é o nome do nosso projeto.
    
    
- `-sup` essa flag cria um arquivo adicional para nós: **`lib/rest_api/application.ex`**. Ele implementa o chamado **`application behavior`**. Seu objetivo principal é implementar a função **`start/2`**, que deve iniciar um arquivo **`supervision tree`**. mas não se preocupe com isso agora, poderemos abordar este assunto em um post futuro. porém deixo aqui o link da doc oficial pra quem quiser entender um pouco mais sobre o assunto.  [Supervisor Behaviour](https://hexdocs.pm/elixir/1.13/Supervisor.html)

Após executar o comando teremos a seguinte saída em nosso terminal:
```
mix new rest_api --sup
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/rest_api.ex
* creating lib/rest_api/application.ex
* creating test
* creating test/test_helper.exs
* creating test/rest_api_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd rest_api
    mix test

Run "mix help" for more commands.
```

**Por dentro do projeto:**
## mix.exs
O arquivo `mix.exs` é semelhante ao `package.json` do node.js 
Este arquivo define um módulo especial chamado `RestApi.MixProject` que contém as informações sobre nosso projeto além de listar todas as nossas dependências.

Não sei se você percebeu mas no arquivo `mix.exs` a extensão é exs ao invés de .ex
Os arquivos com extensão .exs são usados para criar scripts e não devem ser compilados com a nossa app.

```elixir
defmodule RestApi.MixProject do
  use Mix.Project

  def project do
    [
      app: :rest_api,
      version: "0.1.0",
      elixir: "~> 1.13",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  def application do
    [
      extra_applications: [:logger],
      mod: {RestApi.Application, []}
    ]
  end

  defp deps do
    []
  end
end
```

O modulo `MixProject` contém duas funções **públicas** :

- `project` : Retorna as configurações do projeto como o nome do projeto, versão, versão do elixir a ser usada, etc...
- `application` : É o ponto de entrada para nossa aplicação.

Também temos uma função **privada** :

- `deps` : Retorna uma lista com as dependências do projeto.

## Adicionando novas dependências

A primeira coisa que precisamos fazer é adicionar algumas dependências que serão necessárias em nosso projeto:

• [[plug_cowboy]](https://hexdocs.pm/plug/readme.html)- Essa dependência é composta por outros dois módulos `Plug` e `Cowboy`. 
**`Plug`** nos fornece ferramentas para trabalhar com requisições HTTP, como construir endpoints, definir status code e etc..., porém o Plug não sabe como lidar com conexões. É aqui que o **`Cowboy`** entra em cena. este é um servidor web, escrito em **`Erlang`**, que trata de todas as conexões e processa qualquer solicitação de entrada ou saída. Ambos `Plug`e `Cowboy` juntos fornecem uma estrutura simples parecida com o `express.js` do **Node.js**.

• `jason`- É um analisador e gerador JSON, este é semelhante ao antigo `body-parser` utilizado no `express`.

vamos adicionar nossas dependências editando a função `deps` do nosso arquivo `mix.exs`:
```elixir
defp deps do
    [
      {:plug_cowboy, "~> 2.0"},
      {:jason, "~> 1.3"}
    ]
end
```
e em seguida execute o comando 
```
mix deps.get
``` 
para que o mix possa instalar todas as dependências necessárias.

## Construindo nossa primeira Rota.
Antes de começarmos a brincar com a nossa app, precisamos construir uma rota e configura-la para escutar na porta `8080`.

Crie um novo arquivo chamado `router.ex` no diretório `lib/rest_api` com o seguinte código:

```elixir
defmodule RestApi.Router do
  # usa o módulo Plug.Router neste escopo
  use Plug.Router

  # usa o módulo Logger do Plug para registrar as solicitações recebidas 
  plug(Plug.Logger)
  
# Faz match das requisições recebidas com os endpoints 
# definidos em nosso projeto.
  plug(:match)

  # Quando houver uma match, analisa o body da resposta, 
  # verifica se o tipo de conteúdo é application/json. 
  # Aqui a ordem é importante, pois só queremos
  # analisar o body se houver match em alguma rota. (Usando o Jason parser)
  plug(Plug.Parsers,
    parsers: [:json],
    pass: ["application/json"],
    json_decoder: Jason
  )

  plug(:dispatch)

  # Responde a requisição do tipo GET para a rota "/" 
  # com um status code 200, e um texto "ok"
  get "/" do
    send_resp(conn, 200, "OK")
  end

  # caso não de match em nenhuma rota, 
  # responde com um status code 404 e um texto "Not Found"
  match _ do
    send_resp(conn, 404, "Not Found")
  end
end
```
com isso acabamos de criar um novo módulo chamado Router onde usaremos o Plug.Router para lidar com nossas conexões de entrada. 
Para este tutorial, vamos apenas definir a rota `/` para responder com um http status code `200` e um texto `ok`.

Neste ponto, nosso servidor ainda não está em um estado executável. Mas podemos, é claro, testar nossa rota. Então é isso que vamos fazer a seguir. 
Substitua o código do arquivo `rest_api_test.exs` no diretório `test`  pelo seguinte:

```elixir
defmodule RestApiTest.Router do
 
  use ExUnit.Case, async: true
  
 
  use Plug.Test
  
  
  @opts RestApi.Router.init([])
 
  
  test "return ok" do
  
    conn = conn(:get, "/")

    
    conn = RestApi.Router.call(conn, @opts)

   
    assert conn.state == :sent
    assert conn.status == 200
    assert conn.resp_body == "OK"
  end
end
```

**Mas o que está acontecendo no código acima ???**
Aqui estamos usando o módulo `Plug.Test`, um recurso que o Plug nos oferece para facilitar o teste de nossas rotas. 
Primeiro, inicializamos nosso router, em seguida, executamos uma requisição do tipo `GET` para a nossa rota `/`. 

Por fim, estamos fazendo um assert para confirmar que nossa rota esta respondendo com um status code `200` e um `body` com um texto `"ok"`. 

Para executar o teste execute o seguinte comando no terminal.

```
mix test
```
Após o teste executar teremos uma saída semelhante a esta no terminal:

```bash
14:22:56.930 [info]  GET /

14:22:56.938 [info]  Sent 200 in 6ms
.

Finished in 0.05 seconds (0.05s async, 0.00s sync)
1 test, 0 failures

Randomized with seed 870601
```

E veja só… nossa funcionou como esperávamos, agora vamos prosseguir com a construção da nossa rest api.

## Executando o Servidor

Agora que sabemos que nosso router está funcionando a próxima etapa desse processo é conectar nosso router ao Cowboy para levantar um servidor HTTP e lidar com algumas requisições reais.

para fazer isso vamos ao arquivo `application.ex` que se encontra no diretório `lib/rest_api` e iremos adicionar a seguinte linha em nossa função start `{Plug.Cowboy, scheme: :http, plug: RestApi.Router, options: [port: 8080]}` veja o exemplo abaixo:

```elixir
defmodule RestApi.Application do
  use Application

  # The @impl true here denotes that the start function is implementing a
  # callback that was defined in the Application module
  # https://hexdocs.pm/elixir/main/Module.html#module-impl
  # This will aid the compiler to warn you when a implementaion is incorrect
  @impl true
  def start(_type, _args) do
    children = [
      {Plug.Cowboy, scheme: :http, plug: RestApi.Router, options: [port: 8080]}
    ]

    opts = [strategy: :one_for_one, name: RestApi.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```
Após essa alteração, estamos prontos para iniciar nosso servidor e testar de verdade se o endpoint está respondendo como esperamos. 

Execute o seguinte comando no terminal 
```
iex -S mix run
```  
quando a compilação estiver concluída, vá até o Postman, Insomnia ou qualquer outro client http de sua preferência e faça uma requisição do tipo  **`GET`** para a rota `http://localhost:8080`.

No meu caso preferi utilizar o Insominia e obtive a seguinte resposta ao realizar a requisição:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hvrozp30awbi00ohf6io.png)

Como podem ver nosso servidor respondeu exatamente como esperávamos, um status code 200, e um body com um texto OK.

Também obtivemos uma saída em nosso terminal:

```bash
14:51:08.130 [info]  GET /
 
14:51:08.156 [info]  Sent 200 in 23ms
```

Agora faremos o último teste deste post, em nosso arquivo de rotas `router.ex` também configuramos uma função para responder com um status code `404` e um texto `not found` caso nossa requisição não de metach em nenhum endpoint, vamos ver se funciona ???


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nioab5ijb8o3b70ph17r.png)

como podem ver na imagem acima fiz uma requisição para o endpoint `/any` que não existe em nossa aplicação, com isto obtivemos a resposta que esperávamos um status code `404` e um texto `not found`

**Bom! agora que já temos um servidor rodando e respondendo as nossas requisições, concluímos a parte 1 da nossa pequena jornada.**

Nos vemos na **parte 2** onde implementaremos alguns endpoints e conectaremos nossa app a um bando de dados.