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:

{ 
    "key": "ctrl+shift+space",            
    "command": "editor.action.triggerSuggest",
    "when": "editorHasCompletionItemProvider && textInputFocus && !editorReadonly" 
},
{ 
    "key": "ctrl+shift+space",            
    "command": "toggleSuggestionDetails",
    "when": "suggestWidgetVisible && textInputFocus" 
}

Estilo de código

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?

Instalar paquetes y configurar npm start

  • Instalamos express

  • Configuramos package.json para que nuestro servidor arranque mediante npm start

  • Utilizaremos Postmanarrow-up-right para testear nuestra API

    • Independiente de que hagamos tests por otro lado

  • Es una herramienta muy extendida

Comprobación API inicial

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 Postmanarrow-up-right

  • Podemos utilizar la extensión ExpressSnippetarrow-up-right 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

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

Ejemplo con body-parser

  • Instalación:

  • body-parser actúa como middleware

  • El código adicional será similar al siguiente:

Rutas de nuestra API

Rutas 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:

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 hubarrow-up-right

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

MongoDB: Aplicaciones gráficas

Robo3T: Conexiones a MongoDB

  • Robo3T guarda un listado de conexiones a MongoDB

Lista conexiones 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

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 eventosarrow-up-right

  • 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 Robo3Tarrow-up-right 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 ObjectIdarrow-up-right, 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

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

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-simplearrow-up-right 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íarrow-up-right)

    • 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