Skip to content

3 Introducción a API REST

Introducción

Una REST API, o API RESTFUL, es una interfaz de programación de aplicaciones que se ajusta a los límites de la arquitectura REST y permite la interacción con los servicios web de RESTful.

API Rest

Figura 2 - API REST

El usuario realiza una petición que es tratada a través de la API, que comunica con el servidor para que realice la operación y devuelva una respuesta, que vuelve a ser tratada por la API y devuelta al usuario.

APIs

Las API son conjuntos de definiciones y protocolos que se utilizan para diseñar e integrar el software de las aplicaciones. Suele considerarse como el contrato entre el proveedor de información y el usuario, donde se establece el contenido que se necesita por parte del consumidor (la llamada) y el que requiere el productor (la respuesta).Por ejemplo, el diseño de una API de servicio meteorológico podría requerir que el usuario escribiera un código postal y que el productor diera una respuesta en dos partes: la primera sería la temperatura máxima y la segunda, la mínima.

En otras palabras, las API le permiten interactuar con una computadora o un sistema para obtener datos o ejecutar una función, de manera que el sistema comprenda la solicitud y la cumpla.

Se puede considerar como los mediadores, un camino intermedio, entre los usuarios y los servicios web que se requieren obtener. De esta forma se pueden compartir recursos e información mientras se conservan la seguridad, el control y la autentificación.

Otra ventaja de las API es que no se necesita saber cómo se recibe el recurso ni de dónde proviene.

REST

REST no es un protocolo ni un estándar, sino más bien un conjunto de límites de arquitectura. Los desarrolladores de las API pueden implementarlo de distintas maneras.

Cuando el cliente envía una solicitud a través de una API de RESTful, esta transfiere una representación del estado del recurso requerido a quien lo haya solicitado o al extremo. La información se entrega por medio de HTTP en uno de estos formatos: JSON (JavaScript Object Notation), HTML, XLT, Python, PHP o texto sin formato. JSON es el lenguaje de programación más popular, ya que tanto las máquinas como las personas lo pueden comprender y no depende de ningún lenguaje, a pesar de que su nombre indique lo contrario.

También es necesario tener en cuenta otros aspectos. Los encabezados y los parámetros también son importantes en los métodos HTTP de una solicitud HTTP de la API de RESTful, ya que contienen información de identificación importante con respecto a los metadatos, la autorización, el identificador uniforme de recursos (URI), el almacenamiento en caché, las cookies y otros elementos de la solicitud. Hay encabezados de solicitud y de respuesta, pero cada uno tiene sus propios códigos de estado e información de conexión HTTP.

Para que una API se considere de RESTful, debe cumplir los siguientes criterios:

  • Arquitectura cliente-servidor compuesta de clientes, servidores y recursos, con la gestión de solicitudes a través de HTTP.
  • Comunicación entre el cliente y el servidor sin estado, lo cual implica que no se almacena la información del cliente entre las solicitudes de GET y que cada una de ellas es independiente y está desconectada del resto.
  • Datos que pueden almacenarse en caché y optimizan las interacciones entre el cliente y el servidor.
  • Una interfaz uniforme entre los elementos, para que la información se transfiera de forma estandarizada. Para ello deben cumplirse las siguientes condiciones:
    • Los recursos solicitados deben ser identificables e independientes de las representaciones enviadas al cliente.
    • El cliente debe poder manipular los recursos a través de la representación que recibe, ya que esta contiene suficiente información para permitirlo
    • Los mensajes autodescriptivos que se envíen al cliente deben contener la información necesaria para describir cómo debe procesarla.
    • Debe contener hipertexto, lo cual significa que cuando el cliente acceda a algún recurso, debe poder utilizar hipervínculos para buscar las demás acciones que se encuentren disponibles en ese momento.
  • Un sistema en capas que organiza en jerarquías invisibles para el cliente cada uno de los servidores (los encargados de la seguridad, del equilibrio de carga, etc.) que participan en la recuperación de la información solicitada.
  • Código disponible según se solicite (opcional), es decir, la capacidad para enviar códigos ejecutables del servidor al cliente cuando se requiera, lo cual amplía las funciones del cliente.

Si bien la API de REST debe cumplir todos estos parámetros, resulta más fácil de usar que un protocolo definido previamente, como SOAP (protocolo simple de acceso a objetos), el cual tiene requisitos específicos, como la mensajería XML y la seguridad y el cumplimiento integrados de las operaciones, que lo hacen más lento y pesado.

Por el contrario, REST es un conjunto de pautas que pueden implementarse según sea necesario. Por esta razón, las API de REST son más rápidas y ligeras, cuentan con mayor capacidad de ajuste y, por ende, resultan ideales para el Internet de las cosas (IoT) y el desarrollo de aplicaciones para dispositivos móviles.

Creación de una API REST con Node JS

Para crear una API REST, será necesario crear un servidor. Cuando el servidor esta creado habrá que tratar de manejar las diferentes tipos de rutas con las que se va a trabajar.

Supongamos que queremos crear una API para trabajar con pokemon, necesitaremos las rutas para acceder a todos los pokemon, crearlos, modificarlos y eliminarlos:

const ROUTES = {
    ALL: '/api/pokemons',
    ONLY: 'api/pokemon'
}

La ruta ALL será usada para aquellas peticiones con las que tenga que trabajar con TODOS los pokemon, como por ejemplo obtener todos los pokemon. Mientras que la ruta ONLY será necesaria para necesitar realizar operaciones con un pokemon concreto, por ejemplo: obtener un pokemon, eliminar un pokemon, crear un pokemon, actualizar un pokemon.

