NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.

Uso de Rails para Aplicaciones Solo-API

En esta guía aprenderás:


1 ¿Qué es una Aplicación API?

Tradicionalmente, cuando la gente decía que usaba Rails como una "API", se referían a proporcionar una API accesible programáticamente junto a su aplicación web. Por ejemplo, GitHub proporciona una API que puedes usar desde tus propios clientes personalizados.

Con la llegada de los frameworks del lado del cliente, más desarrolladores están usando Rails para construir un back-end que se comparte entre su aplicación web y otras aplicaciones nativas.

Por ejemplo, Twitter utiliza su API pública en su aplicación web, que está construida como un sitio estático que consume recursos JSON.

En lugar de usar Rails para generar HTML que se comunica con el servidor a través de formularios y enlaces, muchos desarrolladores están tratando su aplicación web como solo un cliente API entregado como HTML con JavaScript que consume una API JSON.

Esta guía cubre la construcción de una aplicación Rails que sirve recursos JSON a un cliente API, incluyendo frameworks del lado del cliente.

2 ¿Por qué Usar Rails para APIs JSON?

La primera pregunta que mucha gente tiene al pensar en construir una API JSON usando Rails es: "¿no es usar Rails para generar algo de JSON exagerado? ¿No debería simplemente usar algo como Sinatra?".

Para APIs muy simples, esto puede ser cierto. Sin embargo, incluso en aplicaciones muy pesadas en HTML, la mayor parte de la lógica de una aplicación vive fuera de la capa de vista.

La razón por la que la mayoría de la gente usa Rails es porque proporciona un conjunto de valores predeterminados que permite a los desarrolladores comenzar rápidamente, sin tener que tomar muchas decisiones triviales.

Veamos algunas de las cosas que Rails proporciona de serie que todavía son aplicables a las aplicaciones API.

Manejadas en la capa de middleware:

  • Recarga: Las aplicaciones Rails admiten recarga transparente. Esto funciona incluso si tu aplicación se vuelve grande y reiniciar el servidor para cada solicitud se vuelve inviable.
  • Modo de Desarrollo: Las aplicaciones Rails vienen con valores predeterminados inteligentes para el desarrollo, haciendo que el desarrollo sea agradable sin comprometer el rendimiento en tiempo de producción.
  • Modo de Prueba: Igual que el modo de desarrollo.
  • Registro: Las aplicaciones Rails registran cada solicitud, con un nivel de verbosidad apropiado para el modo actual. Los registros de Rails en desarrollo incluyen información sobre el entorno de la solicitud, consultas a la base de datos e información de rendimiento básica.
  • Seguridad: Rails detecta y frustra ataques de suplantación de IP y maneja firmas criptográficas de manera consciente de un ataque de temporización. ¿No sabes qué es un ataque de suplantación de IP o un ataque de temporización? Exactamente.
  • Análisis de Parámetros: ¿Quieres especificar tus parámetros como JSON en lugar de como una cadena codificada en URL? No hay problema. Rails decodificará el JSON por ti y lo hará disponible en params. ¿Quieres usar parámetros codificados en URL anidados? Eso también funciona.
  • GETs Condicionales: Rails maneja los encabezados de solicitud de procesamiento condicional GET (ETag y Last-Modified) y devuelve los encabezados de respuesta y el código de estado correctos. Todo lo que necesitas hacer es usar la verificación stale? en tu controlador, y Rails manejará todos los detalles HTTP por ti.
  • Solicitudes HEAD: Rails convertirá transparentemente las solicitudes HEAD en GET, y devolverá solo los encabezados al salir. Esto hace que HEAD funcione de manera confiable en todas las APIs de Rails.

Aunque obviamente podrías construir todo esto en términos de middleware existente de Rack, esta lista demuestra que la pila de middleware predeterminada de Rails proporciona mucho valor, incluso si solo estás "generando JSON".

