Atualizado: 07/12/2024

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:

  • GET para obter dados.

  • POST para adicionar dados.

  • PUT para atualizar dados.

  • DELETE para remover dados.

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 com o seguinte conteúdo:

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(), que extrai os dados enviados pelo cliente na requisição e os converte para o formato JSON (assumindo que o cliente enviará os dados em 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);
        }
    });
}

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 definido anteriormente.

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() para extrair os dados da requisição:

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. Após obter os dados, criamos um novo objeto e o adicionamos ao array.

Se a aplicação receber uma requisição PUT, também usamos a função getReqData() para obter os dados enviados pelo cliente. Se o objeto for encontrado no array, atualizamos os dados, caso contrário, enviamos um código de status 404:

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, o servidor retornará o arquivo index.html. Portanto, na mesma pasta do arquivo do servidor, defina um arquivo index.html com o seguinte código:

<!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 representa o formulário para adicionar ou editar um usuário. A variável userId rastreia o identificador do usuário carregado. Se for igual a 0, significa que um novo usuário está sendo criado. Por padrão, ao carregar a página, essa variável é 0, indicando que nenhum usuário está carregado no formulário. Se userId não for 0, um usuário foi carregado previamente através da função getUser, e estamos prontos para editar esse usuário.

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, que retorna uma linha com links para editar e deletar o usuário.

A função getUser obtém o usuário do servidor e preenche o formulário com os dados:

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 envia os dados em uma requisição 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));
    }
}

Se um usuário já foi carregado no formulário, a função editUser envia uma requisição PUT para atualizar os dados:

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.

REST e API e função fetch em JavaScript
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com