Back to all Posts

Test Driving a Rails API - Part One

Part 1: Setup the Environment and Create a Rails Application

byJack Flannery

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.