Manejadas en la capa de Action Pack:

  • Enrutamiento de Recursos: Si estás construyendo una API JSON RESTful, querrás usar el enrutador de Rails. La asignación limpia y convencional de HTTP a controladores significa no tener que pasar tiempo pensando en cómo modelar tu API en términos de HTTP.
  • Generación de URL: La otra cara del enrutamiento es la generación de URL. Una buena API basada en HTTP incluye URLs (ver la API de GitHub Gist como ejemplo).
  • Respuestas de Encabezado y Redirección: head :no_content y redirect_to user_url(current_user) son útiles. Claro, podrías agregar manualmente los encabezados de respuesta, pero ¿por qué?
  • Caché: Rails proporciona caché de página, acción y fragmento. El caché de fragmento es especialmente útil al construir un objeto JSON anidado.
  • Autenticación Básica, Digest y Token: Rails viene con soporte listo para usar para tres tipos de autenticación HTTP.
  • Instrumentación: Rails tiene una API de instrumentación que activa controladores registrados para una variedad de eventos, como el procesamiento de acciones, el envío de un archivo o datos, la redirección y las consultas a la base de datos. La carga útil de cada evento viene con información relevante (para el evento de procesamiento de acciones, la carga útil incluye el controlador, acción, parámetros, formato de solicitud, método de solicitud y la ruta completa de la solicitud).
  • Generadores: A menudo es útil generar un recurso y obtener tu modelo, controlador, pruebas y rutas creadas para ti en un solo comando para luego ajustarlas. Lo mismo para migraciones y otros.
  • Plugins: Muchas bibliotecas de terceros vienen con soporte para Rails que reducen o eliminan el costo de configurar y unir la biblioteca y el framework web. Esto incluye cosas como sobrescribir generadores predeterminados, agregar tareas Rake y honrar las opciones de Rails (como el registrador y el backend de caché).

Por supuesto, el proceso de arranque de Rails también une todos los componentes registrados. Por ejemplo, el proceso de arranque de Rails es lo que usa tu archivo config/database.yml al configurar Active Record.

La versión corta es: puede que no hayas pensado en qué partes de Rails todavía son aplicables incluso si eliminas la capa de vista, pero la respuesta resulta ser la mayor parte de ellas.

3 La Configuración Básica

Si estás construyendo una aplicación Rails que será un servidor API ante todo, puedes comenzar con un subconjunto más limitado de Rails y agregar características según sea necesario.

3.1 Creando una Nueva Aplicación

Puedes generar una nueva aplicación Rails api:

$ rails new my_api --api

Esto hará tres cosas principales por ti:

  • Configurará tu aplicación para comenzar con un conjunto más limitado de middleware que lo normal. Específicamente, no incluirá ningún middleware principalmente útil para aplicaciones de navegador (como soporte de cookies) por defecto.
  • Hará que ApplicationController herede de ActionController::API en lugar de ActionController::Base. Al igual que con el middleware, esto dejará fuera cualquier módulo de Action Controller que proporcione funcionalidades principalmente usadas por aplicaciones de navegador.
  • Configurará los generadores para omitir la generación de vistas, ayudantes y activos cuando generes un nuevo recurso.

3.2 Generando un Nuevo Recurso

Para ver cómo nuestra API recién creada maneja la generación de un nuevo recurso, vamos a crear un nuevo recurso Group. Cada grupo tendrá un nombre.

$ bin/rails g scaffold Group name:string

Antes de que podamos usar nuestro código de andamio, necesitamos actualizar nuestro esquema de base de datos.

$ bin/rails db:migrate

Ahora, si abrimos nuestro GroupsController, deberíamos notar que con una aplicación Rails API estamos renderizando solo datos JSON. En la acción index consultamos Group.all y lo asignamos a una variable de instancia llamada @groups. Pasarlo a render con la opción :json automáticamente renderizará los grupos como JSON.

