Crear una API REST con Node.js y una Base de datos Oracle. Parte 4

Parte 4. Crear una API REST: manejo de solicitudes POST, PUT y DELETE

En el artículo anterior, agregó lógica a la API para solicitudes GET que recuperaron datos de una base de datos. En esta publicación, completará la funcionalidad básica de la API CRUD agregando lógica para manejar las solicitudes POST, PUT y DELETE.

Agregar lógica de enrutamiento

Para simplificar la lógica de enrutamiento, redirigirá todos los métodos HTTP a través de una ruta existente (con un parámetro de identificación opcional). Abra el archivo services / router.js y reemplace la lógica de enrutamiento actual (líneas 5-6) con el siguiente código:

router.route('/employees/:id?') .get(employees.get) .post(employees.post) .put(employees.put) .delete(employees.delete); 

La lógica de enrutamiento actualizada asigna los cuatro métodos HTTP más comunes utilizados para las operaciones CRUD básicas a la lógica correcta del controlador.

Procesamiento de solicitud POST

Las solicitudes HTTP POST se utilizan para crear nuevos recursos (en este caso, registros de empleados). La idea principal es extraer datos del cuerpo de la solicitud HTTP y usarlos para crear una nueva fila en la base de datos. Para agregar lógica de controlador para solicitudes POST, abra el archivo controllers / employee.js y agregue el siguiente código:

 function getEmployeeFromRec(req) { const employee = { first_name: req.body.first_name, last_name: req.body.last_name, email: req.body.email, phone_number: req.body.phone_number, hire_date: req.body.hire_date, job_id: req.body.job_id, salary: req.body.salary, commission_pct: req.body.commission_pct, manager_id: req.body.manager_id, department_id: req.body.department_id }; return employee; } async function post(req, res, next) { try { let employee = getEmployeeFromRec(req); employee = await employees.create(employee); res.status(201).json(employee); } catch (err) { next(err); } } module.exports.post = post; 

La función getEmployeeFromRec toma un objeto de solicitud y devuelve un objeto con las propiedades necesarias para crear un registro de empleado. La función se declaró fuera de la función de publicación para poder usarla más tarde para las solicitudes PUT.

La función post usa getEmployeeFromRec para inicializar la variable, que luego se pasa al método de creación de la API de la base de datos de empleados. Después de la operación de creación, el código de estado "201 Creado" se envía al cliente junto con el JSON del empleado (incluido el nuevo valor de identificador del empleado).

Ahora puede prestar atención a la lógica en la API de la base de datos. Abra el archivo db_apis / employee.js y agregue el siguiente código a continuación.

 const createSql = `insert into employees ( first_name, last_name, email, phone_number, hire_date, job_id, salary, commission_pct, manager_id, department_id ) values ( :first_name, :last_name, :email, :phone_number, :hire_date, :job_id, :salary, :commission_pct, :manager_id, :department_id ) returning employee_id into :employee_id`; async function create(emp) { const employee = Object.assign({}, emp); employee.employee_id = { dir: oracledb.BIND_OUT, type: oracledb.NUMBER } const result = await database.simpleExecute(createSql, employee); employee.employee_id = result.outBinds.employee_id[0]; return employee; } module.exports.create = create; 

La lógica anterior comienza declarando una constante llamada createSql para almacenar la instrucción de inserción. Tenga en cuenta que utiliza variables de enlace , no concatenación de cadenas, para referirse a los valores que se insertarán. Vale la pena repetir la importancia de las variables de enlace por razones de seguridad y rendimiento. Intente evitar la concatenación de cadenas siempre que sea posible.

Dentro de la función de creación, la constante del empleado se define e inicializa para una copia del parámetro emp usando Object.assign. Esto evita la modificación directa del objeto transferido desde el controlador.

