Часть 4. Создание API REST: обработка запросов POST, PUT и DELETE
В предыдущей статье вы добавили логику в API для запросов GET, которые извлекали данные из базы данных. В этом посте вы завершите построение базовой функциональности API CRUD, добавив логику для обработки запросов POST, PUT и DELETE
Добавление логики маршрутизации
Чтобы упростить логику маршрутизации, вы будете перенаправлять все методы HTTP через существующий маршрут (с необязательным параметром id). Откройте файл services / router.js и замените текущую логику маршрутизации (строки 5-6) следующим кодом:
Обновленная логика маршрутизации отображает четыре наиболее распространенных метода HTTP, используемых для основных операций CRUD, на правильную логику контроллера.
Обработка POST-запросов
HTTP POST-запросы используются для создания новых ресурсов (в данном случае это записи сотрудников). Основная идея состоит в том, чтобы извлечь данные из тела HTTP-запроса и использовать его для создания новой строки в базе данных. Чтобы добавить логику контроллера для запросов POST, откройте файл controllers / employee.js и добавьте следующий код:
Функция getEmployeeFromRec принимает объект запроса и возвращает объект со свойствами, необходимыми для создания записи сотрудника. Функция была объявлена вне функции post, чтобы ее можно было использовать позже и для запросов PUT.
Функция post использует getEmployeeFromRec для инициализации переменной, которая затем передается методу create API базы данных сотрудников. После операции создания клиенту отправляется код состояния «201 Создан» вместе с JSON сотрудника (включая новое значение идентификатора сотрудника).
Теперь вы можете обратите внимание на логику в API базы данных. Откройте файл db_apis / employee.js и добавьте следующий код внизу.
Приведенная выше логика начинается с объявления константы с именем createSql для хранения оператора вставки. Обратите внимание, что он использует переменные связывания, а не конкатенацию строк, для ссылки на значения, которые нужно вставить. Стоит повторить, насколько важны bind variables по соображениям безопасности и производительности. Старайтесь избегать конкатенации строк, когда это возможно.
Внутри функции create постоянная сотрудника определяется и инициализируется для копии параметра emp с помощью Object.assign. Это предотвращает прямую модификацию объекта, переданного из контроллера.
Затем свойство employee_id добавляется к объекту employee (настроенному как «out bind»), чтобы оно содержало все переменные связывания, необходимые для выполнения оператора SQL. Затем функция simpleExecute используется для выполнения оператора вставки, а свойство outBinds для перезаписи свойства employee.employee_id перед возвратом объекта.
Поскольку на модуль oracledb есть ссылка, вам потребуется добавить следующую строку в начало файла.
Обработка запросов PUT
Запросы PUT будут использоваться для обновления существующих ресурсов. Важно отметить, что PUT используется для замены всего ресурса — он не выполняет частичные обновления (я покажу вам, как сделать это с PATCH в будущем). Вернитесь в файл controllers / employee.js и добавьте следующий код внизу.
Функция put использует getEmployeeFromRec для инициализации объекта с именем employee, а затем добавляет свойство employee_id из параметра id в URL. Затем объект employee передается в функцию обновления API базы данных, и клиенту на основании результата отправляется соответствующий ответ.
Чтобы добавить логику обновления в API базы данных, добавьте следующий код в файл db_apis / employee.js.
Логика обновления очень похожа на логику создания. Объявлена переменная для хранения оператора SQL, а затем simpleExecute используется для выполнения оператора с переданными динамическими значениями (после копирования их в другой объект). result.rowsAffected используется, чтобы определить, было ли обновление успешным, и вернуть правильное значение.
Обработка запросов DELETE
Последний метод, который вам нужно реализовать, это DELETE, который, что неудивительно, удалит ресурсы из базы данных. Добавьте следующий код в конец файла controllers / employee.js.
Движок JavaScript выдаст исключение, если вы попытаетесь объявить функцию с именем «delete», используя оператор функции. Чтобы обойти это, объявляется функция с именем «del», а затем экспортируется как «delete».
На этом этапе вы должны уметь читать и понимать логику. В отличие от предыдущих примеров, этот HTTP-запрос не имеет тела, используется только параметр id в пути маршрута. Код состояния «204 Нет содержимого» часто используется с запросами DELETE, когда тело ответа не отправляется.
Чтобы завершить логику базы данных, вернитесь в файл db_apis / employee.js и добавьте следующий код в конец.
Поскольку таблица JOB_HISTORY имеет ограничение внешнего ключа, которое ссылается на таблицу EMPLOYEES, простой блок PL / SQL используется для удаления необходимых строк из обеих таблиц за один цикл.
Разбор JSON-запросов
Если вы посмотрите на функцию getEmployeeFromRec в controllers / employee.js, вы заметите, что свойство body запроса — это объект JavaScript. Это обеспечивает простой способ получения значений из тела запроса, но это не происходит автоматически.
API, который вы создаете, ожидает, что клиенты будут отправлять данные в формате JSON в теле запросов POST и PUT. Кроме того, клиенты должны установить заголовок Content-Type запроса на application / json, чтобы веб-сервер знал, какой тип тела запроса отправляется. Вы можете использовать встроенное промежуточное программное обеспечение express.json, чтобы Express мог анализировать такие запросы.
Откройте файл services / web-server.js и добавьте следующие строки чуть ниже вызова app.use, который добавляет morgan в конвейер запросов.
Когда данные JSON распарсятся в native объекты JavaScript, тогда только типы данных, поддерживаемые в JSON, будут правильно сопоставлены с типами JavaScript. Даты не поддерживаются в JSON и обычно представлены в виде строк ISO 8601. Используя функцию reviver, переданную промежуточному программному обеспечению express.json, вы можете выполнить преобразования вручную. Добавьте следующий код в конец файла services / web-server.js.
Тестирование API
Пришло время протестировать новую функциональность CRUD! До сих пор вы использовали браузер для тестирования API, но это не будет работать для запросов POST, PUT и DELETE. Я покажу вам, как тестировать API с помощью команд curl, потому что он легко доступен в виртуальной машине. Но можно использовать и графический инструмент, такой как Postman, Insomina (бесплатно).
Запустите приложение, а затем откройте другое окно терминала и выполните следующую команду, чтобы создать нового сотрудника.
Если запрос был успешным, ответ должен содержать объект employee с атрибутом employee_id. Вот пример:
В моем случае значение employee_id было 227 — вам нужно будет изменить следующие команды, основываясь на полученном значении employee_id.
Например, чтобы обновить новую запись, введите PUT для URL с этим значением идентификатора.
Триггер UPDATE_JOB_HISTORY в схеме HR обнаружит изменение задания и добавит строку в таблицу JOB_HISTORY. Если вы посмотрите в эту таблицу, вы должны увидеть запись для нового сотрудника. Выполните следующую команду, чтобы удалить историю заданий и записи сотрудников.
И вот, у вас есть все, полная функциональность CRUD!
API продвигается хорошо, но есть над чем поработать. В последнем посте я покажу вам, как добавить возможности разбивки на страницы, сортировки и фильтрации в запросах GET.
В предыдущей статье вы добавили логику в API для запросов GET, которые извлекали данные из базы данных. В этом посте вы завершите построение базовой функциональности API CRUD, добавив логику для обработки запросов POST, PUT и DELETE
Добавление логики маршрутизации
Чтобы упростить логику маршрутизации, вы будете перенаправлять все методы HTTP через существующий маршрут (с необязательным параметром id). Откройте файл services / router.js и замените текущую логику маршрутизации (строки 5-6) следующим кодом:
router.route('/employees/:id?')
.get(employees.get)
.post(employees.post)
.put(employees.put)
.delete(employees.delete);
Обновленная логика маршрутизации отображает четыре наиболее распространенных метода HTTP, используемых для основных операций CRUD, на правильную логику контроллера.
Обработка POST-запросов
HTTP POST-запросы используются для создания новых ресурсов (в данном случае это записи сотрудников). Основная идея состоит в том, чтобы извлечь данные из тела HTTP-запроса и использовать его для создания новой строки в базе данных. Чтобы добавить логику контроллера для запросов POST, откройте файл controllers / employee.js и добавьте следующий код:
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;
Функция getEmployeeFromRec принимает объект запроса и возвращает объект со свойствами, необходимыми для создания записи сотрудника. Функция была объявлена вне функции post, чтобы ее можно было использовать позже и для запросов PUT.
Функция post использует getEmployeeFromRec для инициализации переменной, которая затем передается методу create API базы данных сотрудников. После операции создания клиенту отправляется код состояния «201 Создан» вместе с JSON сотрудника (включая новое значение идентификатора сотрудника).
Теперь вы можете обратите внимание на логику в API базы данных. Откройте файл db_apis / employee.js и добавьте следующий код внизу.
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;
Приведенная выше логика начинается с объявления константы с именем createSql для хранения оператора вставки. Обратите внимание, что он использует переменные связывания, а не конкатенацию строк, для ссылки на значения, которые нужно вставить. Стоит повторить, насколько важны bind variables по соображениям безопасности и производительности. Старайтесь избегать конкатенации строк, когда это возможно.
Внутри функции create постоянная сотрудника определяется и инициализируется для копии параметра emp с помощью Object.assign. Это предотвращает прямую модификацию объекта, переданного из контроллера.
Затем свойство employee_id добавляется к объекту employee (настроенному как «out bind»), чтобы оно содержало все переменные связывания, необходимые для выполнения оператора SQL. Затем функция simpleExecute используется для выполнения оператора вставки, а свойство outBinds для перезаписи свойства employee.employee_id перед возвратом объекта.
Поскольку на модуль oracledb есть ссылка, вам потребуется добавить следующую строку в начало файла.
const oracledb = require('oracledb');
Обработка запросов PUT
Запросы PUT будут использоваться для обновления существующих ресурсов. Важно отметить, что PUT используется для замены всего ресурса — он не выполняет частичные обновления (я покажу вам, как сделать это с PATCH в будущем). Вернитесь в файл controllers / employee.js и добавьте следующий код внизу.
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;
Функция put использует getEmployeeFromRec для инициализации объекта с именем employee, а затем добавляет свойство employee_id из параметра id в URL. Затем объект employee передается в функцию обновления API базы данных, и клиенту на основании результата отправляется соответствующий ответ.
Чтобы добавить логику обновления в API базы данных, добавьте следующий код в файл 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;
Логика обновления очень похожа на логику создания. Объявлена переменная для хранения оператора SQL, а затем simpleExecute используется для выполнения оператора с переданными динамическими значениями (после копирования их в другой объект). result.rowsAffected используется, чтобы определить, было ли обновление успешным, и вернуть правильное значение.
Обработка запросов DELETE
Последний метод, который вам нужно реализовать, это DELETE, который, что неудивительно, удалит ресурсы из базы данных. Добавьте следующий код в конец файла 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;
Движок JavaScript выдаст исключение, если вы попытаетесь объявить функцию с именем «delete», используя оператор функции. Чтобы обойти это, объявляется функция с именем «del», а затем экспортируется как «delete».
На этом этапе вы должны уметь читать и понимать логику. В отличие от предыдущих примеров, этот HTTP-запрос не имеет тела, используется только параметр id в пути маршрута. Код состояния «204 Нет содержимого» часто используется с запросами DELETE, когда тело ответа не отправляется.
Чтобы завершить логику базы данных, вернитесь в файл db_apis / employee.js и добавьте следующий код в конец.
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;
Поскольку таблица JOB_HISTORY имеет ограничение внешнего ключа, которое ссылается на таблицу EMPLOYEES, простой блок PL / SQL используется для удаления необходимых строк из обеих таблиц за один цикл.
Разбор JSON-запросов
Если вы посмотрите на функцию getEmployeeFromRec в controllers / employee.js, вы заметите, что свойство body запроса — это объект JavaScript. Это обеспечивает простой способ получения значений из тела запроса, но это не происходит автоматически.
API, который вы создаете, ожидает, что клиенты будут отправлять данные в формате JSON в теле запросов POST и PUT. Кроме того, клиенты должны установить заголовок Content-Type запроса на application / json, чтобы веб-сервер знал, какой тип тела запроса отправляется. Вы можете использовать встроенное промежуточное программное обеспечение express.json, чтобы Express мог анализировать такие запросы.
Откройте файл services / web-server.js и добавьте следующие строки чуть ниже вызова app.use, который добавляет morgan в конвейер запросов.
// Parse incoming JSON requests and revive JSON.
app.use(express.json({
reviver: reviveJson
}));
Когда данные JSON распарсятся в native объекты JavaScript, тогда только типы данных, поддерживаемые в JSON, будут правильно сопоставлены с типами JavaScript. Даты не поддерживаются в JSON и обычно представлены в виде строк ISO 8601. Используя функцию reviver, переданную промежуточному программному обеспечению express.json, вы можете выполнить преобразования вручную. Добавьте следующий код в конец файла 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;
}
}
Тестирование API
Пришло время протестировать новую функциональность CRUD! До сих пор вы использовали браузер для тестирования API, но это не будет работать для запросов POST, PUT и DELETE. Я покажу вам, как тестировать API с помощью команд curl, потому что он легко доступен в виртуальной машине. Но можно использовать и графический инструмент, такой как Postman, Insomina (бесплатно).
Запустите приложение, а затем откройте другое окно терминала и выполните следующую команду, чтобы создать нового сотрудника.
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"
}'
Если запрос был успешным, ответ должен содержать объект employee с атрибутом employee_id. Вот пример:
В моем случае значение employee_id было 227 — вам нужно будет изменить следующие команды, основываясь на полученном значении employee_id.
Например, чтобы обновить новую запись, введите PUT для URL с этим значением идентификатора.
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"
}'
Триггер UPDATE_JOB_HISTORY в схеме HR обнаружит изменение задания и добавит строку в таблицу JOB_HISTORY. Если вы посмотрите в эту таблицу, вы должны увидеть запись для нового сотрудника. Выполните следующую команду, чтобы удалить историю заданий и записи сотрудников.
curl -i -X "DELETE" "http://localhost:3000/api/employees/227"
И вот, у вас есть все, полная функциональность CRUD!
API продвигается хорошо, но есть над чем поработать. В последнем посте я покажу вам, как добавить возможности разбивки на страницы, сортировки и фильтрации в запросах GET.