Criando uma API REST com Node.js e um Banco de Dados Oracle. Parte 5

Parte 5. Criando uma API REST: paginação, classificação manual e filtragem

No artigo anterior, você concluiu a criação da funcionalidade principal da API CRUD.

E agora, quando uma solicitação HTTP GET é emitida na rota de funcionários, todas as linhas da tabela são retornadas. Isso pode não importar muito com apenas 107 linhas na tabela HR.EMPLOYEES, mas imagine o que aconteceria se a tabela contivesse milhares ou milhões de linhas. Clientes como aplicativos móveis e da web geralmente exibem apenas uma fração das linhas disponíveis no banco de dados e, em seguida, selecionam mais linhas quando necessário - talvez quando o usuário rola para baixo ou clica no botão “Avançar” em algum controle de interrupção para páginas na interface do usuário.

Para isso, as APIs REST devem suportar ferramentas de paginação para resultados retornados. Depois que a paginação é suportada, os recursos de classificação se tornam necessários, pois os dados geralmente devem ser classificados antes da aplicação da paginação. Além disso, a ferramenta de filtragem de dados é muito importante para o desempenho. Por que enviar dados do banco de dados, através da camada intermediária e completamente para o cliente, se isso não for necessário?

Usarei os parâmetros da string de consulta da URL para que os clientes possam especificar como os resultados devem ser paginados, classificados e filtrados. Como sempre na programação, a implementação pode variar de acordo com seus requisitos, objetivos de desempenho, etc. Neste post, falarei sobre uma abordagem manual para adicionar essas funções à API.

Paginação

Parâmetros da string de consulta que usarei para paginação: pular e limitar. O parâmetro skip será usado para pular o número especificado de linhas, enquanto limit limitará o número de linhas retornadas. Usarei o valor padrão 30 para o limite se o cliente não fornecer o valor.

Comece atualizando a lógica do controlador para extrair valores da cadeia de consulta e passá-los para a API do banco de dados. Abra o arquivo controllers / employee.js e adicione as seguintes linhas de código à função get após a linha que analisa o 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); 

Agora você precisa atualizar a lógica do banco de dados para levar esses valores em consideração e atualizar a consulta SQL adequadamente. No SQL, a cláusula de deslocamento é usada para ignorar linhas e a cláusula de busca é usada para limitar o número de linhas retornadas pela consulta. Como de costume, os valores não serão adicionados diretamente à consulta - em vez disso, serão adicionados como variáveis ​​de ligação por motivos de desempenho e segurança. Abra db_apis / employee.js e adicione o seguinte código após o bloco if na função find, que adiciona a cláusula where à solicitação.

 // *** 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'; 

É tudo o que você precisa fazer para paginar! Inicie a API e execute alguns comandos de URL em outro terminal para testá-la. Aqui estão alguns exemplos que você pode 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" 

Classificação

No mínimo, os clientes devem poder especificar uma coluna para classificar e ordenar (crescente ou decrescente). A maneira mais fácil de fazer isso é definir um parâmetro de consulta (usarei a classificação), que permite passar uma string como 'last_name: asc' ou 'salário: desc'. A única maneira de garantir a ordem do conjunto de resultados retornado da consulta SQL é incluir a cláusula order by. Por esse motivo, seria bom ter uma definição de pedido padrão definida para garantir consistência quando o cliente não a especificar.

Volte para controllers / employee.js e inclua a seguinte linha de código na função get após a linha que analisa o parâmetro req.query.limit.

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

Em seguida, abra db_apis / employee.js e adicione a seguinte linha abaixo das linhas que declaram e inicializam o baseQuery.

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

sortableColumns é uma lista de permissões de colunas que os clientes podem usar para classificar. Em seguida, dentro da função find, adicione o seguinte bloco if, que adiciona a cláusula order by. Isso deve ser feito após a adição da cláusula where, mas antes das cláusulas offset e 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}`; } 

A primeira parte do bloco if verifica se o cliente passou o valor de classificação. Caso contrário, a cláusula padrão order by é adicionada à consulta SQL, que classifica por last_name em ordem crescente. Se um valor de classificação for especificado, ele será dividido primeiro em valores de coluna e ordem, e cada valor será verificado antes de adicionar ordem à consulta.

Agora você pode executar vários comandos de URL para validá-lo. Aqui estão alguns exemplos para tentar:

 # 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" 

Os dois últimos exemplos devem gerar exceções, pois contêm valores que não foram criados para a lista de permissões. Ele usa o manipulador de erros Express padrão, portanto, o erro é retornado como uma página da Web HTML.

Filtragem

A capacidade de filtrar dados é um recurso importante que todas as APIs REST devem fornecer. Como na classificação, a implementação pode ser simples ou complexa, dependendo do que você deseja oferecer suporte. A abordagem mais fácil é adicionar suporte a filtros de correspondência completa (por exemplo, last_name = Doe). Implementações mais complexas podem adicionar suporte para operadores básicos (por exemplo, <,>, instr etc.) e operadores lógicos complexos (por exemplo, e / ou) que podem agrupar vários filtros.

Neste post, tentarei simplificar a situação e adicionar suporte ao filtro para apenas duas colunas: department_id e manager_id. Para cada coluna, ativarei o parâmetro correspondente na sequência de consultas. A lógica do banco de dados que adiciona a cláusula where quando as solicitações GET são enviadas para o terminal com um único funcionário precisa ser atualizada para acomodar esses novos filtros.

Abra controllers / employee.js e adicione as seguintes linhas abaixo da linha que analisa o valor req.query.sort na função 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); 

Em seguida, edite db_apis / employee.js para adicionar a frase 1 = 1 à consulta base, como mostrado abaixo.

 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`; 

Obviamente, 1 = 1 sempre será verdadeiro; portanto, o otimizador simplesmente o ignorará. No entanto, esse método simplificará a adição de predicados adicionais no futuro.

Na função find, substitua o bloco if, que adiciona a cláusula where ao passar context.id, pelas seguintes linhas.

 // *** 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 você pode ver, cada bloco if simplesmente adiciona o valor passado ao objeto binds e, em seguida, adiciona o predicado correspondente à cláusula where. Salve as alterações e reinicie a API. Em seguida, use estes comandos de URL para verificar isso:

 # 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" 

É isso aí - a API agora suporta paginação, classificação e filtragem! Uma abordagem manual fornece muito controle, mas requer muito código. A função de pesquisa agora possui 58 linhas e suporta apenas recursos limitados de classificação e filtragem. Você pode considerar usar um módulo, como o construtor de consultas Knex.js. , para simplificar essas operações.

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


All Articles