Una vez definidas las rutas creamos nuestro servidor, con un método callback que se encargará de procesar la información:

const processRequest = (req, res) => { }

const server = http.createServer(processRequest)

server.listen(1234, () => console.log('server listening on port http://localhost:1234'))

En el método processRequest debemos comprobar el método que se está usando, en este ejemplo vamos a usar el método GET y el método POST, para obtener pokemons y crearlos.

const processRequest = (req, res) => {
    const { method } = req

    switch (method) {
        case 'GET':
        // handler get
        case 'POST':
        // handler post
    }
}

Una vez que conocemos el método que se está llevando a cabo, debemos comprobar si coincide con alguna de las rutas, en caso de que deban coincidir. Por ejemplo, en el método GET de nuestro proyecto, podrá realizar la petición con las dos rutas definidas, para así obtener TODOS o UNO solo, mientras que a la hora de hacer el POST solo es necesario una ruta:

const processRequest = (req, res) => {
    const { method, url } = req

    switch (method) {
        case 'GET':
            switch (url) {
                case ROUTES.ALL:
                    res.setHeader('Content-Type', 'application/json; charset=utf-8')
                    return res.end(JSON.stringify(pokemons))
                case ROUTES.ONLY:
                    res.setHeader('Content-Type', 'application/json; charset=utf-8')
                    return res.end(JSON.stringify(pokemons[0]))
            }
        case 'POST':
        // handler POST
    }
}

Al hacer el POST debemos tener en cuenta que es necesario obtener el body de la petición, pero el problema está en que la petición van llegando poco a poco hasta que termine de completarse. Para ello, disponemos del evento data que se ejecuta cada vez que nos va llegando esa información, y el evento end que se ejecuta cuando la petición ha terminado de llegar:

const processRequest = (req, res) => {
    // ...
    switch (method) {
        // ...
        case 'POST':
            switch (url) {
                case ROUTES.ONLY: {
                    let body = '' // Creamos un cuerpo vacío

                    // Escuchamos el evento DATA
                    req.on('data', chunk => {
                        body += chunk.toString()
                    })

                    // Escuchamos el evento END que sucede cuando
                    // la petición a finalizado de llegar
                    req.on('end', () => {
                        const data = JSON.parse(body)

                        res.writeHead(201, { 'Content-Type': 'application/json; charset=utf-8' })

                        res.end(JSON.stringify(data))
                    })

                }

            }
    }
}

La petición va llegando poco a poco, por lo que se va almacenando a la variable body.Es importante saber que la información llega en binario, por eso se usa el método toString() a la información que nos va llegando (chuck). Una vez que la petición ha finalizado su llegada, en el evento end, resolvemos la petición

Por último, se indicaría un caso default para tratar con las rutas o los métodos que no se han indicado:

default:
    res.setHeader('Content-Type', 'text/html; charset=utf-8')
    return res.end('<h1>404</h1>')

Ejemplo completo:

const http = require('node:http')
const pokemons = require('package');

const ROUTES = {
    ALL: '/api/pokemons',
    ONLY: 'api/pokemon'
}

const processRequest = (req, res) => {
    const { method, url } = req

    switch (method) {
        case 'GET':
            switch (url) {
                case ROUTES.ALL:
                    res.setHeader('Content-Type', 'application/json; charset=utf-8')
                    return res.end(JSON.stringify(pokemons))
                case ROUTES.ONLY:
                    res.setHeader('Content-Type', 'application/json; charset=utf-8')
                    return res.end(JSON.stringify(pokemons[0]))
                default:
                    res.setHeader('Content-Type', 'text/html; charset=utf-8')
                    return res.end('<h1>404</h1>')
            }
        case 'POST':
            switch (url) {
                case ROUTES.ONLY: {
                    let body = '' // Creamos un cuerpo vacío

                    // Escuchamos el evento DATA
                    // Cada vez que va llegando la petición poco a poco
                    // se va almacenando a la variable body
                    // es importante saber que la información llega en binario,
                    // por eso se usa el método toString()
                    req.on('data', chunk => {
                        body += chunk.toString()
                    })

                    // Escuchamos el evento END que sucede cuando
                    // la petición a finalizado de llegar
                    req.on('end', () => {
                        const data = JSON.parse(body)

                        res.writeHead(201, { 'Content-Type': 'application/json; charset=utf-8' })

                        res.end(JSON.stringify(data))
                    })

                }
                default:
                    res.setHeader('Content-Type', 'text/html; charset=utf-8')
                    return res.end('<h1>404</h1>')
            }
        default:
            res.setHeader('Content-Type', 'text/html; charset=utf-8')
            return res.end('<h1>404</h1>')
    }
}

const server = http.createServer(processRequest)

server.listen(1234, () => console.log('server listening on port http://localhost:1234'))

Sugerencia

Cierto es, que cada vez que debemos de añadir una ruta o un método, debemos tener de modificar el código. Por eso recomienda el uso de métodos que maneje cada caso del switch incluso poder usar if con retornos:

const processRequest = (req, res) => {
    if(req.method === "GET"){
        return handlerGET(req, res)
    }

    // ...
}

const handlerGET = (req, res) =>{
    if(req.url === Routes.ALL){
        return getAll(req, res)
    }

    // ...
}
Además, también, se recomienda crear diferentes ficheros haciendo uso de las importaciones y de las exportaciones para poder trabajar de una forma clara.

Info

Para poder realizar peticiones a una API REST podemos crearnos un proyecto en cualquier otro lenguaje y crear una aplicación que consuma nuestra API. También existen aplicaciones como POSTMAN para poder consumirlas.