Parte 4. Criando uma API REST: Manipulando Solicitações POST, PUT e DELETENo
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 roteamentoPara 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 POSTSolicitaçõ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 PUTSolicitaçõ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 ProcessingO ú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 JSONSe 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.
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) {
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:

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.