Booksica-01: Criando uma API Rails com Postgres e Docker
Tags: devjournal, rails, docker, braziliandevs
Published: 2023-10-14 13:21:00

## Olá você que está lendo este post por algum motivo!

![Eu mostrando a API criada funcionando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/csyf8nrdguu0ayaj678c.jpeg)

Não sei como iniciar um diário de desenvolvimento corretamente, creio que seja a primeira vez que faço algo assim e também é a primeira vez que escrevo um artigo completo aqui no site. Portanto, peço desculpas se o formato ficar estranho.

Mas acho que você está aqui porque talvez queira criar um pequeno projeto em **Rails** e aprender um pouco mais sobre **Docker**. E é exatamente isso que eu também quero! Portanto, esta entrada no diário é minha tentativa de construir uma base para projetos e um passo a passo para você construir também.

Então vamos lá!


## Sobre o projeto
Estou em período de mudança e ainda não comprei uma estante para guardar meus livros, que estão todos jogados pelo chão do escritório. Como está tudo bagunçado não sei quantos livros são, quais são, se perdi algum na mudança, e não consigo definir uma lista do que irei ler.

Então a ideia é simples (na verdade extremamente complicada pois poderia usar uma planilha), construir uma API que converse com um banco de dados e guarde informações dos livros que tenho.


