Incluya rápidamente operaciones CRUD, manejo de errores, clasificación y filtrado en su API REST hoy
Configuración del entorno
¡Comencemos! Primero, déjame revisemos las herramientas que usaremos:
Node.js
: Un entorno de tiempo de ejecución JavaScript de código abierto que le permite ejecutar código fuera de un navegador. Desarrollaremos nuestra API RESTful en JavaScript en un servidor Node.jsMongoDB
: la base de datos en la que escribiremos nuestros datos.Postman
: la herramienta que usaremos para probar nuestra AP.VSCode
: puedes usar el editor de texto que quieras. Para el ejercicio usaremos VSCode.
Si no tiene ninguna de estas herramientas configuradas, vaya a Configurando Node.js y MongoDB Atlas que lo guía paso a paso.
Pero, antes de empezar, revisemos algunos conceptos:
Introducción a REST API
Hoy en día se escucha sobre REST API en todas partes en el mundo tecnológico actual, pero ¿qué es? Para empezar, API significa Interfaz de Programación de Aplicaciones.
¿Cuál es el beneficio de una API? Permite que dos piezas de software se comuniquen entre sí. Existen muchos tipos de API: SOAP, XML-RPC, JSON-RPC, pero en este artículo hablaremos de REST.
¿Qué es REST? Es sinónimo de REpresentational State Transfer (Transferencia de Estado de Representación). Es un estilo de arquitectura de software que se utiliza para crear servicios web. REST ha facilitado que los sistemas informáticos se comuniquen entre sí a través de Internet.
¿Como funciona? Bastante similar a cómo escribe «rest api» en Google y se devuelven los resultados de búsqueda. Explicándolo de la forma más sencilla, un cliente realiza una llamada (solicitud) en forma de URL a un servidor que solicita algunos datos. A continuación se muestra un ejemplo de una búsqueda en Google para «API REST»
www.google.com/search?q=rest+api
El servidor luego responde con los datos (respuesta) a través del protocolo HTTP. Estos datos aparecen en notación JSON y se convierten en una imagen estéticamente agradable en su navegador: los resultados de búsqueda de Google.
En resumen, envía una solicitud en forma de URL y recibe una respuesta en forma de datos.
¿Cómo se reciben estos datos normalmente? Generalmente en formato JSON. JSON (Javascript Object Notation) describe sus datos en pares clave-valor. JSON facilita la lectura de los datos tanto en máquinas como en humanos.
Requests
Primero, es importante comprender que las solicitudes constan de cuatro partes.
Endpoint: la URL que solicita. Típicamente compuesto por una raíz y una ruta. P.ej. https://www.google.com/search?q=rest+api, donde la raíz es https://www.google.com
y la ruta es / search? q = rest + api
.
Método: el tipo de solicitud que envía al servidor o «verbo HTTP». Así es como podemos ejecutar nuestras operaciones CRUD (Crear, Leer, Actualizar o Eliminar). Los cinco tipos son GET
, POST
, PUT
, PATCH
y DELETE
. (fuente: https://developer.mozilla.org/es/docs/Web/HTTP/Methods)
Encabezados: información adicional que se puede enviar al cliente o servidor que se utiliza para ayudar a sus datos de alguna manera. Por ejemplo, se puede usar para autenticar a un usuario para que puedan ver los datos. O puede decirle cómo se deben recibir los datos (aplicación / JSON).
Datos (body): la información que queremos recibir de nuestra solicitud como JSON.
Revisando con mas detalle los endpoints: dada la importancia de estos, estos son los conceptos adicionales que debemos manejar:
- Dos puntos (
:
) Se usa para indicar una variable en la cadena. En API REST, verá endpoints como:username
(o algo similar). Solo sepa que cuando lo pruebe, debe reemplazar ese nombre de usuario con un nombre de usuario real. Por ejemplo:
prueba.com/users/:username/articles?query=value&query2=value2
prueba.com/users/epadilla/articles?published=true&date=today
- Interrogación de cierre (
?
): Comienza los parámetros de consulta. Los parámetros de consulta son conjuntos de pares clave-valor que se pueden usar para modificar sus solicitudes. Aquí hay un ejemplo de un endpoint en el que quiero ver mis artículos y si han sido publicados o no:
prueba.com/users/:username/articles?query=value
prueba.com/users/epadilla/articles?published=true
- Ampersand (
&
): Se utiliza para separar los parámetros de consulta cuando desea usar múltiples. Por ejemplo, podemos ver mis artículos publicados y publicados hoy
prueba.com/users/:username/articles?query=value&query2=value2
prueba.com/users/epadilla/articles?published=true&date=today
Una API REST debe tener al menos las siguientes cuarta partes:
- Servidor: se utiliza para establecer todas las conexiones que necesitamos, así como para definir información importante como puntos finales, puertos y enrutamiento.
- Modelo: ¿Cómo son nuestros datos?
- Rutas: ¿A dónde van nuestros endpoint?
- Controladores: ¿Qué hacen nuestros endpoints?
Configurando el Server:
Empecemos agregando las dependencias:
npm install mongoose
npm install body-parser
npm install express
Después de instalar las dependencias su package.json
debe lucir similar al siguiente:
Ahora si, empezamos por el main.js
. sustituyamos el código, por el siguiente:
var express = require("express"),
app = express(),
port = process.env.PORT || 3000,
mongoose = require("mongoose"),
bodyParser = require("body-parser"),
Entry = require("./api/models/leaderboardModel");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
mongoose.connect(
"mongodb+srv://ryangleason:leaderboard@cluster0-6mky3.mongodb.net/RESTTutorial?retryWrites=true&w=majority",
{ useUnifiedTopology: true, useNewUrlParser: true, useFindAndModify: false }
);
var routes = require("./api/routes/leaderboardRoutes");
routes(app);
app.listen(port);
console.log("API server started on " + port);
Explicando un poco este archivo:
Express
: el marco de la aplicación web paraNode.js
. Esto inicia un servidor y escucha las conexiones en el puerto 3000.Mongoose
: nos ayuda a modelar objetos para MongoDB. Ayuda a gestionar las relaciones entre los datos, valida el esquema y, en general, es útil para comunicarse desde Node.js aMongoDB
.bodyParser
: es necesario para interpretar las solicitudes. Funciona extrayendo la parte del cuerpo de una solicitud entrante y la expone comoreq.body
y comoJSON
.Entry
: este es el nombre de nuestro esquema. Necesitamos incluir esto en nuestroapp.js
porque el esquema debe estar «registrado» para cada modelo que usemos.mongoose.connect (cadena uri, {opciones})
: establece una conexión con nuestra base de datos Atlas MongoDB. Establecemos estas opciones para eliminar las advertencias de desuso.routes (app)
: – Define cómo responderán losendpoints
de una aplicación a las solicitudes de los clientes. Esto apuntará a las rutas que nos definimos.
Antes de poder probar nuestro REST API, necesitamos crear nuestro modelo, rutas y el controlador. Así es como se ve la estructura de directorios del proyecto:
Creando el modelo
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var EntrySchema = new Schema({
player: {
type: String,
required: "Please enter a name"
},
score: {
type: Number,
required: "Please enter a score"
},
registered: {
type: String,
default: "No"
}
});
module.exports = mongoose.model("Entry", EntrySchema, "Leaderboard");
Se ha creado un modelo básico, que solo contiene tres campos. Es importante comprender que estamos utilizando un esquema Mongoose. Cada esquema se asigna a una colección MongoDB y define la estructura de los documentos dentro de esa colección. Luego verá que al final estamos exportando nuestro esquema, llamado Entry
, y exponiéndolo al resto de nuestra aplicación.
Cada Entry
representará un registro diferente en nuestra «tabla» de clasificación donde una Entry
es el nombre del jugador, el puntaje y si están registrados.
Definiendo Rutas
module.exports = app => {
var leaderboard = require("../controllers/leaderboardController");
app
.route("/entries")
.get(leaderboard.read_entries)
.post(leaderboard.create_entry);
app
.route("/entries/:entryId")
.get(leaderboard.read_entry)
.put(leaderboard.update_entry)
.delete(leaderboard.delete_entry);
};
Las rutas definirán lo que sucede cuando un usuario llega a uno de nuestros endpoints
. También es cómo determinamos qué operación CRUD se utiliza para interactuar con los datos en nuestra base de datos.
POST
—Create un entryGET
— Read un entryPUT
— Update un entryDELETE
— Delete un entry
Definamos el Controlador
var mongoose = require("mongoose"),
Entry = mongoose.model("Entry");
exports.read_entries = async (req, res) => {
ret = await Entry.find();
res.json(ret);
};
exports.create_entry = async (req, res) => {
const new_entry = new Entry(req.query);
ret = await new_entry.save();
res.json(ret);
};
exports.read_entry = async (req, res) => {
ret = await Entry.findById(req.params.entryId);
res.send(ret);
};
exports.update_entry = async (req, res) => {
ret = await Entry.findByIdAndUpdate({ _id: req.params.entryId }, req.body, {
new: true
});
res.json(ret);
};
exports.delete_entry = async (req, res) => {
await Entry.deleteOne({ _id: req.params.entryId });
res.json({ message: "Entry deleted" });
};
Aquí es donde «sucede la magia». Entonces, ¿qué está sucediendo cuando nuestras rutas se ven afectadas? Este es el mínimo necesario para una API completamente funcional. Cada uno de estos métodos corresponde con una ruta: notará que todos los nombres son los mismos que las rutas.
Cada uno de estos métodos incluye dos parámetros: la solicitud y la respuesta (req, res)
. Luego usamos el método Mongoose que coincide con la operación que estamos tratando de implementar. Por ejemplo, para una solicitud GET
, queremos encontrar todos los documentos y devolverlos como un objeto JSON
de respuesta. Para hacerlo, utilizamos el método find()
.
Lo mismo vale para POST
. Queremos crear un registro, por lo que utilizamos el método Mongoose, save()
, para escribir ese registro en la base de datos.
Ha llegado el momento de probar nuestra aplicación.
Ahora realice pruebas desde el navegador. también puede usar curl
y Postman
.
Creemos nuestro primer registro, usando postman
Así es como se verá una solicitud POST enviada a localhost:3000/Entries
. Aquí está la respuesta que debe obtener al presionar el botón Send
:
¡Felicidades! Acaba de ingresar su primer registro en la base de datos. Puede confirmarlo consultando las Collections
en MongoDB Atlas
:
Una solicitud GET
a localhost:3000/entries
recibirá todos los documentos en nuestra base de datos como respuesta.
Una solicitud GET
a localhost:3000/entries /: entryId
(ejemplo de arriba: localhost:3000/entries/5e5a731b3e8009a0b9bdd9ff
) devolverá solo un registro.
Una solicitud PUT
enviada a localhost:3000/entries/:entryId
actualizará la entrada con lo que haya puesto en el cuerpo.
Una solicitud DELETE
enviada a localhost:3000/entries/:entryId
eliminará la entrada especificada.
Gestión de Errores
Para el manejo de errores con las API REST, vamos a utilizar declaraciones try-catch
. Vamos a «intentar» hacer la solicitud y si eso falla, «atraparemos» el error. Esta es una manera de manejar con gracia los errores. A continuación se muestran los tipos de errores que puede encontrar al crear una solicitud de API:
100 level (informational)
— El servidor reconoce una solicitud.200 level (Successful)
— El servidor completó la solicitud.300 level (Redirect)
— El cliente debe hacer más para completar la solicitud.400 level (Client Error)
—El cliente envió una solicitud no válida.500 level (Server Error)
— El servidor no pudo completar la solicitud debido a un error del servidor.
Cambie su controlador con el siguiente código:
var mongoose = require("mongoose"),
Entry = mongoose.model("Entry");
exports.read_entries = async (req, res) => {
try {
const ret = await Entry.find();
res.json(ret);
} catch (error) {
res.send({ message: "Bad request: " + error });
}
};
exports.create_entry = async (req, res) => {
try {
const new_entry = new Entry(req.query);
ret = await new_entry.save();
res.json(ret);
} catch (error) {
res.send({ message: "Bad request: " + error });
}
};
exports.read_entry = async (req, res) => {
try {
const ret = await Entry.findById(req.params.entryId);
res.send(ret);
} catch (error) {
res.send({ message: "Bad request: " + error });
}
};
exports.update_entry = async (req, res) => {
try {
const ret = await Entry.findByIdAndUpdate(
{ _id: req.params.entryId },
req.query,
{ new: true }
);
res.json(ret);
} catch (error) {
res.send({ message: "Bad request: " + error });
}
};
exports.delete_entry = async (req, res) => {
try {
const ret = await Entry.deleteOne({ _id: req.params.entryId });
res.json({ message: "Deleted entry" });
} catch (error) {
res.send({ message: "Bad Request: " + error });
}
};
Para realizar la prueba, enviemos una solicitud incorrecta para que podamos ver si nuestro manejo de errores funciona:
Ordenado
Por ejemplo, si queremos ordenar en orden descendente por puntaje. Tomamos el parámetro de clasificación de la cadena de URL, verificamos si es verdadero y luego ejecutamos una clasificación simple.
Solo tenemos que actualizar nuestro método read_entries
:
exports.read_entries = async (req, res) => {
try {
const ret = await Entry.find();
if (req.query.sort === "true")
res.json(ret.sort((a, b) => b.score - a.score));
else res.json(ret);
} catch (error) {
res.send({ message: "Bad Request: " + error });
}
};
Tomamos la variable sort
de nuestra cadena de consulta de URL. La forma en que accedemos a esto es req.query.sort
.
Realice la prueba agregando sort=true
a nuestro endpoint.
Filtrado
Ahora realizaremos un filtro para mostrar solo las entradas registradas. ¿Cómo hacemos eso? Pues, agregamos un filtro. Nuevamente, por simplicidad, solo vamos a modificar el método read_entries
.
exports.read_entries = async (req, res) => {
try {
var ret = await Entry.find();
if (req.query.sort === "true") {
ret = ret.sort((a, b) => b.score - a.score);
}
if (req.query.registered === "yes") {
ret = ret.filter(a => a.registered === "yes");
}
res.json(ret);
} catch (error) {
res.send({ message: "Bad GET Request: " + error });
}
};
Tomamos la variable registered
de la URL y, si es igual a «yes
«, filtramos todas las entradas que cumplan esta condición.
Para probarlo, agregue registered=yes
a su endpoint:
Felicidades. Su primer CRUD API REST está listo.
Summary
- Creamos nuestro primer servidor REST
- Realizamos operaciones CRUD
- Manejamos los errores
- Ordenamos los datos utilizando parámetros de consulta
- Filtramos los datos utilizando parámetros de consulta