Back to all Posts
Test Driving a Rails API - Part One
Part 1: Setup the Environment and Create a Rails Application
Intro to REST APIs
In this tutorial, I will show you how to create an API-only application with Ruby on Rails. While Rails is often used to create monolith web applications that serve server-rendered HTML pages, its versatility and rich feature set make it a great choice for building API applications.
The demand for highly interactive browser applications has prompted a surge in JavaScript front-end frameworks. These frameworks allow developers to shift a significant portion of application logic to the client side, using a RESTful API for interactions with the back-end application server. A web application where JavaScript renders the UI in the browser, which is highly interactive and communicates with the server through an API, is often called a single-page application (SPA).
In this tutorial, we’ll build a REST API, sometimes called a web API or JSON API. An API, or application programming interface, is a way for two applications or pieces of software to communicate with each other.
REST APIs allow a client application, typically a SPA running in a web browser, to communicate with the backend application server, which handles the application’s data storage and persistent state.
The client makes HTTP requests to fetch data from and save data back to the server. The payload of those requests will contain representations of the application’s data. Those representations will often be formatted as JSON, as will be the case here.
Architecting a web application with an interactive client side alongside a REST API presents both advantages and challenges. A primary benefit is that it allows you to use the same API to build any number of clients: web, mobile, desktop, etc. Additionally, this approach facilitates a clear separation of concerns, decoupling the UI from the data storage and authentication concerns.
Combining the API and client-side components within a single codebase is feasible, albeit with its own set of considerations. However, in this tutorial, we'll develop the API within its own distinct codebase. Subsequent articles will delve into constructing front-end applications that consume the API using various frameworks, providing a comprehensive understanding of both ends of the development stack.
In 2024, there are a myriad of options for building a REST API, and Rails remains high on my list. Rails’ extensive feature set and great developer experience allow you to get up and running with an API very quickly.
About the API
The API we’re about to build will allow us to manage events on a Calendar. Through this API, we’ll be able to create, read, update, and delete calendar events. It’s a CRUD API, but the complexities of calendar events will introduce some additional logic to make things a little more interesting. We’ll use tests to guide us as we develop our API.
Clone the repository yourself and follow along.
Install prerequisites
Before we can create the app, we need to install Ruby, the Rails gem, and the PostgreSQL database.
Install Ruby and Rails
Let’s get started. I prefer to manage my Ruby installations on my development machine with chruby paired with ruby-install. Another outstanding set of tools is rbenv with ruby-build. I highly recommend installing Ruby with one of those two sets of tools. Follow the instructions on their project’s READMEs. For this article, I’ll be running Ruby (MRI) v3.3.0.
$ ruby -v
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin23]
With Ruby installed, you can now manage your RubyGems with the gem
command. Install the Rails gem or update if you already have it.
$ gem install rails
or
$ gem update rails
For this article, I’ll be running Rails v7.1.3:
$ rails -v
Rails 7.1.3
Install PostgreSQL
A running Rails application needs a database to connect to. You may already have your database of choice installed, but if not, I recommend PostgreSQL, or Postgres for short. On a Mac, probably the easiest way to install it is with Posrgres.app. Another option, the one I prefer, is to use Homebrew. With Homebrew installed, this command will install PostgreSQL version 16 along with libpq:
$ brew install libpq postgresql@16
When the install finishes, it will display several Caveats to the postgresql@16
install, a few of which are of interest to us; first:
If you need to have postgresql@16 first in your PATH, run:
echo 'export PATH="/usr/local/opt/postgresql@16/bin:$PATH"' >> ~/.zshrc
While not required for a Rails app to connect Postgres, it is nice to have Postgres’ bin directory in your path because it contains several useful executables for interacting with Postgres, including postgres
, psql
, createdb
, createuser
, and pg_dump
.
Add the Postgres bin directory to your path by adding this line to the shell configuration file that loads on shell startup:
export PATH=/usr/local/opt/postgresql@16/bin:$PATH
Or you can do that with the following command in your terminal:
$ echo 'export PATH=/usr/local/opt/postgresql@16/bin:$PATH' >> ~/.bashrc
Then, reload the file so that it takes effect.
$ source ~/.bashrc
The other, more important caveat is:
To start postgresql@16 now and restart at login:
brew services start postgresql@16
Or, if you don't want/need a background service you can just run:
LC_ALL="C" /usr/local/opt/postgresql@16/bin/postgres -D /usr/local/var/postgresql@16
I prefer to manage running Postgres with brew services
. The following command will start up Postgres in the background now and automatically every time your Mac starts up.
$ brew services start postgresql@16
This way, you mostly won’t have to worry about starting and stopping Postgres, it will always be running in the background. However, you can always restart, start, and stop Postgres with the following brew services
commands:
$ brew services restart postgresql@16
$ brew services stop postgresql@16
$ brew services start postgresql@16
$ brew services list
Name Status User File
postgresql@15 started jack ~/Library/LaunchAgents/homebrew.mxcl.postgresql@16.plist
PostgreSQL databases and roles
With Postgres installed, we can now create any number of databases. A database named postgres
was created for us. Later, we’ll use Rails’ built-in task to create our application’s development and test databases.
Before Rails can begin interacting with Postgres, we need to specify the database user through which it can access the database. When we installed Postgres, a user (called a role in Postgres) with superuser privileges was created for us, with the same name as our current operating system user. My OS username is jack
. Therefore, I now have a Postgres role named jack
. This can be our app’s database user. Optionally, you can set a password for your user. To do so, launch psql, the command-line interface (CLI) tool for interacting with Postgres:
$ psql -d postgres
Because we did not specify one, psql
will attempt to connect as a role under the same name as your OS user. You should have such a role, so there is no need to specify it. psql
will also attempt to connect to a database with the same name as your OS user. In this case, we do not have such a database, so we must pass the -d
flag to connect to the database named postgres
.
At the prompt, use the \password
command to set a new password:
postgres=# \password
Enter new password for user "jack":
Enter it again:
postgres=# \q
Use \q
to quit psql
.
Alternatively, you could create a dedicated Postgres role for each application you’re developing. But on your development machine, sharing the same Postgres role for all apps under development is fine.
Configure bundler for the pg gem
If you installed Postgres via Homebrew, you need to configure bundler so that when it installs the pg gem, it knows where to find the pg_config executable, which is installed as part of Postgres. The pg gem is the Ruby interface to Postgres and requires pg_config
during installation. We can use this command to configure bundler
so that it can find it and successfully install pg
.
$ bundle config set build.pg --with-pg-config=/usr/local/opt/postgresql@16/bin/pg_config
Generate the application
This command will generate a new Rails API-only application named cal_api
using PostgreSQL as the database.
$ rails new cal_api --api --database=postgresql
With that, our app is generated, all dependent gems are installed, and a new git repository is initialized. You can now cd
into the app’s directory.
Configure the database connection
Rails needs to know how to connect to your database and expects to find connection information in the config/database.yml
file. When the app was generated with the --database=postgresql
flag, a database.yml
file tailored for Postgres was created for us. It already contains everything we need to get started; we don’t actually need to specify a database user because Postgres will automatically connect as the Postgres role with the same name as our OS user. However, in the name of making the app more configurable, I prefer to set all of the database config values explicitly. Open config/database.yml
, and I recommend reading through and understanding the comments; some valuable information is within. Then replace all of it’s contents with the following:
default: &default
encoding: unicode
adapter: <%= ENV['DB_ADAPTER'] %>
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>
host: <%= ENV.fetch('DB_HOST') { 'localhost' } %>
port: <%= ENV['DB_PORT'] %>
pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %>
development:
<<: *default
database: cal_api_development
test:
<<: *default
database: cal_api_test
production:
<<: *default
database: cal_api_production
As you can see, there are almost no actual values; the values will come from the environment. Rails will automatically process the ERB when it reads the file. We can keep this in the git repo because it doesn’t expose private information.
Install dotenv
Storing environment variables for a Rails app can be problematic. The dotenv gem will automatically, when Rails boots, load environment variables from .env
files into the Rails ENV
. This is a great way to store private information that varies per developer or deployment environment, such as your development database configuration. Rails Encrypted Credentials is a great way to store private information, like API keys, etc, but I wouldn’t use it for storing my local development environment’s database information. The Encrypted Credentials file is checked into the git repository and would, therefore, be shared by all developers on the project. dotenv
allows each developer or deployment environment to store their own information in .env
files that are ignored by git.
To install dotenv
add it to your Gemfile
's development
and test
groups.
group :development, :test do
gem 'dotenv'
end
Install the gem:
$ bundle install
A common strategy is to simply store all environment variables in a .env
file that is kept out of the git repository. That works fine, however, I like to follow the advice on the dotenv readme and commit the .env
file to the repository. We’ll take advantage of the precedence dotenv follows when loading .env.* files and have each developer maintain a .env.local
file, which we will keep out of the repository. The .env
file will contain all of the variable names without any values and will serve as an example. Any new developer coming onto the project will make a copy of .env
named .env.local
and fill in their own specific database configuration. Rails boots, the values in .env.local
will take precedence over those in .env
.
First, because by default dotenv
will not load the .env.local
file in the test environment, add the following line of code to your config/application.rb
file:
Dotenv::Rails.files.unshift(".env.local") if ENV["RAILS_ENV"] == "test"
Be sure to add the new line right under the line Bundler.require(*Rails.groups)
near the top of the file like this:
# config/application.rb
Bundler.require(*Rails.groups)
# Add this line to load .env.local in test
Dotenv::Rails.files.unshift(".env.local") if ENV["RAILS_ENV"] == "test"
Next, create a file named .env
at the top level of the project and add the following:
DB_ADAPTER=
DB_USERNAME=
DB_PASSWORD=
DB_PORT=
Then make a copy of .env
called .env.local
with your values filled in:
DB_ADAPTER=postgresql
DB_USERNAME=jack
DB_PASSWORD=secret
DB_PORT=5432
These four variables must be defined, otherwise Rails will not have a complete database configuration. It's worth it to make these variables required and have Rails fail early on startup if they are not defined. Create the file config/initializers/dotenv.rb
and add the following contents:
if Rails.env.development? || Rails.env.test?
Dotenv.require_keys("DB_ADAPTER", "DB_USERNAME", "DB_PASSWORD", "DB_PORT")
end
This way, if the variables are not defined, Rails will fail with a clear error message.
Ensure that /.env.*
is listed in your .gitignore
file. The base generated .gitignore
file should have the entry /.env*
, which will cause git to ignore .env
.
Open your .gitignore
file and change the line
/.env*
to
/.env.*
With that, git will now see the .env
file, and you can commit it to the repository.
Create the Databases
If your database is configured properly, you can use the Rails built-in task to create the application's databases. Run the following command in your terminal:
$ rails db:create
That command should have created the development and test databases, as named in the database.yml
file.
Wrapping up Part One
There’s a lot to think about and understand when building an API with Ruby on Rails. Our app is all setup, and now we’re ready to get to the real meat and potatoes of the project. I’m going to break this post up into two parts, otherwise it’d be far too long. In Part Two, we’ll get to building our models, designing the database schema, and building out the API endpoints. We’ll be writing tests along the way and using them as a guide to drive our development. See you in Part Two.