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
yLast-Modified
) y devuelve los encabezados de respuesta y el código de estado correctos. Todo lo que necesitas hacer es usar la verificaciónstale?
en tu controlador, y Rails manejará todos los detalles HTTP por ti. - Solicitudes HEAD: Rails convertirá transparentemente las solicitudes
HEAD
enGET
, y devolverá solo los encabezados al salir. Esto hace queHEAD
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
yredirect_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 deActionController::API
en lugar deActionController::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ónl
yt
.- 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 pararespond_to
.ActionController::Cookies
: Soporte paracookies
, 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.