Создание клиента для REST API

Последнее обновление: 23.01.2024

Используя Fetch API в JavaScript, можно реализовать полноценный клиент для Web API в стиле REST для взаимодействия с пользователем. Архитектура REST предполагает применение следующих методов или типов запросов HTTP для взаимодействия с сервером:

  • GET

  • POST

  • PUT

  • DELETE

Рассмотрим, как создать свой клиент на javascript для API.

Создание сервера на node.js

Для начала определим сервер, который будет и будет собственно представлять Web API. В качестве примера возьмем Node.js. Для обработки запросов определим следующий файл server.js:

const http = require("http");
const fs = require("fs");

// данные, с которыми работает клиент
const users = [
	{ id:1, name:"Tom", age:24}, 
	{ id:2, name:"Bob", age:27},
	{ id:3, name:"Alice", age:23}
]
// обрабатываем полученные от клиента данные
function getReqData(req) {
    return new Promise(async (resolve, reject) => {
        try {
			const buffers = [];
			for await (const chunk of req) {
				buffers.push(chunk);
			}
			const data = JSON.parse(Buffer.concat(buffers).toString());
			resolve(data);
        } catch (error) {
            reject(error);
        }
    });
}

http.createServer(async (request, response) => {
     
	 // получение всех пользователей
	 if (request.url === "/api/users" && request.method === "GET") {
        response.end(JSON.stringify(users));
    }
	// получение одного пользователя по id
	 else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "GET") {
        // получаем id из адреса url
        const id = request.url.split("/")[3];
        // получаем пользователя по id
        const user = users.find((u) => u.id === parseInt(id));
		// если пользователь найден, отправляем его
		if(user) 
			response.end(JSON.stringify(user));
		// если не найден, отправляем статусный код и сообщение об ошибке
		else{
			response.writeHead(404, { "Content-Type": "application/json" });
			response.end(JSON.stringify({ message: "Пользователь не найден" }));
		}
    }
	// удаление пользователя по id	
	else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "DELETE") {
        // получаем id из адреса url
        const id = request.url.split("/")[3];
        // получаем индекс пользователя по id
        const userIndex = users.findIndex((u) => u.id === parseInt(id));
		// если пользователь найден, удаляем его из массива и отправляем клиенту
		if(userIndex > -1) {
			const user = users.splice(userIndex, 1)[0];
			response.end(JSON.stringify(user));
		}
		// если не найден, отправляем статусный код и сообщение об ошибке
		else{
			response.writeHead(404, { "Content-Type": "application/json" });
			response.end(JSON.stringify({ message: "Пользователь не найден" }));
		}
    }
    // добавление пользователя
    else if (request.url === "/api/users" && request.method === "POST") {
		try{
			// получаем данные пользователя
			const userData = await getReqData(request);
			// создаем нового пользователя
			const user = {name: userData.name, age: userData.age};
			// находим максимальный id
			const id = Math.max.apply(Math,users.map(function(u){return u.id;}))
			// увеличиваем его на единицу
			user.id = id + 1;
			// добавляем пользователя в массив
			users.push(user);
			response.end(JSON.stringify(user));
		}
		catch(error){
			response.writeHead(400, { "Content-Type": "application/json" });
			response.end(JSON.stringify({ message: "Некорректный запрос" }));
		}
    }
    // изменение пользователя
    else if (request.url === "/api/users" && request.method === "PUT") {
		try{
			const userData = await getReqData(request);
			// получаем пользователя по id
			const user = users.find((u) => u.id === parseInt(userData.id));
			// если пользователь найден, изменяем его данные и отправляем обратно клиенту
			if(user) {
				user.age = userData.age;
				user.name = userData.name;
				response.end(JSON.stringify(user));
			}
			// если не найден, отправляем статусный код и сообщение об ошибке
			else{
				response.writeHead(404, { "Content-Type": "application/json" });
				response.end(JSON.stringify({ message: "Пользователь не найден" }));
			}
		}
		catch(error){
			response.writeHead(400, { "Content-Type": "application/json" });
			response.end(JSON.stringify({ message: "Некорректный запрос" }));
		}
    }
    else if (request.url === "/" || request.url === "/index.html") {
		fs.readFile("index.html", (error, data) => response.end(data));
    }
	else{
		response.writeHead(404, { "Content-Type": "application/json" });
		response.end(JSON.stringify({ message: "Ресурс не найден" }));
	}
}).listen(3000, ()=>console.log("Сервер запущен по адресу http://localhost:3000"));

Разберем в общих чертах этот код. Вначале идет определение данных, с которыми будет работать клиент:

const users = [
	{ id:1, name:"Tom", age:24}, 
	{ id:2, name:"Bob", age:27},
	{ id:3, name:"Alice", age:23}
]

Для упрошения данные определены в виде обычного массива объектов, но в реальной ситуации обычно подобные данные извлекаются из какой-нибудь базы данных.

Далее определена функция getReqData(), которая извлекает из запроса присланные от клиента данные и конвертирует их в формат json (предполагается, что клиент будет присылать данные в формате json):

function getReqData(req) {
    return new Promise(async (resolve, reject) => {
        try {
			const buffers = [];
			for await (const chunk of req) {
				buffers.push(chunk);
			}
			const data = JSON.parse(Buffer.concat(buffers).toString());
			resolve(data);
        } catch (error) {
            reject(error);
        }
    });
}

Причем результат функции определен в виде промиса. Если данные успешно распарсены, то передаем через промис распарсенный объект. Если же произошла ошибка, то передаем сообщение об ошибке.

Далее для каждого типа запросов определен определенный сценарий.

Когда приложение получает запрос типа GET по адресу "api/users", то срабатывает следующий код:

if (request.url === "/api/users" && request.method === "GET") {
	response.end(JSON.stringify(users));
}

Здесь просто отправляем выше определенный массив users.

Когда клиент обращается к приложению для получения одного объекта по id в запрос типа GET по адресу "api/users/", то срабатывает следующий код:

else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "GET") {
	// получаем id из адреса url
	const id = request.url.split("/")[3];
	// получаем пользователя по id
	const user = users.find((u) => u.id === parseInt(id));
	// если пользователь найден, отправляем его
	if(user) 
		response.end(JSON.stringify(user));
	// если не найден, отправляем статусный код и сообщение об ошибке
	else{
		response.writeHead(404, { "Content-Type": "application/json" });
		response.end(JSON.stringify({ message: "Пользователь не найден" }));
	}
}

В этом случае нам надо найти нужного пользователя по id в массиве, а если он не был найден, возвратить статусный код 404 с некоторым сообщением в формате JSON.

При получении DELETE-запроса по адресу "/api/users/:id" находим индекс объекта в массива. И если объект найден, то удаляем его из массива и отправляем клиенту:

// удаление пользователя по id	
else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "DELETE") {
	// получаем id из адреса url
	const id = request.url.split("/")[3];
	// получаем индекс пользователя по id
	const userIndex = users.findIndex((u) => u.id === parseInt(id));
	// если пользователь найден, удаляем его из массива и отправляем клиенту
	if(userIndex > -1) {
		const user = users.splice(userIndex, 1)[0];
		response.end(JSON.stringify(user));
	}
	// если не найден, отправляем статусный код и сообщение об ошибке
	else{
		response.writeHead(404, { "Content-Type": "application/json" });
		response.end(JSON.stringify({ message: "Пользователь не найден" }));
	}
}

Если объект не найден, возвращаем статусный код 404.

При получении запроса с методом POST по адресу "/api/users" используем функцию getReqData() для извлечения данных из запроса:

else if (request.url === "/api/users" && request.method === "POST") {
	try{
		// получаем данные пользователя
		const userData = await getReqData(request);
		// создаем нового пользователя
		const user = {name: userData.name, age: userData.age};
		// находим максимальный id
		const id = Math.max.apply(Math,users.map(function(u){return u.id;}))
		// увеличиваем его на единицу
		user.id = id + 1;
		// добавляем пользователя в массив
		users.push(user);
		response.end(JSON.stringify(user));
	}
	catch(error){
		response.writeHead(400, { "Content-Type": "application/json" });
		response.end(JSON.stringify({ message: "Некорректный запрос" }));
	}
}

Поскольку при выполнении функции промис может передавать ошибку (например, в результате парсинга в JSON), оборачиваем весь код в try..catch. После получения данных нам надо создать новый объект и добавить его в массив объектов.

Если приложению приходит PUT-запрос, то также с помощью функции getReqData() получаем отправленные клиентом данные. Если объект найден в массиве, то изменяем его, иначе отправляем статусный код 404:

// изменение пользователя
else if (request.url === "/api/users" && request.method === "PUT") {
	try{
		const userData = await getReqData(request);
		// получаем пользователя по id
		const user = users.find((u) => u.id === parseInt(userData.id));
		// если пользователь найден, изменяем его данные и отправляем обратно клиенту
		if(user) {
			user.age = userData.age;
			user.name = userData.name;
			response.end(JSON.stringify(user));
		}
		// если не найден, отправляем статусный код и сообщение об ошибке
		else{
			response.writeHead(404, { "Content-Type": "application/json" });
			response.end(JSON.stringify({ message: "Пользователь не найден" }));
		}
	}
	catch(error){
		response.writeHead(400, { "Content-Type": "application/json" });
		response.end(JSON.stringify({ message: "Некорректный запрос" }));
	}
}

