Skip to content

Instantly share code, notes, and snippets.

@kohheepeace
Last active October 15, 2024 16:59
Show Gist options
  • Save kohheepeace/c551a4d97e0cf7748465a8321c1bd7dd to your computer and use it in GitHub Desktop.
Save kohheepeace/c551a4d97e0cf7748465a8321c1bd7dd to your computer and use it in GitHub Desktop.
Rails ajax comparison (fetch, Rails.ajax, axios, @rails/request.js, Turbo)

Rails ajax comparison (fetch, Rails.ajax, axios, @rails/request.js, Turbo)

I wrote this gist because I felt that the Rails documentation was lacking a description of ajax requests.

πŸ“Œ Options for ajax request

There are various ways to send ajax requests in Rails.

  1. Browser default Fetch API
  2. Rails.ajax (No Official docs and request for docs)
  3. http client like axios
  4. @rails/request.js πŸ‘ˆ I'm using this one now !
  5. Turbo πŸ‘ˆ I'm using this one now !

1. Fetch API

[pros]:

  • Browser default http client, so no need to install something.

[cons]:

[Usage]:

const csrfToken = document.getElementsByName("csrf-token")[0].content;

fetch("/posts", {
  method: "POST",
  headers: {
    "X-CSRF-Token": csrfToken,          // πŸ‘ˆπŸ‘ˆπŸ‘ˆ you need to set token
    "Content-Type": "application/json", // πŸ‘ˆπŸ‘ˆπŸ‘ˆ To send json in body, specify this
     Accept: "application/json",         // πŸ‘ˆπŸ‘ˆπŸ‘ˆ Specify the response to be returned as json. For api only mode, this may not be needed
  },
  body: JSON.stringify({ title, body }),
})
  .then((response) => response.json())
  .then((data) => {
    console.log("Success:", data);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

Ref: official docs about how to get csrf token

2. Rails.ajax

source code

[pros]:

  • No need to set csrf token manually
  • Code become short
  • Turbolikns support

[cons]:

  • No official docs
  • you need to send data as URL scheme

[Usage]:

Rails.ajax({
  url: "/posts", // or "/posts.json"
  type: "POST",
  data: `post[title]=${title}&post[body]=${body}`, // πŸ‘ˆπŸ‘ˆπŸ‘ˆ You need to pass data in URL scheme
  success: function (data) {
    console.log("Success:", data);
  },
  error: function (error) {
    console.error("Error:", error);
  },
});

In app/controllers/posts_controller.rb

# POST /posts or /posts.json
  def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: "Post was successfully created." }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

In console, response of /posts will look like this.

Success: Turbolinks.clearCache()
Turbolinks.visit("http://localhost:3000/posts/rHBcQ9K-4bssCxz7VB4VXw", {"action":"replace"})

In console, response of /posts.json will look like this.

Success: {id: 139, title: "This is a title", body: "This is a body", user_id: 1, created_at: "2021-06-16T23:28:53.563Z", …}

3. axios

axios is better version of Fetch API (πŸ‘ˆ just a opinion). You can write api fetch code cleaner way.

[pros]:

  • Widely used
  • Clean code

[cons]:

[Usage]:

  1. Create custom axios file src/custom-axios.js
import axios from "axios";

const instance = axios.create({
	headers: {
		"Content-Type": "application/json",
		Accept: "application/json",
	},
});

instance.interceptors.request.use(
	function (config) {
		const csrfToken = document.getElementsByName("csrf-token")[0].content;
		config.headers["X-CSRF-Token"] = csrfToken;

		return config;
	},
	function (error) {
		// Do something with request error
		return Promise.reject(error);
	}
);

export default instance;
  1. In js file where you want to call api some-js-file.js
import axios from "../src/custom-axios";

...

axios
  .post("/posts", { title, editorData })
  .then((data) => {
    console.log("Success:", data);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

Refs of axios

Similar http client like axios

4. @rails/request.js

Source

Rails Request.JS encapsulates the logic to send by default some headers that are required by rails applications like the X-CSRF-Token. https://github.com/rails/requestjs-rails

[pros]:

[cons]:

  • Not yet found...

[Usage]:

yarn add @rails/request.js
import { FetchRequest } from '@rails/request.js'

window.FetchRequest = FetchRequest; // πŸ‘ˆ I'm asigning it to window.

const request = new FetchRequest('post',
  'localhost:3000/my_endpoint',
  { body: JSON.stringify({ name: 'Request.JS' }) }
)
const response = await request.perform()
if (response.ok) {
  const body = await response.text
  // Do whatever do you want with the response body
  // You also are able to call `response.html` or `response.json`, be aware that if you call `response.json` and the response contentType isn't `application/json` there will be raised an error.
}

*There is a gem of Request.JS for Rails for Asset Pipeline.

5. Turbo

[Source]

By using Turbo, you don't need to write Javascript, but it works like javascript.

πŸ‘‡ The following Todo apps, which normally require javascript, can be implemented without (or with little) need to write Javascript by using Turbo.

*Credit: https://www.colby.so/posts/turbo-rails-101-todo-list

[Pros]:

  • You don't need to write (much) javascript

[Cons]:

  • Unfamiliar syntax, grammer
  • For simple processes, it may be simpler to write javascript as usual.

βœ… Which one should I use?

In this issue, rails team member encourage you to use fetch api, so I decided to use fetch api. rails/rails#38191 (comment)

I decided to use Rails.ajax because I wanted to use Turbulinks. I know that both Rails.ajax and Turbolinks are supposed to be deprecated, but it's easier to use this method for now, and I felt it would be easy to rewrite even if they were deprecated.

I'm using axios now. There is a cons of axios bundle size, however, I chose this one because it gives me the feeling that I am writing clean code.

I am currently using both Turbo and @rails/request.js.

Turbo:

  • Search modals like Algoria (with complex UI changes).

@rails/request.js:

  • Requests to a very simple backend, like "Fav" functionality

πŸ“Œ About CSRF

CSRF tokens are required if cookies or sessions are used. https://security.stackexchange.com/questions/166724/should-i-use-csrf-protection-on-rest-api-endpoints/166798#166798

πŸ“Œ Refs

@sergioro9
Copy link

Thanks for the article. What is "Fav" functionality?

@Coridyn
Copy link

Coridyn commented Apr 27, 2024

FYI: It is possible to send a JSON-encoded body with Rails.ajax().

This avoids the issue in your note data: ..., // πŸ‘ˆπŸ‘ˆπŸ‘ˆ You need to pass data in URL scheme and lets you pass more complex JSON data structures.

The trick is to set the Content-Type: application/json header and JSON-encode the data property.

My solution is to use a custom dataType: "json" property and beforeSend() handler to control this (so I can choose between JSON or form-encoded requests).

Here is an example:

Rails.ajax({
  url: "/posts", // or "/posts.json"
  type: "POST",
  data: { title, body },
  dataType: "json",
  beforeSend: (xhr, options) => {
    if (options.dataType === "json"){
      xhr.setRequestHeader("Content-Type", "application/json");
      if (typeof options.data !== "undefined"){
          options.data = JSON.stringify(options.data);
      }
    }
    return true;
  },
  success: function (data) {
    console.log("Success:", data);
  },
  error: function (error) {
    console.error("Error:", error);
  },
});

It's also possible to add a global beforeSend handler to avoid needing beforeSend on every request object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment