edge
Más en rubyonrails.org: Más Ruby on Rails

Usando Rails para aplicaciones solo de API

En esta guía aprenderás:

1 ¿Qué es una aplicación de API?

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

Con la aparición de los frameworks del lado del cliente, más desarrolladores están utilizando Rails para construir un backend 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 se construye como un sitio estático que consume recursos JSON.

En lugar de utilizar Rails para generar HTML que se comunica con el servidor a través de formularios y enlaces, muchos desarrolladores tratan su aplicación web como un cliente de API que se entrega 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 de API, incluyendo frameworks del lado del cliente.

2 ¿Por qué usar Rails para API JSON?

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

Para APIs muy simples, esto puede ser cierto. Sin embargo, incluso en aplicaciones con mucho 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 las personas utilizan Rails es que 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 forma predeterminada y que siguen siendo aplicables a las aplicaciones de API.

Manejado en la capa de middleware:

  • Recarga: Las aplicaciones Rails admiten la 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, lo que hace que el desarrollo sea agradable sin comprometer el rendimiento en tiempo de producción.
  • Modo de prueba: Lo mismo que el modo de desarrollo.
  • Registro: Las aplicaciones Rails registran cada solicitud, con un nivel de verbosidad adecuado 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 básica de rendimiento.
  • Seguridad: Rails detecta y frustra los ataques de suplantación de IP y maneja las firmas criptográficas de manera consciente de los ataques de tiempo. ¿No sabes qué es un ataque de suplantación de IP o un ataque de tiempo? 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 pondrá disponible en params. ¿Quieres utilizar parámetros anidados codificados en URL? Eso también funciona.
  • GET condicionales: Rails maneja las solicitudes condicionales GET (ETag y Last-Modified) procesando las cabeceras de solicitud y devolviendo las cabeceras y el código de estado correctos. Todo lo que necesitas hacer es usar la función stale? en tu controlador, y Rails se encargará de todos los detalles HTTP por ti.
  • Solicitudes HEAD: Rails convertirá transparentemente las solicitudes HEAD en solicitudes GET y devolverá solo las cabeceras en el camino de salida. Esto hace que HEAD funcione de manera confiable en todas las APIs de Rails.

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

Manejado en la capa de Action Pack:

  • Enrutamiento de recursos: Si estás construyendo una API JSON RESTful, querrás utilizar el enrutador de Rails. Un mapeo limpio y convencional de HTTP a controladores significa no tener que perder tiempo pensando en cómo modelar tu API en términos de HTTP.
  • Generación de URL: El lado opuesto del enrutamiento es la generación de URL. Una buena API basada en HTTP incluye URLs (consulta la API de Gist de GitHub para ver un 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é hacerlo?
  • Caché: Rails proporciona caché de página, acción y fragmento. La caché de fragmentos es especialmente útil al construir un objeto JSON anidado.
  • Autenticación básica, de digestión y de token: Rails viene con soporte incorporado 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, la acción, los parámetros, el formato de la solicitud, el método de la solicitud y la ruta completa de la solicitud).
  • Generadores: A menudo es útil generar un recurso y obtener tu modelo, controlador, stubs de prueba y rutas creadas para ti en un solo comando para ajustes adicionales. 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 anular los generadores predeterminados, agregar tareas de Rake y respetar las elecciones 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 el que utiliza tu archivo config/database.yml al configurar Active Record.

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

3 La configuración básica

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

3.1 Crear una nueva aplicación

Puedes generar una nueva aplicación Rails para una 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 el soporte de cookies) de forma predeterminada.
  • 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 utilizadas principalmente por aplicaciones de navegador.
  • Configurará los generadores para omitir la generación de vistas, helpers y assets cuando generes un nuevo recurso.

3.2 Generar un nuevo recurso

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

$ bin/rails g scaffold Group name:string

Antes de poder usar nuestro código generado, 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 de API solo estamos renderizando datos JSON. En la acción de índice, 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
    # Use callbacks to share common setup or constraints between actions.
    def set_group
      @group = Group.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    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 Cambiar una aplicación existente

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