## Instalando Ruby e Rails na sua máquina
A primeira coisa é baixar e instalar o Ruby, você pode usar a [instalação padrão](https://www.ruby-lang.org/en/documentation/installation/) ou algum [gerenciador de versões](https://www.ruby-toolbox.com/categories/ruby_version_management). No meu caso estou usando [rbenv](https://github.com/rbenv/rbenv), e se você estiver usando linux recomendo também, é simples e gosto bastante.

Para este projeto estou usando **ruby ​​3.2.0**, mas fique à vontade para usar a versão mais estável/recente disponível.

Depois de instalar o Ruby, instalei a [gem de Rails](https://rubyonrails.org/) e mais algumas relacionadas ao [rubocop](https://rubocop.org/) para lint direto no meu editor de código (eu estou usando [vscode](https://code.visualstudio.com/) com as extensões [ruby](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby) ​​e [ruby-rubocop] (https://marketplace.visualstudio.com/items?itemName=misogi.ruby-rubocop)).

```
gem install rails
gem install rubocop
gem install rubocop-rails
gem install rubocop-performance
```

## Criando uma API Rails com Postgres
Onde trabalho e em vários outros projetos geralmente utilizo [PostgreSQL](https://www.postgresql.org/), estou acostumado com o comportamento e a interface de gerenciamento, configurei ele para ser o banco de dados principal. E como esse projeto será uma API, configurei a flag do Rails **para não criar arquivos front-end** (criarei um projeto separado para consumir a API mais tarde, provavelmente em [VueJS](https:/ /vuejs.org/)).

Fui em um gerador de nomes que me retornou algumas opções de nome do projeto, coloquei o nome como _booksica_.

```
rails new booksica-api --api --database=postgresql
```

Também criei o arquivo de licença como [MIT](https://choosealicense.com/licenses/mit/) para especificar o direito de qualquer pessoa de baixar, usar e modificar o projeto.


## Usando Docker para executar o projeto
Apesar de estar acostumado com o Postgres, não quero instalá-lo na minha máquina, nem usar o Rails diretamente. E por motivos de compatibilidade com outros computadores (e até para você mesmo baixar e executar esse projeto) resolvi usar containers [Docker](https://www.docker.com/). Se quiser, basta seguir este [passo a passo da instalação](https://docs.docker.com/engine/).

Criei meu Dockerfile com as configurações necessárias para um projeto Rails, e um _entrypoint.sh_, que exclui o arquivo _server.pid_ pois às vezes ele não é removido caso o container não seja desligado corretamente (gerando um [erro](https://www.chrisblunt.com/rails-on-docker-using-an-entrypoint-file-containers) ao executá-lo novamente).

Outro ponto a ser mencionado é a configuração **BUNDLE_FROZEN**, que sempre força uma comparação entre o _Gemfile_ e o _Gemfile.lock_, e se as versões forem diferentes o projeto não inicia (mais uma [configuração de segurança](https://stackoverflow.com/questions/68817050/using-bundle-frozen)).

### **`Dockerfile`**
```docker
# syntax = docker/dockerfile:1

# Versão do ruby que será usada
FROM ruby:3.2.0

# Instalação de dependências
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y postgresql-client

# Configuração do BUNDLE_FROZEN
RUN bundle config --global frozen 1

# Criação do diretório de trabalho
WORKDIR /booksica-api

# Cópia dos arquivos e instalação das gems
COPY Gemfile Gemfile.lock ./
RUN bundle install

# Rodar o arquivo de entrypoint para deletar o server.pid
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# Escutar porta 3000 e rodar o servidor
EXPOSE 3000
CMD ["./bin/rails", "server", "-b", "0.0.0.0"]
```

### **`entrypoint.sh`**
```bash
#!/bin/bash
set -e

rm -f /booksica-api/tmp/pids/server.pid

exec "$@"
```

## Configurando variáveis ​​do banco de dados
Antes de rodar o projeto, corrigi algumas configurações para que o ele pudesse se conectar ao banco de dados. Coloquei _host_, _username_ e _password_ como **variáveis ​​de ambiente**, que serão passadas tanto pelo arquivo de configuração do container, quanto em um arquivo _.env_ caso precise ser executado localmente em algum momento. As variáveis ​​estão no escopo _default_, portanto, o banco de dados de desenvolvimento e de teste tem as mesmas credenciais.

### **`config/database.yml`**
```yaml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: <%= ENV['DBHOST'] %>
  username: <%= ENV['DBUSER'] %>
  password: <%= ENV['DBPASS'] %>

development:
  <<: *default
  database: booksica_api_development

test:
  <<: *default
  database: booksica_api_test


production:
  <<: *default
  database: booksica_api_production
  username: booksica_api
  password: <%= ENV["BOOKSICA_API_DATABASE_PASSWORD"] %>
```

### **`.env`**
```properties
DBHOST=localhost
DBUSER=postgres
DBPASS=password
```


## Criando um arquivo docker compose
Por fim, para poder executar o projeto, criei um arquivo _docker-compose.yml_ contendo os seguintes serviços:

- Um serviço de banco de dados utilizando a imagem do Postgres;
- Um serviço administrador de banco de dados com a imagem pgAdmin (opcional);
- Um serviço rails executando meu projeto pelo Dockerfile;

### **`docker-compose.yml`**
```yaml
version: '3.8'

services:
  booksica-db:
    image: postgres
    container_name: booksica-db
    volumes:
      - postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: "booksica_api_development"
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "password"
    ports:
      - "5432:5432"
    networks:
      - booksica-api-network

  booksica-pgadmin:
    image: dpage/pgadmin4
    container_name: booksica-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: "user@booksica.com"
      PGADMIN_DEFAULT_PASSWORD: "password"
    ports:
      - "15432:80"
    depends_on:
      - booksica-db
    networks:
      - booksica-api-network

  booksica-api:
    image: booksica-api
    container_name: booksica-api
    build: .
    environment:
      - DBHOST=booksica-db
      - DBUSER=postgres
      - DBPASS=password
    volumes:
      - .:/booksica-api
    ports:
      - "3000:3000"
    depends_on:
      - booksica-db
    networks:
      - booksica-api-network

networks:
  booksica-api-network:
    driver: bridge

volumes:
  postgres:
```

Alguns pontos são interessantes de falar, todos os serviços possuem variáveis de ambiente definidas, as variáveis do _booksica-db_ e _booksica-api_ são de **acesso ao banco de dados**, e as do _booksica-pgadmin_ **credenciais de acesso** ao painel do administrador. Os serviços API e pgAdmin dependem do banco de dados, portanto, se você subir qualquer um deles, o serviço de banco de dados será carregado automaticamente.

Outro ponto é que criei uma **network** separada para os serviços se comunicarem, só acho mais fácil encontrá-la dando um nome específico. Se você quiser saber mais sobre as tags do docker compose eu recomendo a [documentação oficial](https://docs.docker.com/compose/gettingstarted/), o Docker é apenas assustador no começo, com o tempo você se acostuma.


## Rodando o projeto
Por fim, depois de tantas configurações, montei os containers do projeto e subi os serviços:

```
docker compose build
docker compose up
```

Em outro terminal entrei no container principal do projeto (booksica-api) com o comando bash para realizar a migração do banco de dados:

```
docker compose exec booksica-api bash
rails db:create
rails db:migrate
```

E logo após reiniciar os serviços tenho a confirmação de que está tudo funcionando.

![Página inicial do projeto em Rails](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukvit2pux6fz9m2r6fyv.png)

Também acessei o serviço pgAdmin para testar se o banco de dados foi criado corretamente.

![pgAdmin com os bancos criados](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wo7qp97kx5yeu58nog3.png)


## Conclusão
Por hoje é isso! O projeto está funcionando e pronto para as próximas mudanças, depois farei uma nova entrada aqui mostrando o processo de criação de um fluxo de trabalho de CI com Github Actions.

Se você tiver alguma dúvida ou dica sobre como posso melhorar o projeto (ou mesmo este post), fique à vontade para me enviar! Minha ideia é que esse diário de desenvolvimento seja periódico e não leve muito tempo (por mais que eu suma vez ou outra).

![Eu falando pra você o quanto esse diário de desenvolvimento irá demorar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3euoewbllfzefgf0auo4.png)


## Link para o projeto
https://github.com/betosardinha/booksica-api

## Música aleatória para você escutar
{% embed https://youtu.be/oHsGr6RYpJA %}