Criando um Cliente para REST API - JavaScript
Utilizando o Fetch API em JavaScript, é possível implementar um cliente completo para Web APIs no estilo REST, facilitando a interação com o servidor. A arquitetura REST utiliza métodos HTTP específicos para a comunicação com o servidor:
para obter dados.GET
para adicionar dados.POST
para atualizar dados.PUT
para remover dados.DELETE
Vamos explorar como criar um cliente em JavaScript para interagir com uma API.
Criando o Servidor com Node.js
Primeiramente, vamos definir o servidor que representará a Web API. Utilizaremos Node.js para isso. Para processar as requisições, crie um arquivo server.js
const http = require("http");
const fs = require("fs");
// Dados com os quais o cliente irá interagir
const users = [
{ id: 1, name: "Tom", age: 24 },
{ id: 2, name: "Bob", age: 27 },
{ id: 3, name: "Alice", age: 23 }
];
// Função para processar os dados recebidos do cliente
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) => {
// Obtenção de todos os usuários
if (request.url === "/api/users" && request.method === "GET") {
response.end(JSON.stringify(users));
}
// Obtenção de um usuário específico por ID
else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "GET") {
const id = request.url.split("/")[3];
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: "Usuário não encontrado" }));
}
}
// Exclusão de um usuário por ID
else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "DELETE") {
const id = request.url.split("/")[3];
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: "Usuário não encontrado" }));
}
}
// Adição de um novo usuário
else if (request.url === "/api/users" && request.method === "POST") {
try {
const userData = await getReqData(request);
const user = { name: userData.name, age: userData.age };
const id = Math.max.apply(Math, users.map((u) => 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: "Solicitação inválida" }));
}
}
// Atualização de um usuário
else if (request.url === "/api/users" && request.method === "PUT") {
try {
const userData = await getReqData(request);
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: "Usuário não encontrado" }));
}
} catch (error) {
response.writeHead(400, { "Content-Type": "application/json" });
response.end(JSON.stringify({ message: "Solicitação inválida" }));
}
} 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: "Recurso não encontrado" }));
}
}).listen(3000, () => console.log("Servidor iniciado em http://localhost:3000"));
Analise do Código
Vamos analisar este código de forma geral. Primeiro, são definidos os dados com os quais o cliente irá trabalhar:
const users = [
{ id: 1, name: "Tom", age: 24 },
{ id: 2, name: "Bob", age: 27 },
{ id: 3, name: "Alice", age: 23 }
]
Para simplificação, os dados são definidos como um array comum de objetos, mas em situações reais esses dados geralmente são extraídos de um banco de dados.
Em seguida, é definida a função getReqData()
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);
}
});
}
O resultado da função é definido como uma promise. Se os dados forem analisados com sucesso, a promise retorna o objeto parseado. Caso ocorra um erro, uma mensagem de erro é retornada.
Para cada tipo de requisição, é definido um cenário específico.
Quando a aplicação recebe uma requisição do tipo GET no endereço "api/users", o seguinte código é executado:
if (request.url === "/api/users" && request.method === "GET") {
response.end(JSON.stringify(users));
}
Aqui, simplesmente enviamos o array users
Quando o cliente solicita um objeto específico por ID em uma requisição GET no endereço "api/users/", o seguinte código é executado:
else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "GET") {
const id = request.url.split("/")[3];
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: "Usuário não encontrado" }));
}
}
Nesse caso, precisamos encontrar o usuário no array pelo ID e, se não for encontrado, retornamos um código de status 404 com uma mensagem de erro em JSON.
Ao receber uma requisição DELETE no endereço "/api/users/ ", encontramos o índice do objeto no array e, se encontrado, o removemos do array e enviamos a resposta ao cliente:
else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "DELETE") {
const id = request.url.split("/")[3];
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: "Usuário não encontrado" }));
}
}
Se o objeto não for encontrado, retornamos um código de status 404.
Ao receber uma requisição POST no endereço "/api/users", usamos a função getReqData()
else if (request.url === "/api/users" && request.method === "POST") {
try {
const userData = await getReqData(request);
const user = { name: userData.name, age: userData.age };
const id = Math.max.apply(Math, users.map(u => u.id)) + 1;
user.id = id;
users.push(user);
response.end(JSON.stringify(user));
} catch (error) {
response.writeHead(400, { "Content-Type": "application/json" });
response.end(JSON.stringify({ message: "Solicitação inválida" }));
}
}
Como a execução da função pode lançar um erro (por exemplo, ao analisar JSON), todo o código é encapsulado em um bloco try..catch
Se a aplicação receber uma requisição PUT, também usamos a função getReqData()
else if (request.url === "/api/users" && request.method === "PUT") {
try {
const userData = await getReqData(request);
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: "Usuário não encontrado" }));
}
} catch (error) {
response.writeHead(400, { "Content-Type": "application/json" });
response.end(JSON.stringify({ message: "Solicitação inválida" }));
}
}
Dessa forma, definimos uma API básica. Agora, vamos adicionar o código do cliente.
Definindo o Cliente
Ao acessar a raiz da aplicação web ou o endereço /index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Programício</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>Lista de Usuários</h2>
<form name="userForm">
<p>
<label for="name">Nome:</label><br>
<input name="name" />
</p>
<p>
<label for="age">Idade:</label><br>
<input name="age" type="number" min="1" max="110" />
</p>
<p>
<button type="submit">Salvar</button>
<button type="reset">Resetar</button>
</p>
</form>
<table>
<thead><tr><th>Id</th><th>Nome</th><th>Idade</th><th></th></tr></thead>
<tbody>
</tbody>
</table>
<script>
let userId = 0; // Identificador do usuário que está sendo editado
const userForm = document.forms["userForm"]; // Formulário de entrada
// Função para obter todos os usuários
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)));
}
}
// Função para obter um usuário específico
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;
}
}
// Função para criar um novo usuário
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));
}
}
// Função para editar um usuário
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));
}
}
// Função para deletar um usuário
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();
}
}
// Função para resetar o formulário
function reset() {
userForm.reset();
userId = 0;
}
// Função para criar uma linha na tabela
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("Editar");
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("Deletar");
removeLink.addEventListener("click", async e => {
e.preventDefault();
await deleteUser(user.id);
});
linksTd.append(removeLink);
tr.appendChild(linksTd);
return tr;
}
// Evento de reset do formulário
userForm.addEventListener("reset", e => reset());
// Evento de submit do formulário
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);
}
});
// Carregar os usuários ao carregar a página
getUsers();
</script>
</body>
</html>
Explicação do Código
A lógica principal está no código JavaScript. Inicialmente, definimos dados globais:
let userId = 0; // Identificador do usuário que está sendo editado
const userForm = document.forms["userForm"]; // Formulário de entrada
A constante userForm
userId
userId
getUser
Ao carregar a página, obtemos todos os usuários do banco de dados usando a função 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)));
}
}
Para adicionar linhas na tabela, usamos a função row
A função 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;
}
}
Se userId for 0, a função createUser
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));
}
}
Se um usuário já foi carregado no formulário, a função editUser
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));
}
}
Executando o Servidor
Execute o arquivo do servidor com o comando node server.js
C:\app>node server.js Servidor iniciado em http://localhost:3000
O servidor Node.js com a API para a função fetch em JavaScript será iniciado. Acesse "http://localhost:3000" no navegador para gerenciar os usuários armazenados no arquivo JSON.