# app/controllers/groups_controller.rb
class GroupsController < ApplicationController
  before_action :set_group, only: %i[ show update destroy ]

  # GET /groups
  def index
    @groups = Group.all

    render json: @groups
  end

  # GET /groups/1
  def show
    render json: @group
  end

  # POST /groups
  def create
    @group = Group.new(group_params)

    if @group.save
      render json: @group, status: :created, location: @group
    else
      render json: @group.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /groups/1
  def update
    if @group.update(group_params)
      render json: @group
    else
      render json: @group.errors, status: :unprocessable_entity
    end
  end

  # DELETE /groups/1
  def destroy
    @group.destroy
  end

  private
    # Usa callbacks para compartir configuraciones comunes o restricciones entre acciones.
    def set_group
      @group = Group.find(params[:id])
    end

    # Solo permite una lista de parámetros de confianza a través.
    def group_params
      params.require(:group).permit(:name)
    end
end

Finalmente, podemos agregar algunos grupos a nuestra base de datos desde la consola de Rails:

irb> Group.create(name: "Rails Founders")
irb> Group.create(name: "Rails Contributors")

Con algunos datos en la aplicación, podemos iniciar el servidor y visitar http://localhost:3000/groups.json para ver nuestros datos JSON.

[
{"id":1, "name":"Rails Founders", "created_at": ...},
{"id":2, "name":"Rails Contributors", "created_at": ...}
]

3.3 Cambiando una Aplicación Existente

Si deseas tomar una aplicación existente y convertirla en una API, lee los siguientes pasos.

En config/application.rb, añade la siguiente línea en la parte superior de la definición de la clase Application:

config.api_only = true

En config/environments/development.rb, establece config.debug_exception_response_format para configurar el formato usado en las respuestas cuando ocurren errores en modo desarrollo.

Para renderizar una página HTML con información de depuración, usa el valor :default.

config.debug_exception_response_format = :default

Para renderizar información de depuración preservando el formato de respuesta, usa el valor :api.

config.debug_exception_response_format = :api

Por defecto, config.debug_exception_response_format está configurado en :api, cuando config.api_only está configurado en true.

Finalmente, dentro de app/controllers/application_controller.rb, en lugar de:

class ApplicationController < ActionController::Base
end

haz:

class ApplicationController < ActionController::API
end

4 Elegir Middleware

Una aplicación API viene con el siguiente middleware por defecto:

  • ActionDispatch::HostAuthorization
  • Rack::Sendfile
  • ActionDispatch::Static
  • ActionDispatch::Executor
  • ActionDispatch::ServerTiming
  • ActiveSupport::Cache::Strategy::LocalCache::Middleware
  • Rack::Runtime
  • ActionDispatch::RequestId
  • ActionDispatch::RemoteIp
  • Rails::Rack::Logger
  • ActionDispatch::ShowExceptions
  • ActionDispatch::DebugExceptions
  • ActionDispatch::ActionableExceptions
  • ActionDispatch::Reloader
  • ActionDispatch::Callbacks
  • ActiveRecord::Migration::CheckPending
  • Rack::Head
  • Rack::ConditionalGet
  • Rack::ETag

Consulta la sección de middleware interno de la guía de Rack para obtener más información sobre ellos.

Otros plugins, incluyendo Active Record, pueden agregar middleware adicional. En general, estos middleware son agnósticos al tipo de aplicación que estás construyendo y tienen sentido en una aplicación Rails solo-API.

Puedes obtener una lista de todo el middleware en tu aplicación a través de:

$ bin/rails middleware

4.1 Usando Rack::Cache

Cuando se usa con Rails, Rack::Cache utiliza el almacén de caché de Rails para sus almacenes de entidad y meta. Esto significa que si usas memcache para tu aplicación Rails, por ejemplo, la caché HTTP incorporada utilizará memcache.

Para hacer uso de Rack::Cache, primero necesitas agregar la gema rack-cache a Gemfile y establecer config.action_dispatch.rack_cache en true. Para habilitar su funcionalidad, querrás usar stale? en tu controlador. Aquí tienes un ejemplo de stale? en uso.

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at)
    render json: @post
  end
end

