04 - Api Rest
04 - Api Rest
04 - Api Rest
Teoría
Los Package Managers (o Administradores de paquetes) sirven para no tener que descargar,
instalar y mantener las dependencias de un proyecto a mano. Estas aplicaciones facilitan la
descarga e instalación de las librerías que utiliza el proyecto. Para ello, requiere que
conozcamos el nombre exacto de la librería (y versión deseada si es necesario), y contar con
conexión a Internet. Luego, con solo ejecutar un comando, se descargará de un repositorio
centralizado la versión correspondiente de la dependencia especificada y se agregará al
proyecto.
NodeJS cuenta con su propio Administrador de Paquetes: NPM (NodeJS Package Manager).
Global:
$ npm install -g nombre-de-la-librería
Local:
$ npm install nombre-de-la-librería
Esta segunda opción es la más recomendable, ya que de esta manera podemos tener distintos
proyectos usando distintas versiones de una misma librería, sin generar problemas de
compatibilidad al actualizar a una nueva versión que no sea retrocompatible con las anteriores.
Ejemplo:
El programa A usa la libreria “fecha” en su versión 1.0, que cuenta con el método
“dameFecha()”. El programa B usa la librería “fecha” pero en su versión 2.0, que ya no cuenta
con el metodo “dameFecha()” ya que éste fue reemplazado por el nuevo método
“dameFechaLocal()”.
Si instalamos “fecha” en forma global, al actualizar la librería fecha, romperemos el programa A,
ya que intentará usar un método que ya no existe.
Si realizamos dos instalaciones locales (fecha 1.0 para A, fecha 2.0 para B) al actualizar cada
una por separado, cada programa seguirá contando con la versión correspondiente).
Sin embargo, muchas veces es útil instalar en forma global librerías utilitarias (por ejemplo
librerías de testing) que son usadas para facilitar las tareas de programación y revisión durante
la etapa de desarrollo pero que no son necesarias para el uso de la aplicación.
Advertencia:
Es posible que al instalar dependencias en forma global se nos solicite tener permisos de
administrador, ya que estaremos editando archivos de configuración y agregando contenidos
en carpetas del sistema.
El archivo ”package.json”
Cómo ya sabemos, los proyectos de NodeJS cuentan con un archivo de configuración en
formato JSON que, entre otras cosas, permite especificar el nombre del proyecto, versión,
nombre del autor, etc.
Una de las cosas que podemos especificar en este archivo es la lista de dependencias. Esto
es, una lista con los nombres de las librerías (con sus versiones) que el proyecto usa para su
funcionamiento.
Adicionalmente, también permite especificar una lista de dependencias “dev” (developer,
desarrollador) que son librerías que si bien no son necesarias para funcionamiento del sistema,
son utilizadas por el desarrollador para ir probando sus funcionalidades a medida que avanza el
desarrollo (por ejemplo, librerías de testing).
$ npm install
Si solo queremos instalar las dependencias imprescindibles de un proyecto (es decir, dejando
afuera las dependencias de desarrollador) debemos agregar el flag --production, así:
$ npm install --production
Además, podemos hacer que npm agregue como dependencia al package.json un módulo que
estamos instalando, todo en una misma acción. Si lo queremos como dependencia del
proyecto, agregaremos al comando ‘install’ la opción --save. En cambio, si sólo es una
dependencia del entorno de desarrollo, agregaremos --save-dev. Ejemplo:
Versionado
Las librerías de NPM siguen un estándar de versionado que sigue la siguiente semántica:
Consta de 3 números, separados entre sí por un punto:
● El primer número corresponde a actualizaciones grandes/significativas (Major Release),
que incluyen muchas nuevas características, o que cambian de manera significativa el
funcionamiento de las existentes.
● El segundo número corresponde a actualizaciones pequeñas (Minor Release), que
agregan pocas cosas nuevas o actualizan algún detalle del funcionamiento de la
librería.
● El tercer número corresponde a arreglos o parches (Patches), que corrigen defectos en
las funcionalidades de la librería.
Siguiendo este criterio, NPM ofrece una semántica especial para especificar los módulos que el
proyecto necesita tener instalados para poder funcionar (sus dependencias) y la versión de
cada uno de ellos, todo esto definido en su archivo de configuración package.json.
Como notarán en el ejemplo anterior, cada una de las versiones de las dependencias está
precedida por un símbolo. Este símbolo indica la forma en que deseamos que se actualice ese
módulo en particular cada vez que ejecutemos npm install en el proyecto, siguiendo la
siguiente tabla:
~ (solo patches)
Si escribimos en nuestro package.json: ~0.13.0
Más símbolos:
>: descargar/actualizar a cualquier versión posterior a la dada
>=: descargar/actualizar a cualquier versión igual o posterior a la dada
<=: descargar/actualizar a cualquier versión igual o anterior a la dada
<: descargar/actualizar a cualquier versión anterior a la dada
Aquí se usará la versión 1.0.0 (si la encuentra) o alguna a partir de 1.1.0 pero anteriores a
1.2.0.
Aplicaciones RESTful:
Conceptos Generales
Teoría
Existen diversos tipos de aplicaciones. Uno de los tipos más nombrados en la actualidad es
este. Cuando hablamos de aplicaciones RESTful, nos referimos a aplicaciones que operan en
forma de servicios web, respondiendo consultas a otros sistemas a través de internet, y lo
hacen respetando algunas reglas y convenciones que detallaremos a lo largo de este
documento.
API
Una API (sigla de Application Programming Interface) es un conjunto de reglas y
especificaciones que describen la manera en que un sistema puede comunicarse con otros.
Definir una API en forma clara y explícita, habilita y facilita el intercambio de mensajes entre
sistemas, y permite la colaboración e interoperabilidad entre los mismos, aún cuando éstos
hayan sido desarrollados para distintas plataformas e incluso en distintos lenguajes.
Algunas APIs cuentan con una interfaz gráfica que puede ser embebida en un sitio web y
directamente usada por un usuario, mientras que otras sólo son de uso interno, es decir que un
usuario nunca accederá directamente a ella, sino que será algún programa quien la utilice
(internamente).
Por lo dicho, es importante al momento de crear una API también generar la documentación
detallada que acompañe, en donde se especifique con precisión cómo se debe interactuar con
la misma (esto es, qué tipo de mensajes puede recibir, y qué clase de respuestas se puede
esperar de la misma).
Como ejemplo de API con interfaz gráfica accesible directamente por usuarios podemos
nombrar la API de Google Maps, que nos permite embeber en nuestros sitios y aplicaciones
mapas con toda la información actualizada de google, sin necesidad de haberlos programado.
O también, las APIs de redes sociales como Facebook o Twitter, que nos permiten compartir
artículos y publicaciones en las redes desde el mismo sitio que estamos mirando, sin necesidad
de salir del mismo, vinculándose automáticamente con nuestra cuenta (previo login), y
realizando la publicación del contenido en cuestión.
REST
REST viene del inglés “REpresentational State Transfer” (o en español: Transferencia de
Estado Representacional).
Por Representación nos referimos a un modelo o estructura con la que representamos algo.
Por Estado de una representación, hablamos de los datos que contiene ese modelo estructura.
Transferir un Estado de Representación implica el envío de datos (con una determinada
estructura) entre dos partes.
Los dos formatos más utilizados para este tipo de transferencias de datos son XML y JSON.
Ambos formatos permiten asociar valores con identificadores, así como también generar
estructuras anidadas.
Ejemplo XML
<factura>
<cliente>Gomez</cliente>
<emisor>Perez S.A.</emisor>
<tipo>A</tipo>
<items>
<item>Producto 1</item>
<item>Producto 2</item>
<item>Producto 3</item>
</items>
</factura>
Ejemplo JSON
{
"cliente":"Gomez",
"emisor":"Perez S.A.",
"tipo":"A",
"items": [
"Producto 1",
"Producto 2",
"Producto 3"
]
}
HTTP
HTTP (Hypertext Transfer Protocol o Protocolo de Transferencia de HiperTexto) es, como su
nombre lo dice un protocolo (conjunto de reglas y especificaciones) que se utiliza a la hora de
intercambiar datos a través de internet.
Otro aspecto que define el protocolo es la forma de comunicar el resultado de las peticiones
HTTP.
Cada vez que se realiza una petición, el servidor deberá responder con algún mensaje. Cada
mensaje de respuesta debe tener un código de estado numérico de tres cifras.
El formato de los códigos de estado es el siguiente:
500 Internal Server Error Error genérico del servidor al procesar una petición válida
API REST
Este tipo de API tiene como particularidad que no posee interfaz gráfica, y se utiliza
exclusivamente para comunicación entre sistemas, mediante el protocolo HTTP.
Para que una API se considere REST, debe cumplir con las siguiente características:
Interfaz uniforme
En un sistema REST, cada acción (más correctamente, cada recurso - ver próximo punto) debe
contar con una URI (Uniform Resource Identifier), un identificador único. Ésta nos facilita el
acceso a la información, tanto para consultarla, como para modificarla o eliminarla, pero
también para compartir su ubicación exacta a terceros.
Cacheable
Debe admitir un sistema de almacenamiento en caché. La infraestructura de red debe soportar
una caché de varios niveles. Este almacenamiento evita repetir varias conexiones entre el
servidor y el cliente, en casos en que peticiones idénticas fueran a generar la misma respuesta.
Utilización de hipermedios
Cada vez que se hace una petición al servidor y este devuelve una respuesta, parte de la
información devuelta pueden ser también hipervínculos de navegación asociada a otros
recursos del cliente. Como resultado de esto, es posible navegar de un recurso REST a
muchos otros, simplemente siguiendo enlaces sin requerir el uso de registros u otra
infraestructura adicional.
● getUsuario()
● addUsuario()
● removeUsuario()
● updateUsuario()
● listUsuarios()
● findUsuario()
● Usuarios
Luego, cada funcionalidad relacionada con este recurso tendría sus propios identificadores:
Listar usuarios:
Petición HTTP de tipo GET a la URL: http://servicio/api/usuarios
Agregar usuario:
Petición HTTP de tipo POST a la URL: http://servicio/api/usuarios
(Agregando a la petición el registro correspondiente con los datos del nuevo usuario)
Obtener al usuario 1:
En caso de querer acceder a un elemento en particular dentro de un recurso, se pueden
hacerlo fácilmente si se conoce su identificador (URI):
Modificar al usuario 1:
Para actualizar un dato del usuario, un cliente REST podría primero descargar el registro
anterior usando GET. El cliente después modificaría el objeto para ese dato, y lo enviaría al
servidor utilizando una petición HTTP de tipo PUT.
Introducción
Algo vital a la hora de desarrollar un servidor es poder probar que los puntos de entrada de
nuestra API funcionan como lo deseamos. Si bien nuestros servicios serán consumidos desde
alguna aplicación cliente, no es necesario que utilicemos la misma para probar nuestro
servidor. En este apunte aprenderemos una forma simple y fácil de realizar consultas al
servidor a través de nodejs.
Módulo Axios
Una vez más, en lugar de utilizar las funcionalidades nativas de NodeJS para el envío de
peticiones, usaremos un módulo externo, que nos abrirá una sintaxis más concisa y clara.
En este caso, usaremos ‘axios’, un módulo basado en Promesas, lo cual nos permite
aprovechar el mecanismo ‘async / await’ para reproducir la estructura de código sincrónico en
nuestros programas.
Ejemplo de importación
Los objetos recibidos como respuesta del servidor contienen un campo "data" de tipo objeto.
Éste contiene los datos devueltos como resultado de la petición. La respuesta también
cuenta con algunos otros campos de utilidad. Entre ellos, "status", de tipo numérico, que
contiene el código de estado de la respuesta (200, 400, 500, etc…).
Aquí la propiedad "params" representa a los parámetros de la petición, es decir aquellos datos
que normalmente se envían en una URL precedidos por un signo de pregunta:
URL: http://ruta/al/recurso?autor=nombre
Post
Aquí el segundo argumento debe ser el objeto que se quiere adjuntar a la petición,
normalmente adjuntado en el cuerpo (body) de la misma.
Las funciones del módulo realizan peticiones asincrónicas, y devuelven promesas. Por lo
tanto, para acceder a sus resultados se deberá utilizar la palabra reservada await para
ejecutar dichas funciones, y estos llamados deberán realizarse dentro de funciones async.
Manejo de errores
Todas las peticiones corren el riesgo de fallar. En el caso de Axios, todas las peticiones que
reciben una respuesta con código de estado de la familia del 200 se consideran exitosas. Todas
las demás se consideran falladas, y lanzan un error, el cual contiene la respuesta del servidor
entre sus atributos, bajo el campo "response". Si no queremos atarnos a esta peculiaridad del
módulo, podemos atrapar los errores, y luego relanzar únicamente la respuesta obtenida desde
el servidor.
/test/clienteMensajes.js
function crearClienteMensajes(rutaServidor) {
const rutaApi = '/api/mensajes'
return {
post: async (mensaje) => {
const ruta = rutaServidor + rutaApi
try{
return await axios.post(ruta, mensaje)
} catch(error) {
throw error.response
}
}
}
}
Luego, utilizando nuestro cliente de prueba, podemos escribir algunos tests para probar el
correcto funcionamiento de los endpoints de nuestro servidor.
/test/testMensajes.js
test(async () => {
const mensaje = {
campo: 'valor',
otro: 'otro valor'
}
try {
respuesta = await cliente.post(mensaje)
// validar respuesta?
} catch (error) {
// validar error?
}
})
Para que esta prueba funcione, el servidor debe estar corriendo en el puerto correspondiente!
Aplicaciones RESTful:
Servidor de pruebas
Motivación
En el desarrollo de sistemas se puede presentar la situación en la que al comienzo de un
proyecto o en distintos momentos del proyecto, distintos equipos necesitan contar con una API
para realizar demos, prototipos, etc.
Para que los distintos equipos puedan avanzar, se puede crear una api Rest de prueba, y con
json server podemos crear estas pruebas de forma rápida. En su sitio web:
https://github.com/typicode/json-server se puede encontrar la documentación completa para
más detalles.
Ejemplo de uso
1. Crear un proyecto, en una carpeta nueva: npm init –y
2. Instalar servidor JSON: npm install -g json-server
3. Crea un archivo db.json en la misma carpeta
4. Agregamos datos de prueba al archivo db.json, por ejemplo:
{
"instituto": {
"nombre": "Instituto de tecnología ORT"
},
"carreras": [
{
"nombre": "Analista de sistemas"
},
{
"nombre": "Biotecnología"
}
]
}
{
"nombre": "Instituto de tecnología ORT"
}
7. Para ver las carreras en este ejemplo, en el navegador ingrese esta dirección
http://localhost:3000/carreras se obtiene:
[
{
"nombre": "Analista de sistemas"
},
{
"nombre": "Biotecnología"
}
]
8. Cuando se trata de colecciones, para poder realizar operaciones de tipo POST, PUT, y
DELETE, (así como también para poder acceder a un recurso mediante su identificador
único) es necesario que los objetos presentes cuenten con un campo id numérico.
Caso contrario, ocurrirá un error al intentar cualquiera de estas operaciones.
{
"instituto": {
"nombre": "Instituto de tecnología ORT"
},
"carreras": [
{
"id": 1,
"nombre": "Analista de sistemas"
},
{
"id": 2,
"nombre": "Biotecnología"
}
]
}
Consigna
1) Realizar un cliente de pruebas que, dada una url base de un servidor REST, permita
realizar las peticiones básicas incluidas en el mismo:
a) GET (con y sin parámetros y/o identificadores)
b) POST
c) PUT
d) DELETE
2) Levantar un servidor de pruebas a partir de un JSON, y probar el funcionamiento del
cliente creado.