Используя Express и Node.js, мы можем реализовать полноценный API в стиле REST для взаимодействия с пользователем. Архитектура REST предполагает применение следующих методов или типов запросов HTTP для взаимодействия с сервером:
GET
POST
PUT
DELETE
Зачастую REST-стиль особенно удобен при создании всякого рода Single Page Application. Рассмотрим, как создать свой API. Для нового проекта создадим новую папку. Сразу добавим в проект пакет express с помощью команды:
npm install express
В данном случае мы создадим экспериментальный проект, который призван продемонстрировать применение REST в приложении на Node.js+Express. Для обработки запросов определим в проекте следующий файл app.js:
const express = require("express"); const app = express(); app.use(express.json()); app.use(express.static("public")); // условная база данных const users = []; let id = 1; // для установки идентификаторов // вспомогательная функция для поиска индекса пользователя по id function findUserIndexById(id){ for(let i=0; i < users.length; i++){ if(users[i].id==id) return i; } return -1; } app.get("/api/users", function(_, res){ res.send(users); }); // получение одного пользователя по id app.get("/api/users/:id", function(req, res){ const id = req.params.id; // получаем id // находим в массиве пользователя по id const index = findUserIndexById(id); // отправляем пользователя if(index > -1){ res.send(users[index]); } else{ res.status(404).send("User not found"); } }); // получение отправленных данных app.post("/api/users", function (req, res) { if(!req.body) return res.sendStatus(400); const userName = req.body.name; const userAge = req.body.age; const user = {name: userName, age: userAge}; // присваиваем идентификатор из переменной id и увеличиваем ее на единицу user.id = id++; // добавляем пользователя в массив users.push(user); res.send(user); }); // удаление пользователя по id app.delete("/api/users/:id", function(req, res){ const id = req.params.id; const index = findUserIndexById(id); if(index > -1){ // удаляем пользователя из массива по индексу const user = users.splice(index, 1)[0]; res.send(user); } else{ res.status(404).send("User not found"); } }); // изменение пользователя app.put("/api/users", function(req, res){ if(!req.body) return res.sendStatus(400); const id = req.body.id; const userName = req.body.name; const userAge = req.body.age; const index = findUserIndexById(id); if(index > -1){ // изменяем данные у пользователя const user = users[index]; user.age = userAge; user.name = userName; res.send(user); } else{ res.status(404).send("User not found"); } }); app.listen(3000, function(){ console.log("Сервер ожидает подключения..."); });
В данном приложении для простоты в качестве базы данных мы будем использовать обычный массив - массив users. По умолчанию он пуст
const users = [];
Каждый объект, который будет попадать в этот массив, будет иметь определенный числовой идентификатор. И для установки идентификаторов объектов при их создании определим переменную id:
let id = 1;
Также нам потребуется вспомогательная функция для поиска индекса пользователя по id:
function findUserIndexById(id){ for(let i=0; i < users.length; i++){ if(users[i].id==id) return i; } return -1; }
Если пользователь найден, то возвращается его индекс в массиве. Если не найден, то возвращается число -1.
Для обработки запросов определено пять методов для каждого типа запросов: app.get()/app.post()/app.delete()/app.put()
Когда приложение получает запрос типа GET по адресу "api/users", то срабатывает следующий метод:
app.get("/api/users", function(req, res){ res.send(users); });
В качестве результата обработки мы должны отправить массив пользователей методом res.send()
.
Аналогично работает другой метод app.get(), который срабатывает, когда в адресе указан id пользователя:
app.get("/api/users/:id", function(req, res){ const id = req.params.id; // получаем id // находим в массиве пользователя по id const index = findUserIndexById(id); // отправляем пользователя if(index > -1){ res.send(users[index]); } else{ res.status(404).send("User not found"); } });
Единственное, что в этом случае нам надо найти нужного пользователя по id в массиве, а если он не был найден, возвратить статусный код 404: res.status(404).send()
.
При получении запроса методом POST извлекаем отправленные клиентом данные из запроса:
app.post("/api/users", function (req, res) { if(!req.body) return res.sendStatus(400); const userName = req.body.name; const userAge = req.body.age; const user = {name: userName, age: userAge}; // присваиваем идентификатор из переменной id и увеличиваем ее на единицу user.id = id++; // добавляем пользователя в массив users.push(user); res.send(user); });
Поскльку вы начале файла мы встроили в конвейер обработки запроса автоматический парсинг в json, тело запроса будет представлять объект json, из которого мы можем взять свойства name и age и создать по ним объект пользователя. Для установки идентификатора применяем глобальную переменную id, значение которой инкрементируется.
При удалении производим похожие действия, только теперь извлекаем из массива удаляемый объект:
// удаление пользователя по id app.delete("/api/users/:id", function(req, res){ const id = req.params.id; const index = findUserIndexById(id); if(index > -1){ // удаляем пользователя из массива по индексу const user = users.splice(index, 1)[0]; res.send(user); } else{ res.status(404).send("User not found"); } });
Если объект не найден, возвращаем статусный код 404.
Если приложению приходит PUT-запрос, то он обрабатывается методом app.put(), в котором получаем измененные данные:
app.put("/api/users", function(req, res){ if(!req.body) return res.sendStatus(400); const id = req.body.id; const userName = req.body.name; const userAge = req.body.age; const index = findUserIndexById(id); if(index > -1){ // изменяем данные у пользователя const user = users[index]; user.age = userAge; user.name = userName; res.send(user); } else{ res.status(404).send("User not found"); } });
Здесь также для поиска изменяемого объекта находим по id его индекс, по индексу получаем объект и изменяем у него свойства.
Таким образом, мы определили простейший API. Теперь добавим код клиента. Итак, как установлено в коде, Express для хранения статических файлов использует папку public, поэтому создадим в проекте подобную папку. В этой папке определим новый файл index.html, который будет выполнять роль клиента. В итоге весь проект будет выглядеть следующим образом:
app.js
public
index.html
Далее определим в файле index.html следующий код:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>METANIT.COM</title> <style> td, th {padding:5px;min-width:90px;max-width:200px; text-align:start;} .btn {padding:4px; border:1px solid #333; background-color: #eee; border-radius: 2px; margin:5px; cursor:pointer;} </style> </head> <body> <h2>Список пользователей</h2> <form name="userForm"> <input type="hidden" name="id" value="0" /> <p> <label>Имя:</label><br> <input name="name" /> </p> <p> <label>Возраст:</label><br> <input name="age" type="number" /> </p> <p> <button id="submitBtn" type="submit">Сохранить</button> <button id="resetBtn">Сбросить</button> </p> </form> <table> <thead><tr><th>Id</th><th>Имя</th><th>Возраст</th><th></th></tr></thead> <tbody></tbody> </table> <script> const tbody = document.querySelector("tbody"); // Получение всех пользователей async function GetUsers() { // отправляет запрос и получаем ответ const response = await fetch("/api/users", { method: "GET", headers: { "Accept": "application/json" } }); // если запрос прошел нормально if (response.ok === true) { // получаем данные const users = await response.json(); users.forEach(user => { // добавляем полученные элементы в таблицу tbody.append(row(user)); }); } } // Получение одного пользователя async function GetUser(id) { const response = await fetch("/api/users/" + id, { method: "GET", headers: { "Accept": "application/json" } }); if (response.ok === true) { const user = await response.json(); const form = document.forms["userForm"]; form.elements["id"].value = user.id; form.elements["name"].value = user.name; form.elements["age"].value = user.age; } } // Добавление пользователя async function CreateUser(userName, userAge) { const response = await fetch("api/users", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ name: userName, age: parseInt(userAge, 10) }) }); if (response.ok === true) { const user = await response.json(); reset(); tbody.append(row(user)); } } // Изменение пользователя async function EditUser(userId, userName, userAge) { const response = await fetch("api/users", { method: "PUT", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ id: userId, name: userName, age: parseInt(userAge, 10) }) }); if (response.ok === true) { const user = await response.json(); reset(); document.querySelector(`tr[data-rowid="${user.id}"]`).replaceWith(row(user)); } } // Удаление пользователя async function DeleteUser(id) { const response = await fetch("/api/users/" + id, { method: "DELETE", headers: { "Accept": "application/json" } }); if (response.ok === true) { const user = await response.json(); document.querySelector(`tr[data-rowid="${user.id}"]`).remove(); } } // сброс формы function reset() { const form = document.forms["userForm"]; console.log(form); form.reset(); form.elements["id"].value = 0; } // создание строки для таблицы function row(user) { const tr = document.createElement("tr"); tr.setAttribute("data-rowid", user.id); const idTd = document.createElement("td"); idTd.append(user.id); tr.append(idTd); const nameTd = document.createElement("td"); nameTd.append(user.name); tr.append(nameTd); const ageTd = document.createElement("td"); ageTd.append(user.age); tr.append(ageTd); const linksTd = document.createElement("td"); const editLink = document.createElement("a"); editLink.setAttribute("data-id", user.id); editLink.setAttribute("class", "btn"); editLink.append("Изменить"); editLink.addEventListener("click", e => { e.preventDefault(); GetUser(user.id); }); linksTd.append(editLink); const removeLink = document.createElement("a"); removeLink.setAttribute("data-id", user.id); removeLink.setAttribute("class", "btn"); removeLink.append("Удалить"); removeLink.addEventListener("click", e => { e.preventDefault(); DeleteUser(user.id); }); linksTd.append(removeLink); tr.appendChild(linksTd); return tr; } // сброс значений формы document.getElementById("resetBtn").addEventListener("click", e => { e.preventDefault(); reset(); }); // отправка формы document.forms["userForm"].addEventListener("submit", e => { e.preventDefault(); const form = document.forms["userForm"]; const id = form.elements["id"].value; const name = form.elements["name"].value; const age = form.elements["age"].value; if (id == 0) CreateUser(name, age); else EditUser(id, name, age); }); // загрузка пользователей GetUsers(); </script> </body> </html>
Основная логика здесь заключена в коде javascript. При загрузке страницы в браузере получаем все объекты из БД с помощью функции GetUsers:
async function GetUsers() { // отправляет запрос и получаем ответ const response = await fetch("/api/users", { method: "GET", headers: { "Accept": "application/json" } }); // если запрос прошел нормально if (response.ok === true) { // получаем данные const users = await response.json(); users.forEach(user => { // добавляем полученные элементы в таблицу tbody.append(row(user)); }); } }
Для добавления строк в таблицу используется функция row()
, которая возвращает строку. В этой строке будут определены ссылки для изменения и удаления пользователя.
Ссылка для изменения пользователя с помощью функции GetUser()
получает с сервера выделенного пользователя:
async function GetUser(id) { const response = await fetch("/api/users/" + id, { method: "GET", headers: { "Accept": "application/json" } }); if (response.ok === true) { const user = await response.json(); const form = document.forms["userForm"]; form.elements["id"].value = user.id; form.elements["name"].value = user.name; form.elements["age"].value = user.age; } }
И выделенный пользователь добавляется в форму над таблицей. Эта же форма применяется и для добавления объекта. С помощью скрытого поля, которое хранит id пользователя, мы можем узнать, какое действие выполняется - добавление или редактирование. Если id равен 0, то выполняется функция CreateUser, которая отправляет данные в POST-запросе:
async function CreateUser(userName, userAge) { const response = await fetch("api/users", { method: "POST", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ name: userName, age: parseInt(userAge, 10) }) }); if (response.ok === true) { const user = await response.json(); reset(); tbody.append(row(user)); } }
Если же ранее пользователь был загружен на форму, и в скрытом поле сохранился его id, то выполняется функция EditUser, которая отправляет PUT-запрос:
async function EditUser(userId, userName, userAge) { const response = await fetch("api/users", { method: "PUT", headers: { "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ id: userId, name: userName, age: parseInt(userAge, 10) }) }); if (response.ok === true) { const user = await response.json(); reset(); document.querySelector(`tr[data-rowid="${user.id}"]`).replaceWith(row(user)); } }
Запустим приложение, обратимся в браузере по адресу "http://localhost:3000" и мы сможем добавлять новых пользователей:
Аналогично пользователей можно изменять и удалять.