En config/application.rb, agrega la siguiente línea al principio 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 utilizado en las respuestas cuando ocurren errores en el modo de 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 conservando el formato de respuesta, usa el valor :api.

config.debug_exception_response_format = :api

De forma predeterminada, config.debug_exception_response_format se establece en :api, cuando config.api_only se establece 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 Elección de middleware

Una aplicación de API viene con el siguiente middleware de forma predeterminada:

  • 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 middleware interno de la guía de Rack para obtener más información sobre ellos.

Otros complementos, incluido Active Record, pueden agregar middleware adicional. En general, este middleware es agnóstico al tipo de aplicación que estás construyendo y tiene sentido en una aplicación Rails solo de API. Puedes obtener una lista de todos los 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 la tienda de caché de Rails para sus almacenes de entidad y meta. Esto significa que si usas memcache para tu aplicación de Rails, por ejemplo, la caché HTTP incorporada usará memcache.

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

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 nuevo que la última modificación, esta acción devolverá una respuesta "304 No modificado". De lo contrario, renderizará la respuesta e incluirá un encabezado Last-Modified en ella.

Normalmente, este mecanismo se utiliza de forma individual para cada cliente. Rack::Cache nos permite compartir este mecanismo de almacenamiento en 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 posterior para la misma URL.

Piensa en ello como almacenamiento en caché de páginas utilizando semántica HTTP.

4.2 Usando Rack::Sendfile

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

Si tu servidor frontal admite el envío acelerado de archivos, Rack::Sendfile delegará el trabajo real de envío de archivos al servidor frontal.

Puedes configurar el nombre del encabezado que tu servidor frontal utiliza para este propósito utilizando config.action_dispatch.x_sendfile_header en el archivo de configuración del entorno correspondiente.

Puedes obtener más información sobre cómo usar Rack::Sendfile con servidores frontales 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á los parámetros del cliente en formato JSON y los pondrá a disposición en tu controlador dentro de params.

Para usar esto, tu cliente deberá 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 de API es un navegador, es posible que desees agregar uno de estos nuevamente:

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

El truco para agregar estos nuevamente es que, por defecto, se les pasan session_options cuando se agregan (incluida 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 claro: las sesiones pueden funcionar, pero se ignorarán las opciones de sesión, es decir, la clave de sesión se establecerá en _session_id de forma predeterminada)

En lugar del inicializador, deberás configurar 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: '_interslice_session'

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

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

4.5 Otros Middlewares

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

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

Cualquiera de estos middlewares se puede agregar mediante:

config.middleware.use Rack::MethodOverride

4.6 Eliminando Middlewares

Si no deseas utilizar un middleware que está incluido de forma predeterminada en el conjunto de middlewares solo para API, puedes eliminarlo con: ruby config.middleware.delete ::Rack::Sendfile

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

5 Elección de los módulos del controlador

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

ActionController::UrlFor Hace que url_for y helpers 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 amigos.
ActionController::ConditionalGet Soporte para stale?.
ActionController::BasicImplicitRender Asegura que se devuelva una respuesta vacía si no hay una explícita.
ActionController::StrongParameters Soporte para 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 helpers similares.
ActionController::Rescue Soporte para rescue_from.
ActionController::Instrumentation Soporte para los ganchos de instrumentación definidos por Action Controller (ver la guía de instrumentación para más información al respecto).
ActionController::ParamsWrapper Envuelve el hash de parámetros en un hash anidado, de modo que no sea necesario especificar elementos raíz al enviar solicitudes POST, por ejemplo.
ActionController::Head Soporte para devolver una respuesta sin contenido, solo encabezados.

Otros complementos 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 Agregar otros módulos

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

Algunos módulos comunes que es posible que desees agregar:

  • AbstractController::Translation: Soporte para los métodos de localización y traducción l y t.
  • Soporte para autenticación HTTP básica, de resumen o de 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 de la API. Ten en cuenta que deberás especificar manualmente el almacén de caché dentro del controlador de esta manera:

    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 encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.

También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.

Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.

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