DEV Community

Cover image for Elixir: Criando uma API REST simples ( sem Phoenix ) | Parte 1
Bruno Sampaio
Bruno Sampaio

Posted on

Elixir: Criando uma API REST simples ( sem Phoenix ) | Parte 1

Hoje Elixir já possui um framework web muito popular na comunidade chamado Phoenix , assim como Ruby on Rails no Ruby ou Django 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 ), 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:




Elixir: Criando uma API REST
( sem Phoenix ) | Parte 1


Follow





Hoje Elixir já possui um framework web muito popular na comunidade chamado Phoenix,
assim como Ruby on Rails no Ruby ou Django no Python. Mas como forma de aprendizado,
decidi construir essa API REST sem usar o Phoenix.

Você pode acompanhar detalhadamente como foi este processo em meu post.

Elixir: Criando uma API REST simples ( sem Phoenix ) | Parte 1

Requeridos

Instalação

clonando o projeto:
  git clone git@github.com:BrunoSampaioDev/elixir-rest-api-no-phoenix.git
Enter fullscreen mode Exit fullscreen mode
instalando as dependências:
  mix deps.get
Enter fullscreen mode Exit fullscreen mode
iniciando o projeto:
  iex -S mix run
Enter fullscreen mode Exit fullscreen mode

Open Source

Copyright © 2022-present, Bruno Sampaio.

is MIT licensed 💖






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, 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
Enter fullscreen mode Exit fullscreen mode

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 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

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.
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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]- Essa dependência é composta por outros dois módulos PlugCowboy
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 PlugCowboy 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:

defp deps do
    [
      {:plug_cowboy, "~> 2.0"},
      {:jason, "~> 1.3"}
    ]
end
Enter fullscreen mode Exit fullscreen mode

e em seguida execute o comando

mix deps.get
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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:

14:51:08.130 [info]  GET /

14:51:08.156 [info]  Sent 200 in 23ms
Enter fullscreen mode Exit fullscreen mode

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

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.

Top comments (0)