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:
BrunoSampaioDev / elixir-rest-api-no-phoenix
API com Elixir sem 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
- Git instalado
- Elixir 1.12.0 instalado
- Erlang/OTP 24 instalado
Instalação
clonando o projeto:
git clone git@github.com:BrunoSampaioDev/elixir-rest-api-no-phoenix.git
instalando as dependências:
mix deps.get
iniciando o projeto:
iex -S mix run
Open Source
Copyright © 2022-present, Bruno Sampaio.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
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 aonpm
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 chamadoapplication behavior
. Seu objetivo principal é implementar a funçãostart/2
, que deve iniciar um arquivosupervision 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.
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
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 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
:
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:
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:
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:
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:
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:
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
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 ???
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)