Recently, I started delving into the Hanami framework which version 2 had been released a couple of months ago. Since I got to know Docker and Docker Compose, I use them over and over again because they help with running varied services on my machine without installing required dependencies, etc. If you don’t know them yet, I really encourage you to become acquainted with them. I find them highly convenient and I would like to use them also for setting API service connected with a relational database, in my case, it will be PostgreSQL. Let’s get the show on the road.
First of all, we have to make sure that the ruby version that we use is at least in version 3.0.0. That requirement is constituted by Hanami. To begin, we have to install the hanami
gem.
gem install hanami
Once it’s done, let’s create a new Hanami application from the scratch.
hanami new simple-api
This process created a bunch of files inside of the directory that follows the name of applications that we provided in the previous command. In my case it’s simple_api
.
Now, we need to create Dockerfile to specify the runtime environment for our application.
# simple_api/Dockerfile.dev
FROM ruby:3.2.1-alpine
WORKDIR /usr/src/app
COPY Gemfile Gemfile.lock ./
RUN apk update && apk add --no-cache build-base ruby-dev
RUN bundle install
EXPOSE 2300
COPY . .
We are going to use ruby:3.2.1-alpine
image to avoid using an overloaded ruby image but there is required to install two libraries to make it work: build-base
and ruby-dev
. If you are familiar with Rails, you might know the port number 3000 which is widely used, in the case of Hanami applications the default port is 2300. Once the Dockerfile is ready, we can build an image
docker build -t simple-api -f Dockerfile.dev .
Everything looks good, so we can jump into a docker-compose file.
# simple_api/docker-compose.yaml
version: '3.8'
volumes:
postgres-data:
services:
db:
image: postgres
volumes:
- postgres-data:/var/lib/postgresql/data
env_file: .env
app:
image: simple-api
command: sh -c "hanami server"
ports:
- "2300:2300"
depends_on:
- db
env_file: .env
What we specified here is the db
service that is PostgreSQL database and our web application server - app
. Image naming is pretty straightforward, as you could notice ports as well. We are going to use the default port number. In addition, we have to add POSTGRES_PASSWORD
environment variable to the .env
file, in my case: POSTGRES_PASSWORD=XYZ123QWE
.
# simple_api/.env
POSTGRES_PASSWORD=XYZ123QWE
Let’s run our containers by running
docker-compose up
Awesome, everything looks good but there is still one important thing to do, we have to connect simple-api
web server with postgres. In the beginning, let’s add a few gems to the Gemfile.
# simple_api/Gemfile
gem "rom", "~> 5.3"
gem "rom-sql", "~> 3.6"
gem "pg"
Next we have to add postgresql-dev
library to the Dockerfile.dev
file so that it can successfully install the pg
gem. The line from the file should look like below:
RUN apk update && apk add --no-cache build-base ruby-dev postgresql-dev
Let’s stop containers, rebuild the image, and run containers again. Before we start connecting the API application with the database server, we can create a database. We have to connect to psql
on the database container and call SQL statement, so:
docker-compose exec db psql -U postgres
CREATE DATABASE simple_api_development;
Now it’s time to get a closer look at Hanami framework itself because we have to add a persistence provider. We are supposed to create the following file:
# simple_api/config/providers/persistence.rb
Hanami.app.register_provider :persistence, namespace: true do
prepare do
require "rom"
config = ROM::Configuration.new(:sql, target["settings"].database_url)
register "config", config
register "db", config.gateways[:default].connection
end
start do
config = target["persistence.config"]
config.auto_registration(
target.root.join("lib/simple_api/persistence"),
namespace: "SimpleAPI::Persistence"
)
register "rom", ROM.container(config)
end
end
This target["settings"].database_url
refers to an environment variable with DATABASE_URL
name, let’s add DATABASE_URL=postgres://postgres:XYZ123QWE@db:5432/simple_api_development
to the .env
file.
# simple_api/.env
DATABASE_URL=postgres://postgres:XYZ123QWE@db:5432/simple_api_development
POSTGRES_PASSWORD=XYZ123QWE
Unfortunately, that’s not everything, we have to also add the database_url
setting to the Settings
class:
# simple_api/config/settings.rb
module SimpleAPI
class Settings < Hanami::Settings
setting :database_url, constructor: Types::String
end
end
Now we can rebuild our simple-api
image and restart containers.
Testing
Let’s prove that the connection is established and we can use the DB. We are going to create a table, insert sample data, and try to read it. As a first step, we are going to enable rake migrations by appending the following code to the Rakefile
.
# simple_api/Rakefile
require "rom/sql/rake_task"
task :environment do
require_relative "config/app"
require "hanami/prepare"
end
namespace :db do
task setup: :environment do
Hanami.app.prepare(:persistence)
ROM::SQL::RakeSupport.env = Hanami.app["persistence.config"]
end
end
We will add all the necessary code and then we will rebuild the simple-api
image.
A migration for table creation
# simple_api/db/migrate/20230228200134_create_books.rb
ROM::SQL.migration do
change do
create_table :books do
primary_key :id
column :title, :text, null: false
column :author, :text, null: false
end
end
end
and a relation
# simple_api/lib/simple_api/persistence/relations/books.rb
module SimpleAPI
module Persistence
module Relations
class Books < ROM::Relation[:sql]
schema(:books, infer: true)
end
end
end
end
Once it’s finished, we can rebuild the image and restart the containers. Then we can connect with a shell on the app
container by calling docker-compose exec app sh
. We have to run migrations by executing bundle exec rake db:migrate
. Then hanami console
and app["persistence.rom"].relations[:books].insert(title: 'The Alloy of Law', author: 'Brandon Sanderson')
. Inserting data itself shows us that the connection is working, but let’s try to read data: app["persistence.rom"].relations[:books].to_a
.
Voila! We have the Hanami application connected with PostgreSQL database. For more, you can visit the Hanami 2.0 getting started guide. I appreciate you reached the end of the article, thank you. Enjoy playing with Hanami.
Thanks
Goumbik for the background photo