Membedah API dengan Rails: rails new --api untuk aplikasi tanpa view, serialization dengan jbuilder, versioning API, autentikasi token untuk SPA & mobile, dan praktik membangun JSON API

Setelah di episode 14 kalian membangun realtime, episode 15 membahas sisi Rails yang memisahkan frontend dari backend: API-only. Tidak semua aplikasi memakai view Rails — SPA dengan React/Vue, aplikasi mobile, dan integrasi pihak ketiga butuh JSON API yang bersih dan ter-versioned.
Mengapa episode ini penting? Karena API adalah bahasa publik aplikasi kalian. Sekali dipakai konsumen (aplikasi mobile, partner), breaking change bisa sangat mahal. Episode ini mengajarkan cara membangun Rails API dengan benar sejak awal: struktur API-only, serialization yang terkendali, versioning, dan autentikasi token.
API-only Rails menghilangkan lapisan view dan browser:
rails new blog_api --api --database=postgresql
cd blog_apiOpsi --api menghasilkan project yang: tidak memuat Asset Pipeline, tidak memuat view/helper defaults, menggunakan middleware API (JSON parsing, tanpa session cookies), dan controller mewarisi ActionController::API. Hasilnya: aplikasi ringan yang hanya melayani JSON.
Note
--api bukan berarti "tidak bisa menambah view nanti". Kalian bisa menambahkan full Rails ke project API kapan pun. Pilih --api saat frontend memang terpisah (SPA, mobile) — bukan karena menganggap view itu buruk.
Controller API tidak me-render ERB, melainkan JSON. Cara default Rails: template jbuilder — file dengan ekstensi .json.jbuilder:
def index
@posts = Post.published.includes(:user).limit(20)
endjson.array! @posts do |post|
json.extract! post, :id, :title, :created_at
json.author post.user.name
json.comments_count post.comments.size
endKelebihan jbuilder: kontrol penuh atas bentuk JSON — kalian memilih field mana yang diekspos (jangan sampai password_digest bocor). Controller cukup membuat instance variable; Rails otomatis memilih template .json.jbuilder sesuai format response.
API yang dipakai publik harus di-version. Standar: versi di URL path (/api/v1/...). Rails menyediakan namespace yang cocok untuk ini:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :posts, only: %i[index show]
end
end
endMenghasilkan GET /api/v1/posts → Api::V1::PostsController. Struktur folder:
app/controllers/api/v1/posts_controller.rbmodule Api
module V1
class PostsController < ApplicationController
def index
@posts = Post.published.includes(:user).limit(20)
render json: { posts: @posts.map { |p| PostSerializer.new(p).call } }
end
end
end
endAturan emas versioning: versi lama tetap berjalan. Saat menambah breaking change, buat v2 dan biarkan v1 hidup sampai konsumen bermigrasi. Inilah alasan versioning di path dipakai hampir semua API publik.
SPA dan mobile tidak memakai session cookie browser dengan mulus — mereka memakai token. Pola paling sederhana dan aman: token per-user yang di-hash di database.
bin/rails g migration AddAuthTokenToUsers auth_token:stringclass User < ApplicationRecord
has_secure_password
before_create :generate_auth_token
private
def generate_auth_token
self.auth_token = SecureRandom.hex(32)
end
endSecureRandom.hex(32) menghasilkan token acak 64 karakter. Client mengirim token di header Authorization: Bearer <token>:
class ApplicationController < ActionController::API
before_action :authenticate_user
private
def authenticate_user
token = request.headers["Authorization"]&.split(" ")&.last
@current_user = User.find_by(auth_token: token)
render json: { error: "Unauthorized" }, status: :unauthorized unless @current_user
end
endPenyimpanan token di database (bukan JWT stateless) memberi kemampuan revoke instan: saat user logout atau token dicuri, cukup hapus/null-kan auth_token. JWT tetap opsi untuk kasus stateless (SSO antar service) dengan tradeoff: token tidak bisa dicabut sebelum kedaluwarsa.
Untuk integrasi pihak ketiga atau SSO, ekosistem menyediakan:
jwt gem) — token self-contained untuk mikroservice.Untuk API internal SPA kalian sendiri, token database sederhana sering sudah cukup — hindari kompleksitas sebelum dibutuhkan.
Beberapa keputusan yang membuat API kalian profesional:
{ "data": [...] } agar mudah menambah metadata (pagination) tanpa breaking.{ "error": { "code": "...", "message": "..." } } untuk semua status error.page/per_page params + header Link atau metadata { "total": 100, "page": 1 }.snake_case cocok untuk konsumen internal.def index
posts = Post.published.includes(:user)
.page(params[:page]).per(params[:per_page] || 20)
render json: {
data: posts.map { |p| PostSerializer.new(p).call },
meta: { page: posts.current_page, total: posts.total_count }
}
endpassword_digest di JSON — gunakan jbuilder/serializer yang memilih field; jangan render json: user.Digest::SHA256) agar kebocoran DB tidak memberi token valid.include untuk relasi — N+1 pada serialization; gunakan includes dan serializer yang memakai data yang sudah dimuat.render json: murni, tidak mencampur template.Episode 15 membekali kalian Rails API: rails new --api untuk aplikasi ringan tanpa view, jbuilder untuk serialization terkendali, namespace api/v1 untuk versioning, autentikasi token per-user yang bisa di-revoke, dan kebiasaan envelope/error/pagination yang membuat API profesional.
Inti yang harus dibawa pulang:
--api menghilangkan view/asset; controller mewarisi ActionController::API.namespace :api { namespace :v1 }; versi lama tetap hidup.SecureRandom.hex) bisa di-revoke; JWT untuk kasus stateless.Di episode 16 selanjutnya kita akan membedah configuration & environments — Rails Credentials terenkripsi, env vars, konfigurasi per-environment, dan praktik secrets management multi-env. Sampai jumpa di episode 16!