Abstrakte Illustration einer Schnittstelle: Datenblöcke fließen aus einem Server über eine Übersetzungsschicht als JSON zu mehreren Clients

REST API mit Ruby on Rails: Representer und JSON:API

Ein Controller mit render json: @article funktioniert am ersten Tag. Nach ein paar Wochen hängt die mobile App an jedem Spaltennamen deiner Tabelle, und die neue Spalte internal_notes steht plötzlich in jeder Antwort. Wer als Entwickler eine REST API mit Ruby on Rails erstellen will, braucht deshalb eine Schicht zwischen Model und Ausgabe. Hier siehst du, wie Representer und Serializer diese Aufgabe übernehmen, welche Gems 2026 noch gepflegt werden und was zu Statuscodes, Sicherheit, Paginierung und Versionierung gehört.

REST API in Ruby on Rails: mehr als CRUD

REST steht für Representational State Transfer. Roy Fielding hat den Architekturstil im Jahr 2000 in seiner Dissertation beschrieben. Anders als SOAP (Simple Object Access Protocol) ist REST kein eigenes Protokoll. Der Begriff bezeichnet Regeln für die Interaktion zwischen Client und Server über ein vorhandenes Protokoll, in der Regel HTTP. Der Kern: Ein Client arbeitet mit Ressourcen, die über einen Uniform Resource Identifier (URI) erreichbar sind, und bekommt vom Server eine Repräsentation davon, also eine Darstellung als JSON, XML oder HTML. Die Ressource „Artikel 42“ ist etwas anderes als das Dokument, das du für sie ausgibst.

Dazu kommen ein paar Einschränkungen, die häufig untergehen. Die Kommunikation ist zustandslos: Der Server speichert keine Sitzung, jede Anfrage enthält alle erforderlichen Daten, etwa das Token für den Zugriff. Weil jeder Aufruf zustandslos bleibt, kannst du beliebig viele Server hinter einen Load Balancer stellen. Auch ein mehrschichtiges System mit Proxy und Cache dazwischen ist ausdrücklich vorgesehen. Client und Server bleiben unabhängig voneinander, solange die einheitliche Schnittstelle stabil ist. Deshalb funktionieren RESTful APIs im Internet mit jedem System, das HTTP spricht. Jede Methode hat eine feste Bedeutung, die folgenden fünf wirst du ständig verwenden:

  • GET liest eine Ressource oder Liste und ändert nichts.
  • POST legt eine Ressource an, der Server vergibt die ID.
  • PUT ersetzt die Ressource vollständig, der Client schickt die ganze Repräsentation.
  • PATCH ändert nur die mitgeschickten Felder.
  • DELETE entfernt die Ressource.

Zwischen PUT und PATCH liegt ein handfester Unterschied. PUT und DELETE sind laut RFC 9110 (HTTP Semantics) idempotent: Zwei identische Anfragen hinterlassen denselben Zustand wie eine. Ein Client darf sie nach einem Timeout also gefahrlos wiederholen. PATCH garantiert das nicht, POST ebenfalls nicht. Rails leitet mit resources :articles sowohl PATCH als auch PUT auf die Action update. Wer echte PUT-Semantik will, muss fehlende Felder im Request selbst auf ihren Standardwert setzen.

Bei den Statuscodes reichen in der Praxis wenige:

Situation Code Rails-Symbol
Lesen oder Ändern erfolgreich 200 :ok
Ressource angelegt (mit Location-Header) 201 :created
Gelöscht, kein Inhalt im Body 204 :no_content
Nicht angemeldet / angemeldet, aber ohne Recht 401 / 403 :unauthorized / :forbidden
Ressource existiert nicht 404 :not_found
Validierung fehlgeschlagen 422 :unprocessable_content

Der Code 422 heißt seit RFC 9110 „Unprocessable Content“. Aktuelle Rails-Scaffolds schreiben deshalb :unprocessable_content, das ältere :unprocessable_entity erzeugt mit Rack 3.1 eine Warnung.

Representer und Serializer: die Darstellung gehört nicht ins Model

as_json im Model zu überschreiben, klappt bei Anwendungen mit einem einzigen Client. Spätestens wenn die Admin-Oberfläche andere Felder sehen soll als die öffentliche App, wird das Model zur Ablage für Darstellungslogik. Ein Serializer ist eine eigene Klasse, die festlegt, welche Attribute ein Objekt nach außen zeigt. Ein Beispiel: Die mobile App sieht nur die bestimmten Felder, die sie braucht, die interne Anwendung zusätzlich Notizen und Status. Ein Representer geht einen Schritt weiter: Er kennt das Format in beide Richtungen, er rendert also JSON und parst eingehendes JSON zurück ins Objekt.

Das Gem Representable aus dem Trailblazer-Umfeld folgt genau diesem Muster. Für die Implementierung beschreibt ein Decorator die Felder einmal:

require "representable/json"

class ArticleRepresenter < Representable::Decorator
  include Representable::JSON

  property :id, writeable: false
  property :title
  property :body
  property :published_at, writeable: false