Luego, la propiedad employee_id se agrega al objeto de empleado (configurado como "out bind") para que contenga todas las variables de enlace necesarias para ejecutar la instrucción SQL. Luego, la función simpleExecute se usa para ejecutar la instrucción de inserción, y la propiedad outBinds se usa para sobrescribir la propiedad employee.employee_id antes de devolver el objeto.

Dado que hay un enlace al módulo oracledb, debe agregar la siguiente línea en la parte superior del archivo.
 const oracledb = require('oracledb'); 


PUT Procesamiento de solicitudes

Las solicitudes PUT se utilizarán para actualizar los recursos existentes. Es importante tener en cuenta que PUT se usa para reemplazar todo el recurso; no realiza actualizaciones parciales (en el futuro le mostraré cómo hacer esto con PATCH). Regrese al archivo controllers / employee.js y agregue el siguiente código a continuación.

 async function put(req, res, next) { try { let employee = getEmployeeFromRec(req); employee.employee_id = parseInt(req.params.id, 10); employee = await employees.update(employee); if (employee !== null) { res.status(200).json(employee); } else { res.status(404).end(); } } catch (err) { next(err); } } module.exports.put = put; 

La función put usa getEmployeeFromRec para inicializar un objeto llamado employee, y luego agrega la propiedad employee_id del parámetro id a la URL. Luego, el objeto empleado se pasa a la función de actualización de la API de la base de datos y la respuesta correspondiente se envía al cliente en función del resultado.

Para agregar la lógica de actualización a la API de la base de datos, agregue el siguiente código al archivo db_apis / employee.js .

 const updateSql = `update employees set first_name = :first_name, last_name = :last_name, email = :email, phone_number = :phone_number, hire_date = :hire_date, job_id = :job_id, salary = :salary, commission_pct = :commission_pct, manager_id = :manager_id, department_id = :department_id where employee_id = :employee_id`; async function update(emp) { const employee = Object.assign({}, emp); const result = await database.simpleExecute(updateSql, employee); if (result.rowsAffected && result.rowsAffected === 1) { return employee; } else { return null; } } module.exports.update = update; 

La lógica de actualización es muy similar a la lógica de creación. Se declara una variable para almacenar la instrucción SQL, y luego se usa simpleExecute para ejecutar la instrucción con los valores dinámicos transferidos (después de copiarlos en otro objeto). result.rowsAffected se usa para determinar si la actualización fue exitosa y devolver el valor correcto.

ELIMINAR el procesamiento de solicitudes

El último método que necesita implementar es DELETE, que, como era de esperar, eliminará recursos de la base de datos. Agregue el siguiente código al final del archivo controllers / employee.js.

 async function del(req, res, next) { try { const id = parseInt(req.params.id, 10); const success = await employees.delete(id); if (success) { res.status(204).end(); } else { res.status(404).end(); } } catch (err) { next(err); } } module.exports.delete = del; 

El motor de JavaScript arrojará una excepción si intenta declarar una función llamada "eliminar" utilizando el operador de función. Para evitar esto, se declara una función llamada "del" y luego se exporta como "eliminar".

En este punto, debería poder leer y comprender la lógica. A diferencia de los ejemplos anteriores, esta solicitud HTTP no tiene cuerpo; solo se usa el parámetro id en la ruta de la ruta. El código de estado "204 Sin contenido" se usa a menudo con solicitudes DELETE cuando no se envía el cuerpo de respuesta.

Para completar la lógica de la base de datos, regrese al archivo db_apis / employee.js y agregue el siguiente código al final.

 const deleteSql = `begin delete from job_history where employee_id = :employee_id; delete from employees where employee_id = :employee_id; :rowcount := sql%rowcount; end;` async function del(id) { const binds = { employee_id: id, rowcount: { dir: oracledb.BIND_OUT, type: oracledb.NUMBER } } const result = await database.simpleExecute(deleteSql, binds); return result.outBinds.rowcount === 1; } module.exports.delete = del; 

Debido a que la tabla JOB_HISTORY tiene una restricción de clave externa que se refiere a la tabla EMPLOYEES, se usa un bloque PL / SQL simple para eliminar las filas necesarias de ambas tablas en un solo ciclo.

