.../articles/
Handling Format in Rails API Mode Routing

Handling Format in Rails API Mode Routing

2020.10.14

Rails routing

For Rails routing, you can refer to the official documentation, and searching for something like "Rails routing" also turns up plenty of information.

Even when using API mode, the way you write routes is the same, so it doesn't cause any trouble—but I got curious again about how format is handled, so I looked into it.

The rest of this post assumes we're building an API server that handles JSON, and I verified behavior with the following versions:

  • ruby: 2.7.1
  • rails: 6.0.3.3

Setting format in routing

As a baseline, let's define the following routes.

Rails.application.routes.draw do
  namespace :v1 do
    resources :users
  end
end
# Output of rails routes (partially omitted)
# v1_users GET    /v1/users(.:format)     v1/users#index
#          POST   /v1/users(.:format)     v1/users#create
# v1_user  GET    /v1/users/:id(.:format) v1/users#show
#          PATCH  /v1/users/:id(.:format) v1/users#update
#          PUT    /v1/users/:id(.:format) v1/users#update
#          DELETE /v1/users/:id(.:format) v1/users#destroy

And let's say v1/users#show is defined as follows:

    def show
      render json: User.find(params[:id])
    end

Here's what happens when requesting in three different patterns. (Some header information is omitted.)

# Pattern 1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# Pattern 2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# Pattern 3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

Patterns 1 and 2 look fine, but in pattern 3, even though we requested .html, we still get a JSON response back. Since the action uses render json:, the controller returns JSON regardless of the format.

Specifying format (default scope)

Change the routing to namespace :v1, default: { format: 'json' } do.

Rails.application.routes.draw do
  namespace :v1, default: { format: 'json' } do
    resources :users
  end
end

Requesting the same three patterns produced the same results as when nothing was specified. However, when using the head method to return a response, the headers did change.

# Pattern 1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# Pattern 2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# Pattern 3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

Specifying format

Change the routing to namespace :v1, format: 'json' do.

Rails.application.routes.draw do
  namespace :v1, format: 'json' do
    resources :users
  end
end

Requesting the same three patterns, pattern 3 now raised a RoutingError and returned a 404.

# Pattern 1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# Pattern 2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"email":"email"}

# Pattern 3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 404 Not Found

# RoutingError raised

So which one should you use?

As mentioned in the article below that I referenced, for an API that handles JSON, using format: 'json'—the last option introduced in this post—seems like the best choice.

As in the example, it seems more desirable behavior-wise to return a 404 when .html or some other format is requested unintentionally (or maliciously).

If you want to allow other formats as an exception, you can override this by setting format on either the individual route or the block. It also seems possible to use a regular expression such as /json|yaml/.

written by

.../article/

Articles

All articles

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

From Firebase to Vercel, Contentful to microCMS — a migration log written with Claude Code

We moved our corporate site's hosting and CMS, and made it bilingual along the way. The constraints we only found by running against real data were more useful than the migration itself, so this post focuses on where we got stuck.

Can't Read POST Data with Firebase Functions × Remix?

Can't Read POST Data with Firebase Functions × Remix?

How to read POST data from a Remix action when running on Firebase Functions.

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Generative AI for Executives and Leaders: An Approach to Self-Driven DX

Building a structure where executives and leaders themselves can identify issues and evaluate solutions using generative AI. We introduce how combining this with our hands-on support dramatically improves both the quality and speed of digital transformation.

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Deploying a Monorepo Next.js App (App Router) to AWS Amplify

Notes on the obstacles we hit while deploying a Next.js app managed in a monorepo to AWS Amplify.

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Keeping Production Running Smoothly with Remote Work and Online Meetings [Documentation]

Many production companies have adopted remote work as a result of the pandemic, and we are one of them.

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

Designing an E-Commerce Site That Sells: How to Find Great Reference Examples

There is no single formula for e-commerce design that sells. Driving revenue requires a solid concept, and getting to that concept requires thorough research.

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

Productivity Tools We Recommend as a Production Company, Including Services That Work Well Solo

With remote work becoming the norm during the COVID-19 pandemic, our team now works from home most days of the week.

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We Released Thought Recorder, a Figma Plugin for Keeping a Commit History of Your Designs

We hope this helps web designers who work in Figma. Read on for how to use it.

How to Build an E-Commerce Site, and Which Platforms We Recommend

How to Build an E-Commerce Site, and Which Platforms We Recommend

Shopping online for fashion, appliances, and even groceries is now routine. With the pandemic accelerating the shift, we receive a steady stream of questions about which platform to use and how much it costs.

Generating FastAPI Schema Classes from OpenAPI

Generating FastAPI Schema Classes from OpenAPI

We chose FastAPI, a relatively modern framework, for a Python API project. FastAPI can generate an OpenAPI definition from your backend code, but here we do the opposite: generating FastAPI schema classes from an OpenAPI definition prepared in advance.

View all articles

Contact us