{"id":7387,"date":"2015-10-06T16:15:11","date_gmt":"2015-10-06T13:15:11","guid":{"rendered":"http:\/\/www.webcodegeeks.com\/?p=7387"},"modified":"2015-12-16T11:13:59","modified_gmt":"2015-12-16T09:13:59","slug":"building-json-api-rails-5","status":"publish","type":"post","link":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/","title":{"rendered":"Building a JSON API with Rails 5"},"content":{"rendered":"<p>Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality right out of the box. It\u2019s also great for building JSON APIs, but why include a whole bunch of functionality that we aren\u2019t going to use if what we want is to simply build a JSON API?<\/p>\n<p>That\u2019s where using Rails in <code>--api<\/code> mode comes in handy. It gives us the power of Rails but with only the functionality that we\u2019re actually going to need for our JSON API.<\/p>\n<p>In this article, we\u2019ll investigate how to take advantage of the <code>rails-api<\/code> gem, which, starting with Rails 5, now comes built in. We\u2019ll look at how to generate different types of JSON responses using ActiveModel::Serializer and how to cache our JSON serialization. Lastly, we\u2019ll look at how we can throttle requests to our API to avoid being taken down by abusive clients.<\/p>\n<h2>Using Rails in \u201cAPI\u201d mode<\/h2>\n<p>Before Rails 5, we could already use Rails in \u201cAPI\u201d mode, but we had to do so through a different gem, namely the <a href=\"https:\/\/github.com\/rails-api\/rails-api\">rails-api<\/a> gem. This past June, Santiago Pastorino opened up a <a href=\"https:\/\/github.com\/rails\/rails\/pull\/19832\">pull-request<\/a> (which has since been merged) requesting that rails-api be merged into core Rails. This change will be included when Rails 5 launches.<\/p>\n<h3>Using Rails 5 Right Now<\/h3>\n<p>It is possible to use Rails 5 right now, although you might want to proceed with caution since it isn\u2019t exactly ready for prime time. Here are the steps to take to use Rails 5 in API mode today:<\/p>\n<ol>\n<li>Ensure you have Ruby 2.2 or higher.<\/li>\n<li>Install Rails from master branch: <code>git clone git@github.com:rails\/rails.git<\/code><\/li>\n<li>Now it\u2019s time to generate a new Rails API application. We do that by passing the <code>--api<\/code> directive to the <code>rails new<\/code> command.<\/li>\n<\/ol>\n<p>Because we are using Rails 5 before it\u2019s officially ready, we\u2019re going to need to run this command from within the directory we just cloned Rails into. We\u2019ll also pass the <code>--edge<\/code> directive:<\/p>\n<pre class=\" brush:php\">bundle exec railties\/exe\/rails new &lt;parent-folder-path&gt;\/api_app_name --api --edge<\/pre>\n<p>Because we\u2019re installing Rails in <code>edge<\/code> mode, it will install a few gems into the Gemfile that we don\u2019t actually need. These are <code>sprockets-rails<\/code>, <code>sprockets<\/code>, and <code>sass-rails<\/code>. Leave them for now because <code>--edge<\/code> Rails seems to need them.<\/p>\n<p>Once Rails 5 officially launches, we\u2019ll simply need to run the <code>rails new --api<\/code> command, and it won\u2019t come with these \u201cview\u201d related gems.<\/p>\n<h3>What makes API mode different?<\/h3>\n<p>For the most part, what the API mode does is remove functionality that you don\u2019t actually need when building an API. This includes things like sessions, cookies, assets, and really anything related to making Rails work with a browser. It will also change the generators so that it won\u2019t generate views, helpers, and assets when generating a new resource.<\/p>\n<p>Specifically, when running <code>rake middleware<\/code> on both an \u2013api app and a normal app, the normal app includes the following middleware that the API doesn\u2019t:<\/p>\n<pre class=\" brush:php\">use #&lt;ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fa7511b02b0&gt;\r\nuse Rack::MethodOverride\r\nuse WebConsole::Middleware\r\nuse ActionDispatch::Cookies\r\nuse ActionDispatch::Session::CookieStore\r\nuse ActionDispatch::Flash<\/pre>\n<p>The difference can also be seen when you compare the <code>ApplicationController<\/code> on a web app versus an API app. The web version extends from <code>ActionController::Base<\/code>, whereas the API version extends from <code>ActionController::API<\/code>, which includes a much smaller subset of functionality.<\/p>\n<p>The web ApplicationController:<\/p>\n<pre class=\" brush:php\">class ApplicationController &lt; ActionController::Base\r\n  # Prevent CSRF attacks by raising an exception.\r\n  # For APIs, you may want to use :null_session instead.\r\n  protect_from_forgery with: :exception\r\nend<\/pre>\n<p>The API ApplicationController:<\/p>\n<pre class=\" brush:php\">class ApplicationController &lt; ActionController::API\r\nend<\/pre>\n<p>Let\u2019s generate some scaffolding for a <code>RentalUnit<\/code> model with the following command:<\/p>\n<pre class=\" brush:php\">bin\/rails g scaffold rental_unit address rooms:integer bathrooms:integer price_cents:integer<\/pre>\n<p>If we look at the folders and files that got created, we\u2019ll notice that it hasn\u2019t included any of the normal view files that get created when you generate scaffolding in a normal Rails app.<\/p>\n<pre class=\" brush:php\">invoke  active_record\r\ncreate    db\/migrate\/20150906194623_create_rental_units.rb\r\ncreate    app\/models\/rental_unit.rb\r\ninvoke    test_unit\r\ncreate      test\/models\/rental_unit_test.rb\r\ncreate      test\/fixtures\/rental_units.yml\r\ninvoke  resource_route\r\n route    resources :rental_units\r\ncreate  app\/serializers\/rental_unit_serializer.rb\r\ninvoke  scaffold_controller\r\ncreate    app\/controllers\/rental_units_controller.rb\r\ninvoke    test_unit\r\ncreate      test\/controllers\/rental_units_controller_test.rb<\/pre>\n<h2>Responding with JSON<\/h2>\n<p>It\u2019s possible that you want to respond with XML, but it\u2019s a fairly safe assumption that you\u2019re going to be responding with JSON. This is where Rails API shines. Before we get to how to respond easily with JSON, let\u2019s talk about which format our JSON should be in.<\/p>\n<h3>JSON formats<\/h3>\n<p>Should we include a root node when responding with multiple objects? What about with single objects? Where do we put meta information, and how do we embed nested or related data?<\/p>\n<p>There are a ton of other questions like these that you can ask (and argue about) when trying to decide how the JSON should look but, luckily, there are standards you can follow. <a href=\"http:\/\/jsonapi.org\/\">json:api<\/a>, popularized by the Ember (and Rails) community, seems to be winning the battle right now and can serve as an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Parkinson%27s_law_of_triviality\">anti-bikeshedding<\/a> tool.<\/p>\n<p>The JSON serializer we\u2019ll be using in these examples allows us to easily respond in this format.<\/p>\n<h3>Serializing our JSON responses<\/h3>\n<p><a href=\"https:\/\/github.com\/rails-api\/active_model_serializers\">ActiveModel::Serializer<\/a> is included by default in your Gemfile when you create an application using the <code>--api<\/code> directive.<\/p>\n<p>ActiveModel::Serializer allows you to define which attributes and relationships you would like to include in your JSON response. It also acts as a presenter where you can define custom methods to display extra information or override how it\u2019s displayed in your JSON.<\/p>\n<p>The data we\u2019re working with for this article has two models: a RentalUnit and a User, where the RentalUnit belongs to a User, and the User has many Rental Units.<\/p>\n<p>In our RentalUnit serializer, we define which attributes to include in the response. Attributes can be anything that the RentalUnit object responds to. <code>price<\/code> and <code>price_per_room<\/code> aren\u2019t real attributes but are methods that we\u2019ve defined in the model. Here we can also say that we want to include the User data along with the RentalUnit by using the <code>belongs_to<\/code> method, much like in Active Record.<\/p>\n<pre class=\" brush:php\">class RentalUnitSerializer &lt; ActiveModel::Serializer\r\n  attributes :id, :address, :rooms, :bathrooms, :price, :price_per_room\r\n  belongs_to :user\r\nend<\/pre>\n<p>In our User serializer, we\u2019ll actually override how the <code>name<\/code> field is displayed in the JSON response. For the sake of privacy, we don\u2019t want to include the full name and are only going to include the first initial followed by their last name. This simple example will fail for Prince, Madonna, and other single-name celebrities, but it\u2019ll do for now. We have access to the <code>object<\/code> which is whatever object you are currently serializing.<\/p>\n<pre class=\" brush:php\">class UserSerializer &lt; ActiveModel::Serializer \r\n  attributes :id, :name, :email\r\n\r\n  def name \r\n    names = object.name.split(\" \") \r\n    \"#{names[0].first}. #{names[1][1]}\" \r\n  end \r\nend<\/pre>\n<p>If we make a request to <code>\/rental_units\/1<\/code>, we\u2019ll get a JSON response like this:<\/p>\n<pre class=\" brush:php\">{\"id\":1,\"address\":\"460 Jane St.\",\"rooms\":2,\"bathrooms\":2,\"price\":900.0,\"price_per_room\":450.0,\"user\":{\"id\":1,\"name\":\"A. Serna\",\"email\":\"email1@sample.com\"}}<\/pre>\n<p>You\u2019ll notice that by default it isn\u2019t in the json:api format. To change it to this format, we simply have to declare an initializer to tell ActiveModel::Serializer how to serialize the JSON data.<\/p>\n<pre class=\" brush:php\"># config\/initializers\/active_model_serializer.rb\r\nActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonApi<\/pre>\n<p>If we refresh the page (you may have to restart your Rails server after making this change), you\u2019ll see that the data is now in the json:api format.<\/p>\n<pre class=\" brush:php\">{\"data\":{\"id\":\"1\",\"type\":\"rental_units\",\"attributes\":{\"address\":\"460 Jane St.\",\"rooms\":2,\"bathrooms\":2,\"price\":900.0,\"price_per_room\":450.0},\"relationships\":{\"user\":{\"data\":{\"type\":\"users\",\"id\":\"1\"}}}}}<\/pre>\n<h2>Caching<\/h2>\n<p>Caching is important when dealing with APIs, especially if your API is read-heavy, the data doesn\u2019t change very often, or the responses are slow to generate.<\/p>\n<p>If the accuracy of your data doesn\u2019t need to be perfect \u2014 meaning you can afford to serve data that\u2019s a few seconds out of date (or when your data doesn\u2019t change much after its creation) \u2014 one of the best ways to speed up your site is to avoid a request hitting your Rails application at all. Using something like <a href=\"https:\/\/www.varnish-cache.org\/\">Varnish Cache<\/a>, a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Reverse_proxy\">reverse proxy<\/a> server can serve cached versions of your content based on different <a href=\"https:\/\/en.wikipedia.org\/wiki\/Time_to_live\">TTLs<\/a>. The fastest work is the work that doesn\u2019t need to be done.<\/p>\n<p>With ActiveModel::Serializer (AMS), you get full-object caching and fragment caching. These types of caching will not save you time making SQL queries but will save you time if serializing your objects is a time-consuming process. Things that can make it time consuming include objects that have a ton of fields to serialize or if certain fields are costly to generate. Hitting the database is still required because normally cache expiration is done based on the database ID of the object as well as its updated_at field. This can of course be overridden, but I\u2019ve found that it is generally the best approach.<\/p>\n<h3>Implementing caching<\/h3>\n<p>To use caching with AMS, you\u2019ll first need to decide what cache store your Rails application will use. Popular choices are <a href=\"http:\/\/memcached.org\/\">Memcached<\/a> or <a href=\"http:\/\/redis.io\/\">Redis<\/a>. For this example, we\u2019ll be using Memcached.<\/p>\n<p>The first step is to set which cache store we\u2019ll be using. Next, we\u2019ll turn on caching in the development environment so that we can see its results. This is done in the config\/environments\/development.rb file. Be careful where you put it because in development mode, Rails includes code in this file to disable caching. We\u2019re going to override this so that we can test it out locally. Alternatively, you could launch your site locally in <code>production<\/code> mode.<\/p>\n<pre class=\" brush:php\"># config\/environments\/development.rb\r\nconfig.cache_store = :mem_cache_store, \"localhost\" config.action_controller.perform_caching = true<\/pre>\n<p>You\u2019ll also need to include the <code>dalli<\/code> gem to your Gemfile which is used to talk to Memcached.<\/p>\n<pre class=\" brush:php\">gem 'dalli', '~&gt; 2.7.4'<\/pre>\n<p>The last step is to tell our serializer what to cache. To our rental_unit_serializer.rb file, we\u2019ll add the following line:<\/p>\n<pre class=\" brush:php\">class RentalUnitSerializer &lt; ActiveModel::Serializer\r\n  cache key: 'rental_unit'\r\n  attributes :id, :address, :rooms, :bathrooms, :price, :price_per_room\r\n  belongs_to :user\r\nend<\/pre>\n<p>This will end up caching the entire serialization of the object. For fragment caching, you can pass the options <code>only<\/code> or <code>except<\/code> to have more fine-grained control over what is cached and what isn\u2019t.<\/p>\n<p>By making these changes, we\u2019ve changed our response time from 30ms to 50ms\u2026 wait, what? Yes, you heard me right. By adding cache, responses in my application have actually slowed down.<\/p>\n<h3>Caching quagmires<\/h3>\n<p>I\u2019ll be honest that this wasn\u2019t the result I was expecting. The whole reason you add cache to your application is to speed up response time, not slow it down. So I did two things: I reached out to one of the core contributors (and the person who worked on caching) of AMS and asked what his opinion was, and I also broke out some flame graphs to see what was happening inside of my application.<\/p>\n<p>Because the application I am testing with isn\u2019t exactly \u201creal-world\u201d (there are only about six fields that I am serializing), it seems faster to actually just regenerate the JSON rather than to go and fetch the cached version of the response.<\/p>\n<p>By looking at the flame graph with caching turned on, I could tell that 48 percent of the time was spent in the <code>cache_check<\/code> method or farther down in the stack trace. This seems to account for the slowdown from 30ms to 50ms.<\/p>\n<pre class=\" brush:php\">active_model_serializers-258f116c3cf5\/lib\/active_model\/serializer\/adapter.rb:110:in `cache_check'   (48 samples - 48.00%)<\/pre>\n<p>Here\u2019s an image of the flamegraph, which was produced by using <a href=\"https:\/\/github.com\/MiniProfiler\/rack-mini-profiler\">rack mini profiler<\/a> gem with the <a href=\"https:\/\/github.com\/SamSaffron\/flamegraph\">flamegraph<\/a> gem. I\u2019ve highlighted in black the portion that\u2019s dealing with the cache.<\/p>\n<p><a href=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/Flame-graph-with-cache-with-box.jpg\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-7439\" src=\"http:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/Flame-graph-with-cache-with-box.jpg\" alt=\"Flame-graph-with-cache-with-box\" width=\"885\" height=\"396\" srcset=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/Flame-graph-with-cache-with-box.jpg 885w, https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/09\/Flame-graph-with-cache-with-box-300x134.jpg 300w\" sizes=\"(max-width: 885px) 100vw, 885px\" \/><\/a><\/p>\n<h3>Support for Russian Doll caching<\/h3>\n<p>At the moment <a href=\"http:\/\/edgeguides.rubyonrails.org\/caching_with_rails.html#russian-doll-caching\">Russian Doll caching<\/a> isn\u2019t supported with AMS. There is good news, though! Jo\u00e3o Moura <a href=\"https:\/\/medium.com\/@joaomdmoura\/the-future-of-ams-e5f9047ca7e9\">wrote about the current state and future of AMS<\/a> and mentioned that Russian Doll caching is planned. He\u2019s hopeful that it will be ready before Rails 5 is released.<\/p>\n<p>Another option if you would like to use Russian Doll caching now is to forgo using AMS and use <a href=\"https:\/\/github.com\/rails\/jbuilder\">Jbuilder<\/a> to generate your JSON responses. Jbuilder supports Russian Doll caching, although if you want to also respond using the json:api format, you\u2019ll have to craft the correctly formatted response yourself.<\/p>\n<h2>Rate Limiting \/ Throttling<\/h2>\n<p>One important thing to think about when you have an API is to be able to limit the amount of requests that can be made. This is important either to be able to stop abuse or perhaps based on the amount of access that your users have paid for (free users get fewer requests per hour than paid users).<\/p>\n<h3>Middleware to the rescue<\/h3>\n<p>To help us with rate limiting, we\u2019ll turn to Rack <a href=\"http:\/\/guides.rubyonrails.org\/rails_on_rack.html\">middleware<\/a>. Middleware is essentially how Rails (and other web frameworks) hook into Rack. The request flows through a stack of middleware objects calling <code>call<\/code> on each one, passing the request along, allowing each middleware to either modify it and call the next middleware in the stack, or to halt the request by returning a response.<\/p>\n<p>The middleware we will be using will be slotted into the middleware stack before we get to the main Rails middleware. It will allow us to look at the request, determine if we will allow it to continue to our API and be processed normally, or if we want to deny the request because the user has reached their allowed limit.<\/p>\n<h3>Rack::Attack<\/h3>\n<p><a href=\"https:\/\/github.com\/kickstarter\/rack-attack\">Rack::Attack<\/a> is a gem released by Kickstarter to help throttle website (or API) usage. It is middleware that allows us to:<\/p>\n<ul>\n<li><em>whitelist<\/em>: Allowing it to process normally if certain conditions are true<\/li>\n<li><em>blacklist<\/em>: Sending a denied message instantly for certain requests<\/li>\n<li><em>throttle<\/em>: Checking if the user is within their allowed usage<\/li>\n<li><em>track<\/em>: Tracking this request to be able to log certain information about our requests<\/li>\n<\/ul>\n<h3>Usage<\/h3>\n<p>Rack::Attack is installed with just a few steps. The first step is to add it to your Gemfile:<\/p>\n<pre class=\" brush:php\"># Gemfile\r\ngem 'rack-attack'<\/pre>\n<p>Once you\u2019ve got it in your Gemfile, you\u2019ll want to insert it into your middleware stack. This is done in the config\/application.rb file.<\/p>\n<pre class=\" brush:php\"># config\/application.rb\r\nconfig.middleware.use Rack::Attack<\/pre>\n<p>Rack::Attack by default is configured to use the Rails.cache, but we can override that if we want by setting the <code>Rack::Attack.cache.store<\/code> value in our <code>Rack::Attack<\/code> class.<\/p>\n<pre class=\" brush:php\"># initializers\/rack_attack.rb\r\nclass Rack::Attack \r\n  Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new \r\nend<\/pre>\n<h3>Deciding what to throttle on<\/h3>\n<p>There are many things we can throttle on. We can throttle by IP address, by API key, control it per path or action, etc. All of these things are done within the <code>throttle<\/code> call that goes inside of our initializer.<\/p>\n<p>Here\u2019s a simple setup which will allow an IP address to make 10 requests every 10 seconds.<\/p>\n<pre class=\" brush:php\">throttle('req\/ip', limit: 10, period: 10) do |req|\r\n  req.ip\r\nend<\/pre>\n<p>Once the user reaches the limit, we\u2019re going to want to respond to them that they will need to retry once their threshold period starts over again. By default, it will respond with <code>text\/html<\/code>, but since we\u2019re building a JSON API, we can override that to respond with a custom message and the correct mime type.<\/p>\n<p>The message conforms to the Rack standard of a callable object (in this case a lambda) which returns an array with three values: HTTP response code, a hash of HTTP headers, and an array of strings which is the body of the response.<\/p>\n<pre class=\" brush:php\">self.throttled_response = -&gt;(env) {\r\n  retry_after = (env['rack.attack.match_data'] || {})[:period]\r\n  [\r\n    429,\r\n    {'Content-Type' =&gt; 'application\/json', 'Retry-After' =&gt; retry_after.to_s},\r\n    [{error: \"Throttle limit reached. Retry later.\"}.to_json]\r\n  ]\r\n}<\/pre>\n<p>We are also able to whitelist certain requests. Maybe everyone no matter what can access a certain path, or requests made from a special internal IP address are always allowed through.<\/p>\n<pre class=\" brush:php\">whitelist('allow-localhost') do |req|\r\n  '127.0.0.1' == req.ip || '::1' == req.ip\r\nend<\/pre>\n<p>If we take a look at the finished Rack::Attack code, it looks like this:<\/p>\n<pre class=\" brush:php\">class Rack::Attack\r\n\r\n  Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new\r\n\r\n  whitelist('allow-localhost') do |req| \r\n    '127.0.0.1' == req.ip || '::1' == req.ip \r\n  end\r\n\r\n  throttle('req\/ip', limit: 10, period: 10) do |req| \r\n    req.ip \r\n  end\r\n\r\n  self.throttled_response = -&gt;(env) { \r\n    retry_after = (env['rack.attack.match_data'] || {})[:period] \r\n    [ \r\n      429, \r\n      {'Content-Type' =&gt; 'application\/json', 'Retry-After' =&gt; retry_after.to_s}, \r\n      [{error: \"Throttle limit reached. Retry later.\"}.to_json] \r\n    ] \r\n  }\r\n\r\nend<\/pre>\n<h3>Tracking API access with Rack::Attack<\/h3>\n<p>There are a couple ways we can track our API access using Rack::Attack. When Rack::Attack either throttles or tracks requests to the API, it will instrument a notification using the <code>ActiveSupport::Notifications<\/code> interface.<\/p>\n<p>We can then set up subscribers to those events and choose how we want to handle them. In this case, we want to just log using <code>Rails.logger<\/code> when someone triggers a throttled access attempt. This way we can use something like Splunk to query against our Rails logs.<\/p>\n<p>I\u2019ve chosen to create an initializer file where I can put the notification subscriptions. Here I can subscribe to the <code>rack.atack<\/code> notifications and check what type of match was triggered. In this case, I\u2019m looked for throttled events. I also have access to the request object, allowing me to pull out the user\u2019s IP address.<\/p>\n<pre class=\" brush:php\"># initializers\/notifications.rb\r\nActiveSupport::Notifications.subscribe(\"rack.attack\") do |name, start, finish, request_id, req| \r\n  if req.env['rack.attack.match_type'] == :throttle \r\n    Rails.logger.info \"Throttled IP: #{req.ip}\" \r\n  end \r\nend<\/pre>\n<h2>Summary<\/h2>\n<p>In this article, we took a look at how we can take advantage of the ability to create an API-specific Rails app which will be available to us starting in Rails 5. You can still use Rails as an API now, but you will have to include a separate <code>rails-api<\/code> gem. You also won\u2019t be able to create it with the <code>rails new --api<\/code> command.<\/p>\n<p>We also looked at how we can respond using the <code>json:api<\/code> format using <code>ActiveModel::Serializer<\/code>. Then we looked at how we can cache our JSON serialization in AMS and how to throttle abusive clients from overloading our API (and ruining performance for the rest of our users).<\/p>\n<p>Bottom line: Rails API gives us the full power of Rails without the bloat of unnecessary functionality.<\/p>\n<div class=\"attribution\">\n<table>\n<tbody>\n<tr>\n<td><span class=\"reference\">Reference: <\/span><\/td>\n<td><a href=\"http:\/\/blog.codeship.com\/building-a-json-api-with-rails-5\/\">Building a JSON API with Rails 5<\/a> from our <a href=\"http:\/\/www.webcodegeeks.com\/wcg\/\">WCG partner<\/a> Florian Motlik at the <a href=\"http:\/\/blog.codeship.com\/\">Codeship Blog<\/a> blog.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality right out of the box. It\u2019s also great for building JSON APIs, but why include a whole bunch of functionality that we aren\u2019t going to use if what we want is to simply build a JSON &hellip;<\/p>\n","protected":false},"author":113,"featured_media":4127,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[95],"class_list":["post-7387","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ruby","tag-rails"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Building a JSON API with Rails 5 - Web Code Geeks - 2026<\/title>\n<meta name=\"description\" content=\"Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a JSON API with Rails 5 - Web Code Geeks - 2026\" \/>\n<meta property=\"og:description\" content=\"Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/\" \/>\n<meta property=\"og:site_name\" content=\"Web Code Geeks\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webcodegeeks\" \/>\n<meta property=\"article:published_time\" content=\"2015-10-06T13:15:11+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2015-12-16T09:13:59+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"150\" \/>\n\t<meta property=\"og:image:height\" content=\"150\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Leigh Halliday\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@leighchalliday\" \/>\n<meta name=\"twitter:site\" content=\"@webcodegeeks\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Leigh Halliday\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"16 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/\"},\"author\":{\"name\":\"Leigh Halliday\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b\"},\"headline\":\"Building a JSON API with Rails 5\",\"datePublished\":\"2015-10-06T13:15:11+00:00\",\"dateModified\":\"2015-12-16T09:13:59+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/\"},\"wordCount\":2621,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"keywords\":[\"Rails\"],\"articleSection\":[\"Ruby\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/\",\"name\":\"Building a JSON API with Rails 5 - Web Code Geeks - 2026\",\"isPartOf\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"datePublished\":\"2015-10-06T13:15:11+00:00\",\"dateModified\":\"2015-12-16T09:13:59+00:00\",\"description\":\"Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality\",\"breadcrumb\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg\",\"width\":150,\"height\":150},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.webcodegeeks.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Ruby\",\"item\":\"https:\/\/www.webcodegeeks.com\/category\/ruby\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Building a JSON API with Rails 5\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#website\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"name\":\"Web Code Geeks\",\"description\":\"Web Developers Resource Center\",\"publisher\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.webcodegeeks.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#organization\",\"name\":\"Exelixis Media P.C.\",\"url\":\"https:\/\/www.webcodegeeks.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"contentUrl\":\"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png\",\"width\":864,\"height\":246,\"caption\":\"Exelixis Media P.C.\"},\"image\":{\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webcodegeeks\",\"https:\/\/x.com\/webcodegeeks\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b\",\"name\":\"Leigh Halliday\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g\",\"caption\":\"Leigh Halliday\"},\"description\":\"Leigh is a developer at theScore. He writes about Ruby, Rails, and software development on his personal site.\",\"sameAs\":[\"http:\/\/www.leighhalliday.com\/\",\"https:\/\/x.com\/leighchalliday\"],\"url\":\"https:\/\/www.webcodegeeks.com\/author\/leigh-halliday\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a JSON API with Rails 5 - Web Code Geeks - 2026","description":"Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/","og_locale":"en_US","og_type":"article","og_title":"Building a JSON API with Rails 5 - Web Code Geeks - 2026","og_description":"Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality","og_url":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/","og_site_name":"Web Code Geeks","article_publisher":"https:\/\/www.facebook.com\/webcodegeeks","article_published_time":"2015-10-06T13:15:11+00:00","article_modified_time":"2015-12-16T09:13:59+00:00","og_image":[{"width":150,"height":150,"url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","type":"image\/jpeg"}],"author":"Leigh Halliday","twitter_card":"summary_large_image","twitter_creator":"@leighchalliday","twitter_site":"@webcodegeeks","twitter_misc":{"Written by":"Leigh Halliday","Est. reading time":"16 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#article","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/"},"author":{"name":"Leigh Halliday","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b"},"headline":"Building a JSON API with Rails 5","datePublished":"2015-10-06T13:15:11+00:00","dateModified":"2015-12-16T09:13:59+00:00","mainEntityOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/"},"wordCount":2621,"commentCount":0,"publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","keywords":["Rails"],"articleSection":["Ruby"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/","url":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/","name":"Building a JSON API with Rails 5 - Web Code Geeks - 2026","isPartOf":{"@id":"https:\/\/www.webcodegeeks.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage"},"image":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage"},"thumbnailUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","datePublished":"2015-10-06T13:15:11+00:00","dateModified":"2015-12-16T09:13:59+00:00","description":"Rails is great for making traditional server-rendered web applications. It has support for cookies, sessions, and other browser-specific functionality","breadcrumb":{"@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#primaryimage","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2015\/04\/rubyonrails-logo.jpg","width":150,"height":150},{"@type":"BreadcrumbList","@id":"https:\/\/www.webcodegeeks.com\/ruby\/building-json-api-rails-5\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.webcodegeeks.com\/"},{"@type":"ListItem","position":2,"name":"Ruby","item":"https:\/\/www.webcodegeeks.com\/category\/ruby\/"},{"@type":"ListItem","position":3,"name":"Building a JSON API with Rails 5"}]},{"@type":"WebSite","@id":"https:\/\/www.webcodegeeks.com\/#website","url":"https:\/\/www.webcodegeeks.com\/","name":"Web Code Geeks","description":"Web Developers Resource Center","publisher":{"@id":"https:\/\/www.webcodegeeks.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.webcodegeeks.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.webcodegeeks.com\/#organization","name":"Exelixis Media P.C.","url":"https:\/\/www.webcodegeeks.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/","url":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","contentUrl":"https:\/\/www.webcodegeeks.com\/wp-content\/uploads\/2022\/06\/exelixis-logo.png","width":864,"height":246,"caption":"Exelixis Media P.C."},"image":{"@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webcodegeeks","https:\/\/x.com\/webcodegeeks"]},{"@type":"Person","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/e496b17f78cdca27723b8e225dc6ab6b","name":"Leigh Halliday","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.webcodegeeks.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/bd40251a1acc424c292c35a3485264a801efa20efa7063c3e320a0a354ddafac?s=96&d=mm&r=g","caption":"Leigh Halliday"},"description":"Leigh is a developer at theScore. He writes about Ruby, Rails, and software development on his personal site.","sameAs":["http:\/\/www.leighhalliday.com\/","https:\/\/x.com\/leighchalliday"],"url":"https:\/\/www.webcodegeeks.com\/author\/leigh-halliday\/"}]}},"_links":{"self":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/7387","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/users\/113"}],"replies":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/comments?post=7387"}],"version-history":[{"count":0,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/posts\/7387\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media\/4127"}],"wp:attachment":[{"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/media?parent=7387"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/categories?post=7387"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.webcodegeeks.com\/wp-json\/wp\/v2\/tags?post=7387"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}