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

Parte 5. Crear una API REST: paginación, clasificación manual y filtrado

En el artículo anterior, completó la creación de la funcionalidad principal de la API CRUD.

Y ahora, cuando se emite una solicitud HTTP GET en la ruta de los empleados, se devuelven todas las filas de la tabla. Esto puede no importar mucho con solo 107 filas en la tabla HR.EMPLOYEES, pero imagine lo que sucedería si la tabla contuviera miles o millones de filas. Los clientes, como las aplicaciones móviles y web, generalmente muestran solo una fracción de las filas disponibles en la base de datos y luego seleccionan más filas cuando es necesario, tal vez cuando el usuario se desplaza hacia abajo o hace clic en el botón "Siguiente" en algún control de interrupción a páginas en la interfaz de usuario.

Para esto, las API REST deben admitir herramientas de paginación para los resultados devueltos. Una vez que se admite la paginación, las capacidades de clasificación se vuelven necesarias, ya que los datos generalmente se deben clasificar antes de aplicar la paginación. Además, la herramienta de filtrado de datos es muy importante para el rendimiento. ¿Por qué enviar datos desde la base de datos, a través de la capa intermedia y completamente al cliente, si esto no es necesario?

Usaré los parámetros de cadena de consulta de URL para que los clientes puedan especificar cómo se deben paginar, clasificar y filtrar los resultados. Como siempre en la programación, la implementación puede variar según sus requisitos, objetivos de rendimiento, etc. En esta publicación le contaré acerca de un enfoque manual para agregar estas funciones a la API.

Paginación

Consultar los parámetros de cadena que usaré para la paginación: omitir y limitar. El parámetro de omisión se usará para omitir el número especificado de líneas, mientras que el límite limitará el número de líneas devueltas. Usaré el valor predeterminado de 30 para el límite si el cliente no proporciona el valor.

Comience actualizando la lógica del controlador para extraer valores de la cadena de consulta y pasarlos a la API de la base de datos. Abra el archivo controllers / employee.js y agregue las siguientes líneas de código a la función get después de la línea que analiza el parámetro req.params.id.

// *** line that parses req.params.id is here *** context.skip = parseInt(req.query.skip, 10); context.limit = parseInt(req.query.limit, 10); 

Ahora necesita actualizar la lógica de la base de datos para tener en cuenta estos valores y actualizar la consulta SQL en consecuencia. En SQL, la cláusula offset se usa para omitir filas, y la cláusula fetch se usa para limitar el número de filas devueltas por la consulta. Como de costumbre, los valores no se agregarán directamente a la consulta; en cambio, se agregarán como variables de enlace por razones de rendimiento y seguridad. Abra db_apis / employee.js y agregue el siguiente código después del bloque if en la función find, que agrega la cláusula where a la solicitud.

 // *** if block that appends where clause ends here *** if (context.skip) { binds.row_offset = context.skip; query += '\noffset :row_offset rows'; } const limit = (context.limit > 0) ? context.limit : 30; binds.row_limit = limit; query += '\nfetch next :row_limit rows only'; 

¡Esto es todo lo que necesitas hacer para paginar! Inicie la API y luego ejecute algunos comandos de URL en otra terminal para probarla. Aquí hay algunos ejemplos que puede usar:

 # use default limit (30) curl "http://localhost:3000/api/employees" # set limit to 5 curl "http://localhost:3000/api/employees?limit=5" # use default limit and set skip to 5 curl "http://localhost:3000/api/employees?skip=5" # set both skip and limit to 5 curl "http://localhost:3000/api/employees?skip=5&limit=5" 

Clasificación

Como mínimo, los clientes deben poder especificar una columna para ordenar y ordenar (ascendente o descendente). La forma más fácil de hacer esto es definir un parámetro de consulta (usaré sort), que le permite pasar una cadena como 'last_name: asc' o 'salario: desc'. La única forma de garantizar el orden del conjunto de resultados devuelto por la consulta SQL es incluir el orden por cláusula. Por esta razón, sería bueno tener una definición de orden predeterminada definida para garantizar la coherencia cuando el cliente no la especifica.

Regrese a controllers / employee.js y agregue la siguiente línea de código a la función get después de la línea que analiza el parámetro req.query.limit.

 // *** line that parses req.query.limit is here *** context.sort = req.query.sort; 

Luego abra db_apis / employee.js y agregue la siguiente línea debajo de las líneas que declaran e inicializan baseQuery.

 // *** lines that initalize baseQuery end here *** const sortableColumns = ['id', 'last_name', 'email', 'hire_date', 'salary']; 