Analizando solicitudes JSON

Si observa la función getEmployeeFromRec en controllers / employee.js, notará que la propiedad del cuerpo de la solicitud es un objeto JavaScript. Esto proporciona una manera fácil de obtener valores del cuerpo de la solicitud, pero esto no sucede automáticamente.

La API que está creando espera que los clientes envíen datos en formato JSON en el cuerpo de las solicitudes POST y PUT. Además, los clientes deben establecer el encabezado Content-Type de la solicitud en application / json para que el servidor web sepa qué tipo de cuerpo de solicitud se envía. Puede usar el middleware incorporado express.json para que Express pueda analizar tales solicitudes.

Abra el archivo services / web-server.js y agregue las siguientes líneas justo debajo de la llamada app.use, que agrega Morgan a la canalización de solicitudes.

 // Parse incoming JSON requests and revive JSON. app.use(express.json({ reviver: reviveJson })); 

Cuando los datos JSON se analizan en objetos JavaScript nativos, solo los tipos de datos admitidos en JSON se asignarán correctamente a los tipos JavaScript. Las fechas no son compatibles con JSON y, por lo general, se representan como cadenas ISO 8601. Al usar la función reviver que pasó al middleware express.json, puede realizar la conversión manualmente. Agregue el siguiente código al final del archivo services / web-server.js .

 const iso8601RegExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/; function reviveJson(key, value) { // revive ISO 8601 date strings to instances of Date if (typeof value === 'string' && iso8601RegExp.test(value)) { return new Date(value); } else { return value; } } 

Prueba de API

¡Es hora de probar la nueva funcionalidad CRUD! Hasta ahora, ha utilizado el navegador para probar la API, pero esto no funcionará para las solicitudes POST, PUT y DELETE. Le mostraré cómo probar la API utilizando comandos curl porque es fácilmente accesible en una máquina virtual. Pero puede usar una herramienta gráfica como Postman , Insomina (gratis).

Inicie la aplicación y luego abra otra ventana de terminal y ejecute el siguiente comando para crear un nuevo empleado.

 curl -X "POST" "http://localhost:3000/api/employees" \ -i \ -H 'Content-Type: application/json' \ -d $'{ "first_name": "Dan", "last_name": "McGhan", "email": "dan@fakemail.com", "job_id": "IT_PROG", "hire_date": "2014-12-14T00:00:00.000Z", "phone_number": "123-321-1234" }' 

Si la solicitud fue exitosa, la respuesta debe contener un objeto de empleado con el atributo employee_id. Aquí hay un ejemplo:

imagen

En mi caso, el valor de employee_id era 227; deberá cambiar los siguientes comandos según el valor recibido de employee_id.

Por ejemplo, para actualizar una nueva entrada, ingrese PUT para la URL con este valor de identificador.

 curl -X "PUT" "http://localhost:3000/api/employees/227" \ -i \ -H 'Content-Type: application/json' \ -d $'{ "first_name": "Dan", "last_name": "McGhan", "email": "dan@fakemail.com", "job_id": "AD_PRES", "hire_date": "2014-12-14T00:00:00.000Z", "phone_number": "123-321-1234" }' 

El activador UPDATE_JOB_HISTORY en el esquema HR detectará un cambio de trabajo y agregará una fila a la tabla JOB_HISTORY. Si observa esta tabla, debería ver un registro del nuevo empleado. Ejecute el siguiente comando para eliminar el historial de trabajos y los registros de empleados.

 curl -i -X "DELETE" "http://localhost:3000/api/employees/227" 


¡Y ahora, tiene todo, funcionalidad CRUD completa!

La API está progresando bien, pero hay trabajo por hacer. En la última publicación, le mostraré cómo agregar capacidades de paginación, clasificación y filtrado en las solicitudes GET.

Source: https://habr.com/ru/post/473560/


All Articles