Writing an API in Rails 6
Tags: rails, beginners, codenewbie, webdev
Published: 2020-07-28 15:25:47

In the final stage of my [coding bootcamp](https://dev.to/alvinqingxing/so-i-just-completed-coding-bootcamp-30ep), when I had become familiar with Ruby on Rails, one project I wanted to do was to write my own API.

APIs (Application Programming Interfaces) are widely used on the internet for computers to send data to one another. Unlike a standard web page, where the server sends a bundle of HTML, CSS, and JavaScript to create a view which is consumed by a human audience, with APIs it is machines communicating with one another.

So if you are viewing a web application that requires data from an API, your browser will send the request to the API, get the requested data back, and then *render* that data into a format that is suitable for you, a human, to view.

Hence, instead of HTML and CSS, APIs send data in more machine-friendly formats such as JSON, YAML, or XML.

For my API, I wanted to do something fun. My coding class had previously learned how to use [Faker](https://github.com/faker-ruby/faker) to seed our databases with mock information, and I recalled that one of Faker's libraries consisted of Chuck Norris jokes. I decided to go with this and create an API that sends out Chuck Norris jokes.

Let's build the API! First I create the directory and go into it:

```
$ mkdir chuck-norris-api
$ cd chuck-norris-api
```

Once in the directory, I instruct Rails to generate the app:

```
$ rails new chuck-norris-api --api --database=postgresql
```

The `--api` option (which is available in Rails 6) tells Rails that you don't want it to generate a full app -- only enough for an API to run. I set the second option `--database=postgresql` as I will be using Heroku to host the API, and Heroku does not support Rails' default SQLite database.

Once the app is generated, I open my code editor and edit the `Gemfile` located at the root of the directory. I add `gem 'faker'` and uncomment `gem 'rack-cors'`:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7bk7z822wof88ia7ncn5.png)

As I explained earlier, I will be using [Faker](https://github.com/faker-ruby/faker) to populate my database with Chuck Norris jokes, while [Rack CORS](https://github.com/cyu/rack-cors) is the middleware I need to ensure that the front-end I will be building to  display the jokes will be able to receive the jokes from the API (more on that later).

I then run `bundle install` in the terminal to install the new gems. Once that's done, it's time to build out the app's MVC!

First, the **models**. In this case, there's only one: the jokes. Each joke has one field: its *content*. In the terminal I instruct Rails to generate the model:

```
$ rails g model Joke content
```

This create the following migration:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/04wcq7rmzt8hyk0yitvc.png)

In the terminal I instruct Rails to run the migration:

```
$ rails db:migrate
```

This creates the Jokes table in the database:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/h8do6qk9dndxga923u9r.png)

The database can now be populated with the Chuck Norris jokes. In `seeds.rb` I instruct Rails to retrieve a hundred jokes from [Faker's](https://github.com/faker-ruby/faker) Chuck Norris library:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cumfm0vwv62ijxbgmh8i.png)

In the terminal, I then instruct Rails to run `seeds.rb` and populate the database:

```
$ rails db:seed
```

Now that the **model** and database are sorted, I can proceed with the **controller** and **view** (the remaining two-thirds of the app's MVC). In the `app\controllers` folder I create the nested folders `api\v1`. In the `v1` folder I create the `jokes_controller.rb` file:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7fq0shqhggt7tm8zews1.png)

In my controller, I only allow the user to perform two actions: get *all* jokes, and get *one* particular joke. I do not want the user to edit or delete existing jokes or to add new jokes. This is for the simple reason that I do not want to have to keep checking the database for inappropriate content once the API is deployed. Hence I do not include these remaining [CRUD operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) in the controller.

As with the controller, I add nested folders (`api\v1\jokes`) in the `app\views` folder. The `api\v1\jokes` folder has two files corresponding to the two controller actions. The first is the `index.json.jbuilder` file which will build and return a JSON file containing an array of all one hundred Chuck Norris jokes when a request for all jokes is received. In particular, I instruct the JSON builder to *only* include the content of each joke in the array, and leave out the other fields such as the id numbers of the jokes:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7xjjwhb6fkyp2hsdylg9.png)

The second is the `show.json.jbuilder` file which will return the joke with the particular id sent by the controller. As with the index file, I instruct the JSON builder to only include the content of the joke in the JSON file that is sent back to the user:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pysq7xq78uqofbpb60i8.png)

Now that the MVC are done, we can now proceed to the routes. In the `config\routes.rb` file I specify the nested `api` and `v1` namespaces that correspond to the nested `api\v1` folders where the controller is housed, and I also specify that the only actions offered by the API are the `index` (get *all* jokes) and `show` (get *one* joke) actions, rather than the full suite of [CRUD operations](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete):

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1m0sjwr8o0chwspzh1dy.png)

There is one final step that I need to do before I can deploy the app to production. It is a security feature of modern web browsers that, by default, they do not retrieve data from APIs if the domain of the requesting web page is different from the domain of the API. This will only be allowed if the API specifically allows web pages hosted on that particular domain to retrieve its data.

The obvious way to get around this restriction is to host the web page on the same domain as the API. But what is the fun in that? For this project, the API will be hosted on Heroku, and the web page which I will be displaying the jokes will be hosted on GitHub Pages. This is where the [Rack CORS](https://github.com/cyu/rack-cors) gem that had been installed earlier comes in. In the `config\initializers\cors.rb` file I specify that my GitHub Pages domain is to be allowed:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/90vpztr8doxsxlpzblw7.png)

While it is certainly possible to set `origins '*'` in `cors.rb` and allow *any* website to access the API, this will effectively bypass the security protocol. So I will be following the best practice and curate which domains are allowed to use the API. It should be noted that this is a security feature of web browers. As we shall see, if you use a tool like [curl](https://curl.haxx.se/docs/manpage.html) or [Postman](https://www.postman.com/) to access the API, this cross-domain security block will not apply.

The app is now ready to be deployed! It can be accessed at `https://rails-chuck-norris-api.herokuapp.com` and as noted, you can use `curl` from your terminal to retrieve all the jokes:

```
curl -s https://rails-chuck-norris-api.herokuapp.com/api/v1/jokes | jq
```

... or any one of the one hundred jokes. Here `curl` retrieves joke #99:

```
curl -s https://rails-chuck-norris-api.herokuapp.com/api/v1/jokes/99 | jq
```

But as noted, I have created [a nice front-end](https://alvinqingxing.github.io/chuck-norris-front/) at GitHub Pages to retrieve and display the jokes. The source code can be [viewed here](https://github.com/alvinqingxing/chuck-norris-front), but the essential part is this JavaScript code which generates a random number between 1 and 100 and retrieves the joke with that id from the API:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q5ke0pbomd6qp5levmc5.png)

Chuck Norris has the final word:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xvt8msfw1xvouw7gvfvl.png)

I found this to be a really fun project to do, and I hope it will inspire you to create your own API!