La llamada a stale? comparará el encabezado If-Modified-Since en la solicitud con @post.updated_at. Si el encabezado es más reciente que la última modificación, esta acción devolverá una respuesta "304 Not Modified". De lo contrario, renderizará la respuesta e incluirá un encabezado Last-Modified en ella.

Normalmente, este mecanismo se utiliza en una base por cliente. Rack::Cache nos permite compartir este mecanismo de caché entre clientes. Podemos habilitar el almacenamiento en caché entre clientes en la llamada a stale?:

def show
  @post = Post.find(params[:id])

  if stale?(last_modified: @post.updated_at, public: true)
    render json: @post
  end
end

Esto significa que Rack::Cache almacenará el valor Last-Modified para una URL en la caché de Rails y agregará un encabezado If-Modified-Since a cualquier solicitud entrante subsiguiente para la misma URL.

Piénsalo como almacenamiento en caché de página usando semánticas HTTP.

4.2 Usando Rack::Sendfile

Cuando usas el método send_file dentro de un controlador de Rails, establece el encabezado X-Sendfile. Rack::Sendfile es responsable de enviar realmente el archivo.

Si tu servidor front-end admite el envío acelerado de archivos, Rack::Sendfile descargará el trabajo de envío real del archivo al servidor front-end.

Puedes configurar el nombre del encabezado que tu servidor front-end usa para este propósito usando config.action_dispatch.x_sendfile_header en el archivo de configuración del entorno apropiado.

Puedes aprender más sobre cómo usar Rack::Sendfile con front-ends populares en la documentación de Rack::Sendfile.

Aquí tienes algunos valores para este encabezado para algunos servidores populares, una vez que estos servidores están configurados para admitir el envío acelerado de archivos:

# Apache y lighttpd
config.action_dispatch.x_sendfile_header = "X-Sendfile"

# Nginx
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"

Asegúrate de configurar tu servidor para admitir estas opciones siguiendo las instrucciones en la documentación de Rack::Sendfile.

4.3 Usando ActionDispatch::Request

ActionDispatch::Request#params tomará parámetros del cliente en formato JSON y los hará disponibles en tu controlador dentro de params.

Para usar esto, tu cliente necesitará hacer una solicitud con parámetros codificados en JSON y especificar el Content-Type como application/json.

Aquí tienes un ejemplo en jQuery:

jQuery.ajax({
  type: 'POST',
  url: '/people',
  dataType: 'json',
  contentType: 'application/json',
  data: JSON.stringify({ person: { firstName: "Yehuda", lastName: "Katz" } }),
  success: function(json) { }
});

ActionDispatch::Request verá el Content-Type y tus parámetros serán:

{ person: { firstName: "Yehuda", lastName: "Katz" } }

4.4 Usando Middlewares de Sesión

Los siguientes middlewares, utilizados para la gestión de sesiones, están excluidos de las aplicaciones API ya que normalmente no necesitan sesiones. Si uno de tus clientes API es un navegador, podrías querer agregar uno de estos:

  • ActionDispatch::Session::CacheStore
  • ActionDispatch::Session::CookieStore
  • ActionDispatch::Session::MemCacheStore

El truco para agregar estos es que, por defecto, se pasan session_options cuando se agregan (incluyendo la clave de sesión), por lo que no puedes simplemente agregar un inicializador session_store.rb, agregar use ActionDispatch::Session::CookieStore y tener sesiones funcionando como de costumbre. (Para ser claros: las sesiones pueden funcionar, pero tus opciones de sesión serán ignoradas, es decir, la clave de sesión predeterminada será _session_id).

En lugar del inicializador, tendrás que establecer las opciones relevantes en algún lugar antes de que se construya tu middleware (como config/application.rb) y pasarlas a tu middleware preferido, así:

# Esto también configura session_options para usar a continuación
config.session_store :cookie_store, key: '_your_app_session'

# Requerido para toda la gestión de sesiones (independientemente del session_store)
config.middleware.use ActionDispatch::Cookies

config.middleware.use config.session_store, config.session_options

4.5 Otro Middleware

