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

Parte 4. Criando uma API REST: Manipulando Solicitações POST, PUT e DELETE

No artigo anterior, você adicionou lógica à API para solicitações GET que recuperaram dados de um banco de dados. Nesta postagem, você completará a funcionalidade básica da API CRUD adicionando lógica para lidar com solicitações POST, PUT e DELETE.

Adicionando lógica de roteamento

Para simplificar a lógica de roteamento, você redirecionará todos os métodos HTTP por meio de uma rota existente (com um parâmetro de identificação opcional). Abra o arquivo services / router.js e substitua a lógica de roteamento atual (linhas 5-6) pelo seguinte código:

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

A lógica de roteamento atualizada mapeia os quatro métodos HTTP mais comuns usados ​​para operações básicas de CRUD para a lógica correta do controlador.

Processamento de solicitação POST

Solicitações HTTP POST são usadas para criar novos recursos (nesse caso, registros de funcionários). A idéia principal é extrair dados do corpo da solicitação HTTP e usá-los para criar uma nova linha no banco de dados. Para adicionar lógica do controlador para solicitações POST, abra o arquivo controllers / employee.js e adicione o seguinte 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; 

A função getEmployeeFromRec pega um objeto de solicitação e retorna um objeto com as propriedades necessárias para criar um registro de funcionário. A função foi declarada fora da função post para que pudesse ser usada posteriormente para solicitações PUT.

A função de postagem usa getEmployeeFromRec para inicializar a variável, que é então passada para o método de criação da API do banco de dados do funcionário. Após a operação de criação, o código de status “201 Criado” é enviado ao cliente junto com o JSON do funcionário (incluindo o novo valor do identificador do funcionário).

Agora você pode prestar atenção à lógica na API do banco de dados. Abra o arquivo db_apis / employee.js e adicione o seguinte código abaixo.

 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; 

A lógica acima começa declarando uma constante chamada createSql para armazenar a instrução insert. Observe que ele usa variáveis ​​de ligação , não concatenação de cadeia, para se referir aos valores a serem inseridos. Vale a pena repetir a importância das variáveis ​​de ligação por motivos de segurança e desempenho. Tente evitar a concatenação de cadeias sempre que possível.

Dentro da função create, a constante empregado é definida e inicializada para uma cópia do parâmetro emp usando Object.assign. Isso evita a modificação direta do objeto transferido do controlador.

Em seguida, a propriedade employee_id é adicionada ao objeto employee (configurado como "out bind") para que ele contenha todas as variáveis ​​de ligação necessárias para executar a instrução SQL. Em seguida, a função simpleExecute é usada para executar a instrução insert e a propriedade outBinds é usada para substituir a propriedade employee.employee_id antes de retornar o objeto.

Como existe um link para o módulo oracledb, você precisa adicionar a seguinte linha na parte superior do arquivo.
 const oracledb = require('oracledb'); 


Processamento de solicitação PUT

Solicitações PUT serão usadas para atualizar os recursos existentes. É importante observar que o PUT é usado para substituir todo o recurso - ele não executa atualizações parciais (mostrarei como fazer isso com o PATCH no futuro). Retorne ao arquivo controllers / employee.js e adicione o seguinte código abaixo.

 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; 

A função put usa getEmployeeFromRec para inicializar um objeto chamado employee e, em seguida, adiciona a propriedade employee_id do parâmetro id à URL. Em seguida, o objeto empregado é passado para a função de atualização da API do banco de dados e a resposta correspondente é enviada ao cliente com base no resultado.

Para adicionar a lógica de atualização à API do banco de dados, adicione o seguinte código ao arquivo 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; 

A lógica de atualização é muito semelhante à lógica de criação. Uma variável é declarada para armazenar a instrução SQL e simpleExecute é usada para executar a instrução com os valores dinâmicos transferidos (depois de copiá-los para outro objeto). result.rowsAffected é usado para determinar se a atualização foi bem-sucedida e retornar o valor correto.

DELETE Request Processing

O último método que você precisa implementar é DELETE, que, sem surpresa, removerá recursos do banco de dados. Adicione o seguinte código no final do arquivo 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; 

O mecanismo JavaScript lançará uma exceção se você tentar declarar uma função chamada “delete” usando o operador de função. Para contornar isso, uma função chamada "del" é declarada e depois exportada como "delete".

Neste ponto, você deve ser capaz de ler e entender a lógica. Diferente dos exemplos anteriores, essa solicitação HTTP não possui corpo; apenas o parâmetro id é usado no caminho da rota. O código de status “204 No Content” é frequentemente usado com solicitações DELETE quando o corpo da resposta não é enviado.

Para concluir a lógica do banco de dados, volte ao arquivo db_apis / employee.js e inclua o seguinte código no 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; 

Como a tabela JOB_HISTORY possui uma restrição de chave estrangeira que se refere à tabela EMPLOYEES, um bloco PL / SQL simples é usado para remover as linhas necessárias das duas tabelas em um único ciclo.

Analisando Pedidos JSON

Se você observar a função getEmployeeFromRec em controllers / employee.js, notará que a propriedade do corpo da solicitação é um objeto JavaScript. Isso fornece uma maneira fácil de obter valores do corpo da solicitação, mas isso não acontece automaticamente.

A API que você está criando espera que os clientes enviem dados no formato JSON no corpo das solicitações POST e PUT. Além disso, os clientes devem definir o cabeçalho Content-Type da solicitação como application / json para que o servidor da Web saiba que tipo de corpo de solicitação é enviado. Você pode usar o middleware embutido express.json para que o Express possa analisar essas solicitações.

Abra o arquivo services / web-server.js e adicione as seguintes linhas logo abaixo da chamada app.use, que adiciona morgan ao pipeline de solicitação.

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

Quando os dados JSON são analisados ​​em objetos JavaScript nativos, apenas os tipos de dados suportados no JSON serão mapeados corretamente para os tipos JavaScript. As datas não são suportadas no JSON e geralmente são representadas como sequências ISO 8601. Usando a função reviver passada para o middleware express.json, você pode executar a conversão manualmente. Adicione o seguinte código ao final do arquivo 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; } } 

Teste de API

É hora de testar a nova funcionalidade CRUD! Até o momento, você usou o navegador para testar a API, mas isso não funcionará para solicitações POST, PUT e DELETE. Mostrarei como testar a API usando os comandos curl, pois ela é facilmente acessível em uma máquina virtual. Mas você pode usar uma ferramenta gráfica como Postman , Insomina (grátis).

Inicie o aplicativo e abra outra janela do terminal e execute o seguinte comando para criar um novo funcionário.

 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" }' 

Se a solicitação foi bem-sucedida, a resposta deve conter um objeto de funcionário com o atributo employee_id. Aqui está um exemplo:

imagem

No meu caso, o valor de employee_id era 227 - você precisará alterar os seguintes comandos com base no valor recebido de employee_id.

Por exemplo, para atualizar uma nova entrada, insira PUT para a URL com 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" }' 

O acionador UPDATE_JOB_HISTORY no esquema HR detectará uma alteração de trabalho e incluirá uma linha na tabela JOB_HISTORY. Se você olhar para esta tabela, deverá ver um registro para o novo funcionário. Execute o seguinte comando para excluir o histórico de tarefas e os registros de funcionários.

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


E agora, você tem tudo, funcionalidade CRUD completa!

A API está progredindo bem, mas há trabalho a fazer. Na última postagem, mostrarei como adicionar recursos de paginação, classificação e filtragem nas solicitações GET.

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


All Articles