Whether you create frontends in JavaScript, Swift, Java, or Kotlin, there’s a server-side framework for creating web service APIs in the same programming language you already use. It would seem like it’d be easiest for you to write your web service in the same language you use for your frontend.
But “seems” can be deceiving! The fastest and easiest way to create a web service API is Ruby on Rails—even if you don’t know Ruby. Rails’ batteries-included approach means that you don’t have to write boilerplate or implement plumbing yourself. Instead of being locked in to features a hosted API service provides, or having to write them all yourself, the Rails ecosystem is so mature that there is a package for just about every feature you’ll need.
To see how quickly Rails allows us to create and deploy a web service to production, let’s create a web service for tracking todos.
Getting Past the CRUD
Install the latest version of Ruby (2.5.1 as of this writing), then install Rails:
$ gem install rails
Create a new Rails app with rails new
. We’ll pass in a few flags to remove features we don’t need, to keep our app as simple as possible:
$ rails new simple-rails-api --api \
--skip-action-cable \
--skip-active-storage \
--skip-test
$ cd simple-rails-api
Now let’s create our todo resource using the rails generate scaffold
command, passing it the fields and types we want:
$ rails generate scaffold todo title:string complete:boolean
This creates everything we need to get started with todos:
app/models/todo.rb
— A model with atitle
string field and acomplete
boolean field.app/controllers/todos_controller.rb
— A controller with default implementations of functionality to create, read, update, and delete todos.- An entry in
config/routes.rb
mapping URLs to the actions in that controller. - A file in
db/migrate
with instructions to create a table for todos in our database.
Let’s run that migration to update our database:
$ rails db:migrate
Next, let’s insert a Todo record by hand. You don’t need to use a SQL database tool directly; you can use the rails console
:
$ rails console
The Rails console lets you execute any arbitrary Ruby code you want. Run the following to create a todo:
irb(main):001:0> Todo.create!(title: 'Create an API', complete: false)
You’ll see some log output and then the following:
=> #<Todo id: 1, title: "Create an API", complete: false,
created_at: "2018-07-01 10:42:49", updated_at: "2018-07-01 10:42:49">
A few notes on what we typed:
Todo
is the name of the model class.create!
is one of many methods available on Rails models. It inserts the record and will throw an error if we get something wrong, like pass an incorrect field name. Ruby method names can end with a!
or?
.- Ruby supports named parameters just like Swift and Kotlin.
Now let’s see what URLs were created for us by generate resource
. Run rails routes
:
$ rails routes
Prefix Verb URI Pattern Controller#Action
todos GET /todos(.:format) todos#index
POST /todos(.:format) todos#create
todo GET /todos/:id(.:format) todos#show
PATCH /todos/:id(.:format) todos#update
PUT /todos/:id(.:format) todos#update
DELETE /todos/:id(.:format) todos#destroy
This is all we need to create, read, update, or delete todos. Let’s give them a try! Let’s start up our server:
$ rails server
Now go to http://localhost:3000/todos
in your browser. You should see JSON data returned (the way it’s formatted may vary depending on your browser):
[
{
"id": 1,
"title": "Create an API",
"complete": false,
"created_at": "2018-07-01T10:42:49.188Z",
"updated_at": "2018-07-01T10:42:49.188Z"
}
]
Notice that, although we didn’t specify them, Rails created fields for id
, created_at
, and updated_at
. All of these will be automatically assigned for you when records are created or updated.
Next let’s access an individual record. Go to http://localhost:3000/todos/1
and you should see just an individual record returned.
To test out creating a record, download the Postman app. Create a new request to POST
to http://localhost:3000/todos
. In the Body tab, choose “raw” and “JSON (application/json)”, then enter the following body:
{
"title": "Test out create endpoint",
"complete": true
}
Click “Send” and you should receive a 201 Created
response with the following content:
{
"id": 2,
"title": "Test out create endpoint",
"complete": true,
"created_at": "2018-07-01T11:36:35.793Z",
"updated_at": "2018-07-01T11:36:35.793Z"
}
We saw other routes earlier for updating and deleting todos; you can feel free to try those out if you like.
Validation
Next, let’s add some validation. We probably want to ensure the title
field isn’t empty. Open app/models/todo.rb
and you’ll see the following:
class Todo < ApplicationRecord
end
This is pretty typical class declaration syntax, other than using <
to indicate inheritance. It creates a Todo
class that inherits from ApplicationRecord
, a class Rails sets up for us. Note that there is no other code in this model yet, but it still works. We don’t need to configure it with the fields on the object; Rails can tell the fields by inspecting the database table.
To make the title required, add this:
class Todo < ApplicationRecord
+ validates :title, presence: true
end
Here’s what’s going on with this line of code:
validates
is a method provided onApplicationRecord
that we are running in the middle of the class definition. Notice that parentheses are optional for method calls in Ruby; this is the same as writingvalidates(:title, presence: true)
. Typically, “normal” method calls in our code are written with parens, but calls to Rails class-level methods likevalidates
are called without parens.- An identifier that starts with
:
is a symbol; it’s just like a string but it doesn’t have string manipulation methods like substring. Here we’re just saying to validate the field named:title
.
To test this out, in your Postman request, change the title
to an empty string:
{
"title": "",
"complete": true
}
Rerun the request. You’ll get a 422 Unprocessable Entity
response, with the following body:
{
"title": [
"can't be blank"
]
}
Default Values
Instead of making the complete
field required, let’s set it to default to false
if it isn’t specified. We can create another migration to update our database scheme to set the default value:
$ rails generate migration set_default_todo_completed_to_false
This creates a new empty migration file. Add this line to it:
class SetDefaultTodoCompletedToFalse < ActiveRecord::Migration[5.2]
def change
+ change_column_default :todos, :complete, from: nil, to: false
end
end
(The reason we specify a from:
value is so the migration is reversible. If you ever need to undo a migration, just run rails db:rollback
.)
Run the migration with rails db:migrate
. Now send a POST
request without a complete
value specified:
{
"title": "Defaulting to incomplete"
}
The response you get should have complete
set to false
:
{
"id": 7,
"title": "Defaulting to incomplete",
"complete": false,
"created_at": "2018-07-02T09:02:08.884Z",
"updated_at": "2018-07-02T09:02:08.884Z"
}
Enabling CORS
If you’re planning on accessing this API from a web app on another “origin”—even a different port on localhost
—browsers will block the request by default for security. We can allow these requests by configuring Cross-Origin Resource Sharing (CORS) headers.
Open the Gemfile
at the root of your project. This file specifies dependencies like package.json
for JavaScript or gradle.build
for Android.
In your Gemfile, find this line:
# gem 'rack-cors'
A gem is a Ruby package. The #
is Ruby’s comment marker, so this dependency is commented out. Uncomment this line by removing #
.
Next, run bundle install
to install the rack-cors
gem. bundle
is the Ruby package manager, equivalent to npm
, yarn
, or gradle
. Running the install
subcommand will install the gems and update the Gemfile.lock
, which tracks exact versions that were installed.
Next, open config/initializers/cors.rb
. Uncomment the following block:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'example.com'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Then change origins
to allow any origin:
- origins 'example.com'
+ origins '*'
Next, restart your rails server
. Rails can automatically load changes to your classes without requiring a restart. But if you add a gem or change an initializer, as we did here, you need to restart the server to catch the changes.
Now you should be able to make requests from web apps running on other ports.
Enabling a Production-Ready Database
We’re almost ready to go to production, but first we need to change our database. By default Rails uses a SQLite database, which is easy for development but not robust enough for production. Let’s tell our Rails app to install support for Postgres in production, but continue to use SQLite for development.
Open your Gemfile
and find the line gem 'sqlite3'
. Move this line down inside the group :development, :test do
block:
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+ gem 'sqlite3'
end
This ensures SQLite will only be installed in the development and test environments, not production.
Next, create a :production
group with the pg
gem in it:
group :production do
gem 'pg'
end
This will install the Postgres gem only in production.
Run bundle install
to update your Gemfile.lock
.
Deploying to Production
Now we’re ready to deploy to production. Heroku is a super simple platform for deploying applications in many languages, and allows you to run “hobby” applications for free. Heroku has particularly good support for Rails apps: it will automatically detect that you have a Rails app and run it appropriately. It will also automatically create a Postgres database and provide the connection info to your Rails app, so you don’t need to change any config files to use it.
Heroku allows you to deploy code using Git pushes. If you haven’t already been committing your code to a Git repo, go ahead and do so:
$ git init
$ git add .
$ git commit -m "Create app"
Sign up for a Heroku account, install the Heroku command line tools, and log in:
$ heroku login
Now it’s time to create our Heroku app.
$ heroku create
This will create an app on Heroku, assign it a (very) random name, and connect your local Rails app to it. To deploy:
$ git push heroku master
This is just a normal git command; you’re pushing your local master branch to the git remote named heroku
that the heroku create
command set up for you.
You’ll see a bunch of output, and if it succeeds, you’ll see the following at the end:
https://your-app-name.herokuapp.com/ deployed to Heroku
Heroku doesn’t automatically migrate your database, so we need to run the migrate command as we’ve been doing locally. To run a command in a Heroku app, prefix it with heroku run
:
$ heroku run rails db:migrate
Now your app should be ready to access. Run your POST command against your production app URL and you should see a successful response!
Next Steps
In just a few minutes Rails allowed us to create a fully-functioning web service and deploy it to production. But there’s one major thing missing: we need to secure it so not just anyone can change data. In my next post we’ll add password authentication to our API.