end

# Rendern
ArticleRepresenter.new(article).to_json

# Parsen: setzt title und body über die Setter des Objekts
article = Article.new
ArticleRepresenter.new(article).from_json(request.raw_post)
article.save

Der Vorteil: Eingabe und Ausgabe können nicht auseinanderlaufen, weil beide aus derselben Definition entstehen. writeable: false sorgt dafür, dass ein Client die ID nicht von außen überschreibt. Validierung bleibt trotzdem Aufgabe des Models oder eines Form-Objekts.

Jbuilder, Alba, Representable und jsonapi-serializer im Vergleich

Die Tabelle zeigt die Werkzeuge, die Entwickler für Rails-APIs am häufigsten verwenden. Den Stand habe ich auf RubyGems geprüft (letzte stabile Version, Stand November 2026).

Gem Ansatz Parsen Letzte Version Passt für
Jbuilder Template (.json.jbuilder) nein 2.15.1 (05/2026) Apps, die ohnehin Views nutzen
Alba Ruby-Klasse, schnell nein 4.0.3 (11/2026) frische Projekte, eigenes Format
Representable Decorator, beide Richtungen ja 3.3.1 (09/2026) Ein- und Ausgabe aus einer Definition
Roar Representable plus Hypermedia-Links (u. a. HAL) ja 1.2.0 (01/2023) bestehende Trailblazer-Projekte
jsonapi-serializer JSON:API-Ausgabe nein 2.2.0 (03/2021) JSON:API, aber kaum noch Releases
active_model_serializers Serializer-Klasse nein 0.10.16 (12/2025) Bestandsprojekte

Jbuilder kommt vom Rails-Team und steht bei rails new ohne --api im Gemfile, im API-Modus nur auskommentiert. Für reine APIs würde ich bei neuen Projekten Alba verwenden: aktiv gepflegt, keine Abhängigkeit von Rails, und die Ressourcen sind normale Ruby-Klassen, die sich leicht testen lassen.

class AuthorResource
  include Alba::Resource
  attributes :id, :name
end

class ArticleResource
  include Alba::Resource

  attributes :id, :title
  attribute :published_on do |article|
    article.published_at&.to_date&.iso8601
  end
  one :author, resource: AuthorResource
end

ArticleResource.new(article).serialize
# => {"id":42,"title":"…","published_on":"2026-11-09","author":{"id":7,"name":"…"}}

Wer auch Eingaben über dieselbe Klasse abbilden will, sollte bei Representable bleiben. Für das Parsen genügen in vielen Projekten aber Strong Parameters, die du ohnehin verwenden musst.

Schichtenmodell einer Rails-API: Model, Representer und JSON-Ausgabe als gestapelte Ebenen mit Pfeilen in beide Richtungen

Ein Representer übersetzt in beide Richtungen: Model zu JSON und JSON zurück ins Model.

JSON:API und HAL: Hypermedia ohne Eigenbau

Irgendwann stellt sich die Frage, wie Beziehungen, Fehler und Links aussehen sollen. Statt eigene Konventionen zu erfinden, kannst du ein fertiges Format übernehmen.

JSON:API (aktuell Version 1.1) legt fest, dass jede Ressource in data mit type, id, attributes und relationships steht. Verknüpfte Datensätze landen in included, Fehler in einem Array errors. Der Medientyp ist application/vnd.api+json. Der Vorteil: Viele Client-Bibliotheken unterstützen das Format direkt, Entwickler auf der Gegenseite können sie ohne eigenen Parser verwenden. Der Preis ist ein recht wortreiches Format.

{
  "data": {
    "type": "articles",
    "id": "42",
    "attributes": { "title": "REST in Rails" },
    "relationships": { "author": { "data": { "type": "people", "id": "7" } } }
  },
  "links": { "self": "/api/v1/articles/42" }
}

HAL ist schlanker. Es ergänzt dein JSON nur um _links und optional _embedded. Hypermedia bedeutet hier: Der Server liefert die nächsten möglichen Schritte als Links mit, der Client muss keine URLs selbst zusammenbauen. Im Reifegradmodell von Leonard Richardson ist das Stufe 3 (HATEOAS). Roar bringt für HAL ein eigenes Modul mit. In der Praxis genügt vielen RESTful Diensten schon ein self-Link plus Paginierungslinks.

Sicherheit und Zugriff auf die API

Eine RESTful API ist ohne Browser-Session unterwegs. Clients senden deshalb bei jeder Anforderung ein Token im Authorization-Header. Rails bringt dafür authenticate_or_request_with_http_token mit, das ein fehlendes oder falsches Token einfach mit 401 beantwortet. Seit Rails 7.2 ermöglicht rate_limit to: 10, within: 1.minute außerdem eine Drosselung direkt im Controller, überzählige Aufrufe bekommen 429.