Таким образом, мы определили простейший API. Теперь добавим код клиента.

Определение клиента

При обращении к корню веб-приложению или по адресу "/index.html", сервер будет отдавать файл index.html. Поэтому в одной папке с файлом сервера определим файл index.html со следующим кодом:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>METANIT.COM</title>
    <style>
    tr{height:30px;}
    td, th {min-width: 40px;text-align: left;}
    a {cursor:pointer; padding:5px;text-decoration: underline;color:navy;}
    input{width:180px;}
    </style>
</head>
<body>
    <h2>Список пользователей</h2>
    <form name="userForm">
        <p>
            <label for="name">Имя:</label><br>
            <input name="name" />
        </p>
        <p>
            <label for="age">Возраст:</label><br>
            <input name="age" type="number" min="1" max="110" />
        </p>
        <p>
            <button type="submit">Сохранить</button>
            <button type="reset">Сбросить</button>
        </p>
    </form>
    <table>
        <thead><tr><th>Id</th><th>Имя</th><th>Возраст</th><th></th></tr></thead>
        <tbody>
        </tbody>
    </table>
   
    <script>
        let userId = 0;     // идентификатор пользователя, который редактируется
        const userForm = document.forms["userForm"];    // форма ввода
        // Получение всех пользователей
        async function getUsers() {
            // отправляет запрос и получаем ответ
            const response = await fetch("/api/users", {
                method: "GET",
                headers: { "Accept": "application/json" }
            });
            // если запрос прошел нормально
            if (response.ok === true) {
                // получаем данные
                const users = await response.json();
                const rows = document.querySelector("tbody"); 
                // добавляем полученные элементы в таблицу
                users.forEach(user => rows.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();
                userId = user.id;
                userForm.elements["name"].value = user.name;
                userForm.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();
                document.querySelector("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() {
            userForm.reset();
            userId = 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.append("Изменить");
            editLink.addEventListener("click", async e => {

                e.preventDefault();
                await getUser(user.id);
            });
            linksTd.append(editLink);
  
            const removeLink = document.createElement("a");
            removeLink.setAttribute("data-id", user.id);
            removeLink.append("Удалить");
            removeLink.addEventListener("click", async e => {
  
                e.preventDefault();
                await deleteUser(user.id);
            });
  
            linksTd.append(removeLink);
            tr.appendChild(linksTd);
  
            return tr;
        }
        // сброс значений формы
        userForm.addEventListener("reset", e => reset());
  
        // отправка формы
        userForm.addEventListener("submit", e => {
            e.preventDefault();
            const name = userForm.elements["name"].value;
            const age = userForm.elements["age"].value;

            if (userId === 0) createUser(name, age);
            else editUser(userId, name, age);
        });
  
        // загрузка пользователей
        getUsers();
    </script>
</body>
</html>

Основная логика здесь заключена в коде javascript. В самом начале определяются глобальные данные:

let userId = 0;     // идентификатор пользователя, который редактируется
const userForm = document.forms["userForm"];    // форма ввода

Константа userForm представляет форму для добавления или редактирования объекта. А с помощью переменной userId отслеживаем идентификатор загруженного пользователя. Если он равен 0, то пользователь создается. По умолчанию при загрузке страницы эта переменная равна нулю, так как никакой пользователь не загружен на форму. Если же userId НЕ равен 0, то пользователь ранее был загружен с помощью функции getUser, и мы собираемся отредактировать этого пользователя.

При загрузке страницы в браузере получаем все объекты из БД с помощью функции 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();
		const rows = document.querySelector("tbody"); 
		// добавляем полученные элементы в таблицу
		users.forEach(user => rows.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();
		userId = user.id;
        userForm.elements["name"].value = user.name;
        userForm.elements["age"].value = user.age;
	}
}

И выделенный пользователь добавляется в форму над таблицей.

Если userId равен 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();
		document.querySelector("tbody").append(row(user));
	}
}

Если же ранее пользователь был загружен на форму, и в переменной userId сохранился его 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));
	}
}

В конце запустим файл сервера server.js командой:

node server.js
сервер node.js с API для функции fetch в JavaScript

И обратимся в браузере по адресу "http://localhost:3000" и мы сможем управлять пользователями, которые хранятся в файле json:

REST и API и функция fetch в JavaScript
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850