sortableColumns es una lista blanca de columnas que los clientes pueden usar para ordenar. Luego, dentro de la función de búsqueda, agregue el siguiente bloque if, que agrega el orden por cláusula. Esto debe hacerse después de agregar la cláusula where, pero antes de las cláusulas offset y fetch.

 // *** if block that appends where clause ends here *** if (context.sort === undefined) { query += '\norder by last_name asc'; } else { let [column, order] = context.sort.split(':'); if (!sortableColumns.includes(column)) { throw new Error('Invalid "sort" column'); } if (order === undefined) { order = 'asc'; } if (order !== 'asc' && order !== 'desc') { throw new Error('Invalid "sort" order'); } query += `\norder by "${column}" ${order}`; } 

La primera parte del bloque if verifica si el cliente pasó el valor de clasificación. De lo contrario, el orden predeterminado por cláusula se agrega a la consulta SQL, que se ordena por apellido en orden ascendente. Si se especifica un valor de clasificación, primero se divide en valores de columna y orden, y cada valor se verifica antes de agregar el orden por a la consulta.

Ahora puede ejecutar varios comandos de URL para validarlo. Aquí hay algunos ejemplos para probar:

 # use default sort (last_name asc) curl "http://localhost:3000/api/employees" # sort by id and use default direction (asc) curl "http://localhost:3000/api/employees?sort=id" # sort by hire_date desc curl "http://localhost:3000/api/employees?sort=hire_date:desc" # use sort with limit and skip together curl "http://localhost:3000/api/employees?limit=5&skip=5&sort=salary:desc" # should throw an error because first_name is not whitelisted curl "http://localhost:3000/api/employees?sort=first_name:desc" # should throw an error because 'other' is not a valid order curl "http://localhost:3000/api/employees?sort=last_name:other" 

Los últimos dos ejemplos deberían arrojar excepciones, porque contienen valores que no se hicieron en la lista blanca. Utiliza el controlador de errores Express estándar, por lo que el error se devuelve como una página web HTML.

Filtrado

La capacidad de filtrar datos es una característica importante que todas las API REST deben proporcionar. Al igual que con la ordenación, la implementación puede ser simple o compleja según lo que desee admitir. El enfoque más fácil es agregar soporte para filtros de coincidencia completa (por ejemplo, last_name = Doe). Las implementaciones más complejas pueden agregar soporte para operadores básicos (por ejemplo, <,>, instr, etc.) y operadores lógicos complejos (por ejemplo, y / o) que pueden agrupar varios filtros.

En esta publicación intentaré simplificar la situación y agregar soporte de filtro para solo dos columnas: department_id y manager_id. Para cada columna, habilitaré el parámetro correspondiente en la cadena de consulta. La lógica de la base de datos que agrega la cláusula where cuando las solicitudes GET se envían al punto final con un solo empleado debe actualizarse para tener en cuenta estos nuevos filtros.

Abra controllers / employee.js y agregue las siguientes líneas debajo de la línea que analiza el valor req.query.sort en la función get.

 // *** line that parses req.query.sort is here *** context.department_id = parseInt(req.query.department_id, 10); context.manager_id = parseInt(req.query.manager_id, 10); 

Luego edite db_apis / employee.js para agregar la oración 1 = 1 a la consulta base, como se muestra a continuación.

 const baseQuery = `select employee_id "id", 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" from employees where 1 = 1`; 

Por supuesto, 1 = 1 siempre será verdadero, por lo que el optimizador simplemente lo ignorará. Sin embargo, este método simplificará la adición de predicados adicionales en el futuro.

En la función de búsqueda, reemplace el bloque if, que agrega la cláusula where al pasar context.id, con las siguientes líneas.

 // *** line that declares 'binds' is here *** if (context.id) { binds.employee_id = context.id; query += '\nand employee_id = :employee_id'; } if (context.department_id) { binds.department_id = context.department_id; query += '\nand department_id = :department_id'; } if (context.manager_id) { binds.manager_id = context.manager_id; query += '\nand manager_id = :manager_id'; } 

Como puede ver, cada bloque if simplemente agrega el valor pasado al objeto binds, y luego agrega el predicado correspondiente a la cláusula where. Guarde los cambios y reinicie la API. Luego use estos comandos de URL para verificar esto:

 # filter where department_id = 90 (returns 3 employees) curl "http://localhost:3000/api/employees?department_id=90" # filter where manager_id = 100 (returns 14 employees) curl "http://localhost:3000/api/employees?manager_id=100" # filter where department_id = 90 and manager_id = 100 (returns 2 employees) curl "http://localhost:3000/api/employees?department_id=90&manager_id=100" 

Eso es todo: ¡la API ahora admite paginación, clasificación y filtrado! Un enfoque manual proporciona mucho control, pero requiere mucho código. La función de búsqueda ahora tiene 58 líneas y solo admite capacidades limitadas de clasificación y filtrado. Puede considerar usar un módulo, como el generador de consultas Knex.js , para simplificar estas operaciones.

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


All Articles