class Api::BaseController < ActionController::API
  include ActionController::HttpAuthentication::Token::ControllerMethods
  before_action :authenticate

  private

  def authenticate
    authenticate_or_request_with_http_token do |token, _options|
      @current_client = ApiClient.find_by(token_digest: Digest::SHA256.hexdigest(token))
    end
  end
end

Für die Sicherheit deiner Daten gilt außerdem: nur HTTPS, Tokens nur als Hash speichern und im Serializer ausdrücklich festlegen, welcher Inhalt nach außen geht. Wer zusätzliche Spalten automatisch ausliefert, veröffentlicht früher oder später etwas, das intern bleiben sollte.

Paginierung: Offset oder Cursor

Eine Liste ohne Begrenzung ist ein Lastproblem, das nur auf den Tag wartet. Zwei Verfahren sind verbreitet:

  1. Offset: ?page=3&per_page=25 wird zu LIMIT 25 OFFSET 50. Leicht zu bauen, Sprünge auf Seite 40 sind möglich. Die Datenbank muss aber alle übersprungenen Zeilen lesen, und kommen zwischen zwei Abrufen Datensätze hinzu, sieht der Client Einträge doppelt.
  2. Cursor (Keyset): Der Client schickt die letzte gesehene ID, die Abfrage lautet WHERE id > 1234 ORDER BY id LIMIT 25. Mit Index bleibt das auch bei Millionen Zeilen schnell, Seitensprünge gehen dafür nicht.
def index
  scope = Article.order(:id)
  scope = scope.where("id > ?", params[:after]) if params[:after].present?
  articles = scope.limit(26).to_a
  next_page = articles.size > 25
  articles = articles.first(25)

  if next_page
    response.headers["Link"] = %(<#{api_v1_articles_url(after: articles.last.id)}>; rel="next")
  end
  render json: ArticleResource.new(articles).serialize
end

Für Feeds und Synchronisation würde ich Cursor verwenden, für Admin-Tabellen mit Seitenzahlen Offset. Das Gem Pagy kann beide Varianten unterstützen. Wichtig ist, dass der Client die Informationen zur nächsten Seite aus dem Link-Header oder einem links-Objekt übernimmt. Im Beispiel oben setzt der Controller dafür rel="next".

Versionierung, ohne Clients zu brechen

Eine öffentliche API ändert sich, die Apps deiner Nutzer aktualisieren sich aber nicht sofort. Zusätzliche optionale Felder brechen keinen Client. Umbenennen oder Entfernen schon, dafür brauchst du eine weitere Hauptversion. Die meisten Entwickler verwenden in Rails einen Namespace im Pfad, Clients erstellen Ressourcen dann per POST auf /api/v1/articles:

# config/routes.rb
namespace :api do
  namespace :v1 do
    resources :articles, only: %i[index show create update destroy]
  end
end
module Api
  module V1
    class ArticlesController < Api::BaseController
      def create
        article = Article.new(article_params)
        if article.save
          render json: ArticleResource.new(article).serialize,
                 status: :created, location: api_v1_article_url(article)
        else
          render json: { errors: article.errors }, status: :unprocessable_content
        end
      end

      private

      def article_params
        params.expect(article: [:title, :body])
      end
    end
  end
end

params.expect gibt es seit Rails 8. Es antwortet bei falsch geformten Parametern mit 400 statt mit einem 500er. Alternativ lässt sich die Version über den Accept-Header aushandeln, etwa application/vnd.example.v2+json. Das hält URLs stabil, ist im Browser und in Logs aber schlechter sichtbar. Für die meisten Teams ist /api/v1/ die ehrlichere Wahl. Bei internen Diensten, deren Clients du selbst ausrollst, reicht oft eine einzige Version. Weitere Beiträge zu Architektur und Code findest du in der Rubrik Technik.

Infografik: HTTP-Methoden in einer Rails-API mit zugehöriger Controller-Action und typischem Statuscode

HTTP-Methode, Rails-Action und Statuscode auf einen Blick.

Checkliste für eine saubere Rails-API

Die folgenden Punkte prüfe ich vor dem ersten Release einer REST-API:

  • Ressourcen als Substantive im Plural (/articles), keine Verben in der URL.
  • PATCH für Teiländerungen, PUT nur mit echter Ersetzungs-Semantik.
  • 201 mit Location-Header nach create, 204 nach destroy, 422 bei Validierungsfehlern.
  • Ausgabe über Serializer oder Representer, nie direkt render json: model.
  • Ein einheitliches Fehlerformat für alle Endpoints, etwa per rescue_from ActiveRecord::RecordNotFound.
  • Listen sollten immer begrenzt sein, Standardgröße 25 bis 100, mit Links zur nächsten Seite.
  • Version im Pfad (/api/v1/) ab dem ersten öffentlichen Client.
  • Request-Specs, die das JSON-Schema prüfen, damit Änderungen am Serializer auffallen.
  • Gem-Stand regelmäßig prüfen: Ein Serializer ohne Release seit Jahren ist ein Risiko beim nächsten Rails-Upgrade.
Click to rate this post!
[Total: 0 Average: 0]

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert