Api REST
Creación de una API
Configurar Visual Studio Code
Autocompletar código
Para que nos ayude a completar código usamos el atajo
ctrl+space
Si este atajo lo usamos para otra cosa (p.ej. Albert) debemos añadir los siguientes atajos a nuestro gusto:
Estilo de código
Puede que nuestro proyecto sea desarrollado por varias personas.
Queremos un estilo uniforme
Y si nos detecta fallos mejor
Vamos a ver que configuración necesitamos.
Podemos partir desde este boilerplate: https://github.com/rafacabeza/vscode_node_boilerplate
Prettier
Vamos a añadir el complemento Prettier (Esben Petersen)
vamos a añadir la dependencia al proyecto:
Vamos a modificar algunas pautas para que prettier uniformice nuestro código (js, css, html, json).
ESLint: ECMAScript Linter
Además ESLint nos va a ayudar a depurar el código: detectar errores y corregir algunos.
ESLint puede usarse desde la consola pero nosotros queremos usarlo desde el editor.
Su configuración puede ser compleja. La definimos en el fichero .eslintrc.json
Además puede requerir varias dependencias, entre otras cosas para que VSCode lo pueda usar.
Dependencias de desarrollo que añadiremos a nuestro proyecto:
No te olvides de ejecutar ahora:
npm install
Crearemos el fichero .eslintrc.json con este contenido:
Las reglas las podemos añadir/modificar en rules
Vamos a usar el standard de codificación de airbnb.
Para ficheros javascript, eslin se ocupará de ejecutar prettier
Configurar VS Code
Nos falta añadir algunas reglas para que nuestro editor use los pluggins instalados:
Que formatee al salvar, salvo el javascript.
Que active ESLint para notificar errores de código (menu view->problems)
Que arregle nuestro javascript al salvar (esto incluye pasar prettier)
Esto lo podemos hacer en el json general de VSCode
O sólo para el proyecto actual guardando el json en un fichero llamado .vscode/settings.json
Contenido guardado en .vscode/settings.json
Descripción
Creación de una API REST estándar y completa que pueda clonarse para distintos proyectos
Objetivos
Conocer el objeto Router de express
Conocer arquitecturas MVC
Familiarizarse con base de datos no relacional
Comprender las ventajas en JavaScript de MongoDB frente a bases de datos relacionales
Primeros pasos
TODO: ¿Fork o scratch?
Clonar tu repositorio
Inicializar proyecto con npm init
Instalar y configurar eslint extendiendo de standard:
Personalizar eslint utilizando el fichero .eslintrc.json
Instalar paquetes y configurar npm start
Instalamos express
Configuramos package.json para que nuestro servidor arranque mediante npm start
Utilizaremos Postman para testear nuestra API
Independiente de que hagamos tests por otro lado
Es una herramienta muy extendida
Comprobación API inicial
Arranca la aplicación y comprueba su funcionamiento mediante Postman
Implementación API: Routers y parámetros
Añadir rutas
Añade la ruta POST /cervezas con respuesta:
Añade la ruta DELETE /cervezas con respuesta:
Muestra el mensaje API escuchando en el puerto 8080 justo cuando se levante el puerto
Comprueba funcionamiento mediante Postman
Podemos utilizar la extensión ExpressSnippet para autocompletado.
Commit con las nuevas rutas
Hagamos un commit del repositorio, sin la carpeta node_modules
Debemos hacer nuevas instantáneas (commits) en cada paso
Aquí no se documentarán por brevedad
Aquí hemos mezclado un parche con una funcionalidad nueva. ¡No es lo correcto!
nodemon
Es un wrapper de node, para reiniciar nuestro API Server cada vez que detecte modificaciones.
Cada vez que ejecutemos npm start ejecutaremos nodemon en vez de node. Habrá que cambiar el script en el fichero package.json:
Uso de enrutadores
Normalmente una API:
Tiene varios recursos (cada uno con múltiples endpoints)
Sufre modificaciones -> mantener versiones
Asociamos enrutadores a la app en vez de rutas como hasta ahora:
Cada enrutador se asocia a un recurso y a un fichero específico.
Se pueden anidar enrutadores (router.use)
El código para un enrutador sería así:
Configura enrutadores
Crea un enrutador para el versionado de la API:
Ruta GET /api (simulará el versionado de la api):
Crea un enrutador anidado para los endpoints de las cervezas
GET /api/cervezas
POST /api/cervezas
...
server.js con enrutador
Enrutador base para el versionado
Enrutador cervezas
Envio de parámetros
Cuando el router recibe una petición, podemos observar que ejecuta una función de callback:
El parámetro req representa la petición (request)
Aquí es donde se recibe el parámetro
Tipos de envio de parámetros
Mediante la url
Se recogerán mediante:
Mediante post en http hay dos posiblidades:
application/x-www-form-urlencoded ([pocos datos]((http://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data))
multipart/form-data ([muchos datos ¿ficheros?]((http://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data))
Parámetros por url
Vamos a mandar un parámetro nombre a nuestra api, de modo que nos de un saludo personalizado.
La variable podría acabar en ? (parámetro opcional)
Si mandamos una url del tipo:
El parámetro se recoge mediante req.query:
Parámetros por post
¡Necesitamos middlewares!
application/x-www-form-urlencoded:
body-parser: extrae los datos del body y los convierte en json
Ejemplo con body-parser
Instalación:
body-parser actúa como middleware
El código adicional será similar al siguiente:
Rutas de nuestra API
Enrutado del recurso cervezas
Intenta configurar una API básica para el recurso cervezas en base a las rutas anteriores
Muestra por consola el tipo de petición
Muestra por consola el parámetro de entrada
Solución enrutado recurso cervezas
Fichero app/routes/cervezas.js:
Arquitectura
Situación actual
Se han definido recursos
Cervezas, pero podría haber muchos más
Cada recurso se asocia a un enrutador
Por el momento routes/cervezas
A cada enrutador se asocian las rutas del recurso
Arquitectura MVC
Todas las rutas de un recurso se gestionan por un controlador.
Cada ruta por un método del mismo
El controlador es responsable de:
Acceder a los datos para leer o guardar
Delega en un modelo
Utilizaremos un ODM
Mongoose para MongoDB
Enviar datos de vuelta
Al ser JSON, lo haremos directamente desde el controller
No necesitamos la capa Vista :-)
Fat model, thin controller
El controlador recoge la lógica de negocio.
En nuestro caso es muy sencillo:
Recoge parámetros
Llama al modelo (obtener datos, guardar)
Devuelve json
El modelo puede ser complejo.
Por ej. creación de tokens o encriptación de passwords en el modelo de Usuario
Es un código que puede ser reutilizado entre controladores
Uso de controladores
Desde nuestro fichero de rutas (app/routes/cervezas.js), se llama a un controlador, encargado de añadir, borrar o modificar cervezas.
Endpoint -> Recurso -> Fichero de rutas -> Controlador -> Modelo
Creamos un directorio específico para los controladores (app/controllers)
Un controlador específico para cada recurso, por ej. (app/controllers/cervezasController.js):
Un método en el controlador por cada endpoint del recurso
Mi primer método del controlador
Vamos a crear el método index para que nos devuelva la lista de todas las cervezas.
De entrada creamos nuestro controlador que devuelva un mensaje simple:
Tarea:
Realiza los otros métodos básicos para realizar un CRUD sobre cervezas:
show
para ver un elemento, ruta GET (/cervezas/:id)store
para crear, ruta POST (/cervezas)update
para actualizar, ruta PUT (/cervezas/:id)update
para actualizar, ruta PUT (/cervezas/:id)
Modelos
Llega el momento de que el modelo entre es escena.
Se trata de recoger los datos en un objeto
Y de su persistencia en BBDD: leer, borrar, crear y actualizar
Conexión:
Nuestro modelo necesita usar una conexión a base de datos
Usaremos el módulo node-mysql2
Configuración del controlador Cervezas
Debemos definir los métodos siguientes:
search
list
show
create
update
remove
Listar cervezas
Desde el controlador:
Listar cervezas con el modelo
Al separar el acceso a la base de datos tenemos un problema
¿Cómo accedemos a los datos obtenidos en una función asícrona?
solución: una función de callback debe devolver sus datos a través de otra función de callback.
Modelo
Controlador
Mostrar una cerveza
Crear una cerveza
Actualizar cerveza
Borrar cerveza
MongoDB
Sistema de base de datos NoSQL
Guarda los datos en BSON (Binary JSON)
Es ideal para usarlo con Javascript
Es muy usado en aplicaciones web: pila MEAN
MongoDb, Express, Angular, Node.Js
Instalación de MongoDB
Utilizaremos contenedores docker:
Eliminamos conflictos en la máquina base
Podemos cambiar de versiones con facilidad
Nuestro proyecto es más portable
Fichero de instalación mediante docker
Definiremos un fichero docker-compose.yml
Para ver que imagen necesitamos podemos consultar en el docker hub
Uso básico de MongoDb por consola
Accedemos al contenedor:
Usar/crear una base de datos
Ver la o las bases de datos
Colecciones (Tablas)
Creamos una colección de forma implícita
O explícita
db.createCollection("cervezas") //crear coleccion
La borramos con drop:
Consultar, filtrar y ordenar:
modificar:
Borrar:
MongoDB: Aplicaciones gráficas
Vamos a usar MongoDB Compass
MongoDB: Aplicaciones gráficas
TAmbién podríamos usar Robo3T
Antes llamado Robomongo
El más extendido
Robo3T: Conexiones a MongoDB
Robo3T guarda un listado de conexiones a MongoDB
Robo3t: Configurar conexión a MongoDB
Creamos una nueva conexión a localhost y al puerto que hemos mapeado en Docker (27017)
Conceptos en noSQL
Schema en noSQL
Inserción de datos
Utilizaremos el fichero cervezas.json, de la carpeta data
Importar nuestro cervezas.json a una base de datos
Otra opción es mediante Robomongo:
Para hacer una búsqueda por varios campos de texto, tendremos que hacer un índice:
Comprobamos que el índice esté bien creado
Modelos con Mongo: Mongoose
Instalaremos mongoose en vez de trabajar con el driver nativo de MongoDB (se utiliza por debajo).
Mongoose es un ODM (Object Document Mapper).
Equivale a los ORM como Hibernate en Java o Eloquent en Php/Laravel
Usar Mongoose
Instalación
Probamos la conexión en nuestro código
Creamos el fichero app/db.js donde configuraremos nuestra conexión a base de datos mediante mongoose:
El código de la conexión usa eventos
Nuestro conector se prepara para recibir eventos con ".on". Los eventos los genera mongoose o al cerrar el proceso.
En nuestro fichero app/server.js incluimos el fichero de configuración de bbdd:
Observa que no es necesario asignar el módulo a una constante
El módulo solo abre conexión con la bbdd
Registra eventos de mongodb
No exporta nada
La conexión a bbdd se queda abierta durante todo el funcionamiento de la aplicación:
Las conexiones TCP son caras en tiempo y memoria
Se reutiliza
Modelos
Un modelo mongoose debe definir un esquema
Fichero app/models/v2/Cerveza.js):
Ahora podríamos crear documentos y guardarlos en la base de datos
Guardar documento
Crea una cerveza nueva con todos los campos
Comprueba por consola o desde Robo3T que en nuevo documento aparece en la colección Cervezas
Solución guardar documento
Uso de controladores
Vamos a crear una vesión 2 de nuestra API, esta con MongoDb
En nuestro fichero de rutas routes.js:
Creamos un directorio para los controladores v2 (app/controllers/v2)
Un controlador, en este caso (app/controllers/v2/cervezasController.js):
Un método en el controlador por cada endpoint del recurso
Configuración final del router Cervezas
Implementación del controlador
Test desde el navegador o mediante Postman
Comprobamos que se genera el listado de cervezas
Comprobamos que se busca por keyword:
...
Configuración del controlador Cervezas
Debemos definir los métodos siguientes:
search
list
show
create
update
remove
Listar cervezas
Listar cervezas por palabra clave
Mostrar una cerveza
BSON (datos de MongoDb) define el tipo usado para el campo _id
Su nombre es ObjectId, 12b en hexadecimal
Crear una cerveza
Actualizar cerveza
Borrar cerveza
Mongoose II: La mangosta
Hemos completado el ciclo CRUD pero hemos pasado de puntillas sobre Mongo y Mongoose
Vamos a estudiar como construir nuestras propias colecciones y modelos Mongo/Mongoose
Modelos y esquemas
Mongoose es un Object Document Mapper (ODM).
Permite definir objetos con un esquema fuertemente tipado que se asigna a un documento MongoDB.
Mongoose actualmente contiene ocho tipos de datos o SchemaTypes
Son:
Date (Fecha)
Buffer
Boolean (Booleano)
Mixed (Mixto)
ObjectId
Array (Matriz)
Sobre cada tipo podemos definir:
un valor predeterminado
una función de validación personalizada
indicar si es requerido
una función get que le permite manipular los datos antes de que se devuelva como un objeto
una función de conjunto que le permite manipular los datos antes de guardarlos en la base de datos
crear índices para permitir que los datos se obtengan más rápido
Sobre los String podemos:
convertirlo a minúsculas
convertirlo a mayúsculas
recortar datos antes de guardar
una expresión regular que puede limitar los datos que se pueden guardar durante el proceso de validación
una enumeración que puede definir una lista de cadenas que son válidas
Buffer
Tipo de datos binario
Por ejemplo para guardar un pdf o una foto en Mongo
Mixed
Todo vale
Ojo! perdemos posibilidades con su uso como el de la validación
ObjectId
Campo de 12B usado para identificar registrso. Se compone de :
4B timestap desde época unix
5B aleatorios
3B contador (inicio aleatorio)
Array
El tipo de datos Array le permite almacenar matrices similares a JavaScript.
Con un tipo de datos Array, podemos realizar operaciones de matriz JavaScript como push, pop, shift, slice, etc.
Definir un esquema
Ejemplo
Podemos usar el nombre como un objeto y añadir una fecha de creación:
Ejemplo más completo:
Crear y guardar modelos
Añadimos validación:
El fichero de modelo
Validadores (validators)
Todos los tipos tienen incorporado el validador required
Los números tienen min y max
Los textos (String), enum, match, minlength, and maxlength validators.
Crear documentos:
Actualización
Buscar + modificar + salvar
O todo en uno con findByIdAndUpdate
Ejercicio 1:
Crea una una colección de tipos de productos (families) en la BBDD web
Crea crea un modelo para dicha coleccion.
code: código, requerido, texto de longitud máxima 4 caracteres
name: requerido, máximo 20 caracteres
Crea las rutas y el controlador para gestionar los productos.
Ejercicio 2:
Crea una una colección productos (products) en la BBDD web
Crea crea un modelo para dicha coleccion.
name: requerido, máximo 30 caracteres
price: requerido, numérico
description: máximo 255 caracteres.
created: fecha de creación
family: requerido, relación con el modelo de familias.
Crea las rutas y el controlador para gestionar los productos.
Usa populate para mostrar los datos de la familia en el detalle y en la lista de productos.
Autenticación JWT
RESTful define arquitecturas sin estado.
Sin estado implica no guardar en el servidor ninguna estructura de datos de sesión ni usar cookies.
La autentinticación en este entorno está basada en tokens.
Una implementación muy habitual es Json Web Token (JWT)
Para llevar a cabo el uso de JWT necesitamos algunas dependencias:
jwt-simple, paquete que gestiona los jwt
bcryptjs , encriptado
moment , uso de fechas
Debemos intalar todas ellas con "npm i -S ..."
Login: rutas y controlador
Necesitamos crear un fichero de rutas y un controlador.
Rutas:
Y controlador (probar):
El modelo de Usuario
Vamos a realizar autenticación basada en email + contraseña
La contraseña debe ir encriptada con bcrypt
El email debe ser único y en minúsculas
Además guardaremos:
Un nombre de usuario
Fecha de creación
Fecha de último login
Nuestro modelo: imports/exports
Nuestro modelo: esquema
Modelos y middleware
Son funciones invocadas antes o después (pre o post) de la ejecución de funciones asíncronas.
Un documento cuenta con las siguientes funciones:
validate
save
remove
init
Podemos añadir funciones pro/post de las anteriores
Este código lo añadiremos al modelo.
Nosotros necesitamos que antes de salvar un nuevo usuario genere un nuevo token.
Nuestro caso: middleware pre - al salvar
Hacemos uso de los middleware de Mongoose
NOTA: no usar aquí función flecha.
Registro y Login
Controlador userController
Función register en el controlador:
Emitir el token
Vamos a crear una función en el fichero '/services/servicejwt.js'
Mediante la librería jwt-simple vamos a generar nuestro token.
Ojo!! necesitamos crear el fichero de configuración config/config.js
Proteger rutas:
El token transporta de forma cifrada información del usuario.
Vamos a añadir una función de descifrado en el auth
Esa función la vamos a usar en un middleware asociado a las rutas
Añadimos este middleware en el enrutador de cervezas o productos. Así protegemos todas las rutas:
Mejor refactorizar en una función :
Y ahora lo usamos:
Tarea: Login
Una vez registrado tenemos acceso para cierto periodo
Nos interesa generar nuevos tokens (login) en nuevas máquinas o tras caducar el que tenemos.
Debemos implementar un método login para obtener un nuevo token.
Recibimos usuario + contraseña
Verificamos si la contraseña es válida. Para hacerlo debemos comparar contraseñas (busca aquí)
Si es válida generamos un token y lo entregamos
Si no status 401
Ejercicio 3:
Crea un directorio middlewares y crea un middleware llamado auth.js
Coloca en él el código de autenticación
Refactoriza el controlador de cervezas para que use ese middleware
Úsa el middleware en el controlador de productos.
Asincronía: callbacks, promesas y async-await
Last updated