Rails viene con una serie de otros middlewares que podrías querer usar en una aplicación API, especialmente si uno de tus clientes API es el navegador:

  • Rack::MethodOverride
  • ActionDispatch::Cookies
  • ActionDispatch::Flash

Cualquiera de estos middlewares se puede agregar a través de:

config.middleware.use Rack::MethodOverride

4.6 Eliminando Middleware

Si no deseas usar un middleware que está incluido por defecto en el conjunto de middleware solo-API, puedes eliminarlo con:

config.middleware.delete ::Rack::Sendfile

Ten en cuenta que eliminar estos middlewares eliminará el soporte para ciertas características en Action Controller.

5 Elegir Módulos del Controlador

Una aplicación API (usando ActionController::API) viene con los siguientes módulos de controlador por defecto:

ActionController::UrlFor Hace que url_for y ayudantes similares estén disponibles.
ActionController::Redirecting Soporte para redirect_to.
AbstractController::Rendering y ActionController::ApiRendering Soporte básico para renderizar.
ActionController::Renderers::All Soporte para render :json y similares.
ActionController::ConditionalGet Soporte para stale?.
ActionController::BasicImplicitRender Asegura devolver una respuesta vacía, si no hay una explícita.
ActionController::StrongParameters Soporte para el filtrado de parámetros en combinación con la asignación masiva de Active Model.
ActionController::DataStreaming Soporte para send_file y send_data.
AbstractController::Callbacks Soporte para before_action y ayudantes similares.
ActionController::Rescue Soporte para rescue_from.
ActionController::Instrumentation Soporte para los hooks de instrumentación definidos por Action Controller (consulta la guía de instrumentación para obtener más información sobre esto).
ActionController::ParamsWrapper Envuelve el hash de parámetros en un hash anidado, para que no tengas que especificar elementos raíz al enviar solicitudes POST, por ejemplo.
ActionController::Head Soporte para devolver una respuesta sin contenido, solo encabezados.

Otros plugins pueden agregar módulos adicionales. Puedes obtener una lista de todos los módulos incluidos en ActionController::API en la consola de Rails:

irb> ActionController::API.ancestors - ActionController::Metal.ancestors
=> [ActionController::API,
    ActiveRecord::Railties::ControllerRuntime,
    ActionDispatch::Routing::RouteSet::MountedHelpers,
    ActionController::ParamsWrapper,
    ... ,
    AbstractController::Rendering,
    ActionView::ViewPaths]

5.1 Agregando Otros Módulos

Todos los módulos de Action Controller conocen sus módulos dependientes, por lo que puedes sentirte libre de incluir cualquier módulo en tus controladores, y todas las dependencias serán incluidas y configuradas también.

Algunos módulos comunes que podrías querer agregar:

  • AbstractController::Translation: Soporte para los métodos de localización y traducción l y t.
  • Soporte para autenticación HTTP básica, digest o token:
    • ActionController::HttpAuthentication::Basic::ControllerMethods
    • ActionController::HttpAuthentication::Digest::ControllerMethods
    • ActionController::HttpAuthentication::Token::ControllerMethods
  • ActionView::Layouts: Soporte para diseños al renderizar.
  • ActionController::MimeResponds: Soporte para respond_to.
  • ActionController::Cookies: Soporte para cookies, que incluye soporte para cookies firmadas y encriptadas. Esto requiere el middleware de cookies.
  • ActionController::Caching: Soporte para el almacenamiento en caché de vistas para el controlador API. Ten en cuenta que necesitarás especificar manualmente el almacén de caché dentro del controlador así:

    class ApplicationController < ActionController::API
      include ::ActionController::Caching
      self.cache_store = :mem_cache_store
    end
    

    Rails no pasa esta configuración automáticamente.

El mejor lugar para agregar un módulo es en tu ApplicationController, pero también puedes agregar módulos a controladores individuales.


Comentarios

Se te anima a ayudar a mejorar la calidad de esta guía.

Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.

También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.

Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.

Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.