MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Crearea unui client pentru REST API

Folosind Fetch API în JavaScript, se poate implementa un client complet pentru Web API în stil REST pentru interacțiunea cu utilizatorul. Arhitectura REST presupune utilizarea următoarelor metode sau tipuri de cereri HTTP pentru interacțiunea cu serverul:

  • GET
  • POST
  • PUT
  • DELETE

Să examinăm cum să creăm propriul client în JavaScript pentru API.

Crearea unui server pe node.js

Pentru început, vom defini serverul care va reprezenta de fapt Web API. Ca exemplu, vom folosi Node.js. Pentru a procesa cererile, vom defini următorul fișier server.js:

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

// datele cu care lucrează clientul
const users = [
   { id:1, name:"Tom", age:24},
   { id:2, name:"Bob", age:27},
   { id:3, name:"Alice", age:23}
]
// procesăm datele primite de la client
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) => {
     
    // obținerea tuturor utilizatorilor
    if (request.url === "/api/users" && request.method === "GET") {
       response.end(JSON.stringify(users));
   }
   // obținerea unui singur utilizator după id
    else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "GET") {
       // obținem id din adresa url
       const id = request.url.split("/")[3];
       // obținem utilizatorul după id
       const user = users.find((u) => u.id === parseInt(id));
       // dacă utilizatorul este găsit, îl trimitem
       if(user)
           response.end(JSON.stringify(user));
       // dacă nu este găsit, trimitem codul de stare și mesajul de eroare
       else{
           response.writeHead(404, { "Content-Type": "application/json" });
           response.end(JSON.stringify({ message: "Utilizatorul nu a fost găsit" }));
       }
   }
   // ștergerea utilizatorului după id  
   else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "DELETE") {
       // obținem id din adresa url
       const id = request.url.split("/")[3];
       // obținem indexul utilizatorului după id
       const userIndex = users.findIndex((u) => u.id === parseInt(id));
       // dacă utilizatorul este găsit, îl ștergem din array și îl trimitem clientului
       if(userIndex > -1) {
           const user = users.splice(userIndex, 1)[0];
           response.end(JSON.stringify(user));
       }
       // dacă nu este găsit, trimitem codul de stare și mesajul de eroare
       else{
           response.writeHead(404, { "Content-Type": "application/json" });
           response.end(JSON.stringify({ message: "Utilizatorul nu a fost găsit" }));
       }
   }
   // adăugarea unui utilizator
   else if (request.url === "/api/users" && request.method === "POST") {
       try{
           // obținem datele utilizatorului
           const userData = await getReqData(request);
           // creăm un nou utilizator
           const user = {name: userData.name, age: userData.age};
           // găsim cel mai mare id
           const id = Math.max.apply(Math,users.map(function(u){return u.id;}))
           // îl mărim cu o unitate
           user.id = id + 1;
           // adăugăm utilizatorul în array
           users.push(user);
           response.end(JSON.stringify(user));
       }
       catch(error){
           response.writeHead(400, { "Content-Type": "application/json" });
           response.end(JSON.stringify({ message: "Cerere incorectă" }));
       }
   }
   // modificarea unui utilizator
   else if (request.url === "/api/users" && request.method === "PUT") {
       try{
           const userData = await getReqData(request);
           // obținem utilizatorul după id
           const user = users.find((u) => u.id === parseInt(userData.id));
           // dacă utilizatorul este găsit, îi modificăm datele și îl trimitem înapoi clientului
           if(user) {
               user.age = userData.age;
               user.name = userData.name;
               response.end(JSON.stringify(user));
           }
           // dacă nu este găsit, trimitem codul de stare și mesajul de eroare
           else{
               response.writeHead(404, { "Content-Type": "application/json" });
               response.end(JSON.stringify({ message: "Utilizatorul nu a fost găsit" }));
           }
       }
       catch(error){
           response.writeHead(400, { "Content-Type": "application/json" });
           response.end(JSON.stringify({ message: "Cerere incorectă" }));
       }
   }
   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: "Resursa nu a fost găsită" }));
   }
}).listen(3000, ()=>console.log("Serverul a fost lansat la adresa http://localhost:3000"));

Să examinăm în linii generale acest cod. La început, sunt definite datele cu care va lucra clientul:

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

Pentru simplificare, datele sunt definite sub formă de un array obișnuit de obiecte, dar într-o situație reală, de obicei, aceste date sunt extrase dintr-o anumită bază de date.

Apoi este definită funcția getReqData(), care extrage din cererea trimisă de client datele și le convertește în format JSON (se presupune că clientul va trimite date în format 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);
       }
   });
}

Rezultatul funcției este definit sub formă de promisiune. Dacă datele sunt analizate cu succes, atunci transmitem prin promisiune obiectul analizat. Dacă apare o eroare, atunci transmitem mesajul de eroare.

Apoi, pentru fiecare tip de cerere, este definit un scenariu specific.

Când aplicația primește o cerere de tip GET la adresa "api/users", se execută următorul cod:

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

Aici pur și simplu trimitem array-ul users definit mai sus.

Când clientul accesează aplicația pentru a obține un singur obiect după id într-o cerere de tip GET la adresa "api/users/", se execută următorul cod:

else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "GET") {
   // obținem id din adresa url
   const id = request.url.split("/")[3];
   // obținem utilizatorul după id
   const user = users.find((u) => u.id === parseInt(id));
   // dacă utilizatorul este găsit, îl trimitem
   if(user)
       response.end(JSON.stringify(user));
   // dacă nu este găsit, trimitem codul de stare și mesajul de eroare
   else{
       response.writeHead(404, { "Content-Type": "application/json" });
       response.end(JSON.stringify({ message: "Utilizatorul nu a fost găsit" }));
   }
}

În acest caz, trebuie să găsim utilizatorul necesar după id în array, iar dacă nu este găsit, returnăm codul de stare 404 cu un anumit mesaj în format JSON.

La primirea unei cereri DELETE la adresa "/api/users/:id", găsim indexul obiectului în array. Dacă obiectul este găsit, îl ștergem din array și îl trimitem clientului:

// ștergem utilizatorul după id
else if (request.url.match(/\/api\/users\/([0-9]+)/) && request.method === "DELETE") {
   // obținem id din adresa url
   const id = request.url.split("/")[3];
   // obținem indexul utilizatorului după id
   const userIndex = users.findIndex((u) => u.id === parseInt(id));
   // dacă utilizatorul este găsit, îl ștergem din array și îl trimitem clientului
   if(userIndex > -1) {
       const user = users.splice(userIndex, 1)[0];
       response.end(JSON.stringify(user));
   }
   // dacă nu este găsit, trimitem codul de stare și mesajul de eroare
   else{
       response.writeHead(404, { "Content-Type": "application/json" });
       response.end(JSON.stringify({ message: "Utilizatorul nu a fost găsit" }));
   }
}

Dacă obiectul nu este găsit, returnăm codul de stare 404.

La primirea unei cereri cu metoda POST la adresa "/api/users", folosim funcția getReqData() pentru a extrage datele din cerere:

else if (request.url === "/api/users" și request.method === "POST") {
   try{
       // obținem datele utilizatorului
       const userData = await getReqData(request);
       // creăm un nou utilizator
       const user = {name: userData.name, age: userData.age};
       // găsim cel mai mare id
       const id = Math.max.apply(Math,users.map(function(u){return u.id;}))
       // îl mărim cu o unitate
       user.id = id + 1;
       // adăugăm utilizatorul în array
       users.push(user);
       response.end(JSON.stringify(user));
   }
   catch(error){
       response.writeHead(400, { "Content-Type": "application/json" });
       response.end(JSON.stringify({ message: "Cerere incorectă" }));
   }
}

Deoarece la executarea funcției promisiunea poate transmite o eroare (de exemplu, în rezultatul analizei în JSON), învăluim întregul cod în try..catch. După obținerea datelor, trebuie să creăm un nou obiect și să-l adăugăm în array-ul de obiecte.

Dacă aplicației i se trimite o cerere PUT, atunci de asemenea, cu ajutorul funcției getReqData, obținem datele trimise de client. Dacă obiectul este găsit în array, îl modificăm, altfel trimitem codul de stare 404:

else if (request.url === "/api/users" și request.method === "PUT") {
   try{
       const userData = await getReqData(request);
       // obținem utilizatorul după id
       const user = users.find((u) => u.id === parseInt(userData.id));
       // dacă utilizatorul este găsit, îi modificăm datele și îl trimitem înapoi clientului
       if(user) {
           user.age = userData.age;
           user.name = userData.name;
           response.end(JSON.stringify(user));
       }
       // dacă nu este găsit, trimitem codul de stare și mesajul de eroare
       else{
           response.writeHead(404, { "Content-Type": "application/json" });
           response.end(JSON.stringify({ message: "Utilizatorul nu a fost găsit" }));
       }
   }
   catch(error){
       response.writeHead(400, { "Content-Type": "application/json" });
       response.end(JSON.stringify({ message: "Cerere incorectă" }));
   }
}

Astfel, am definit cea mai simplă API. Acum să adăugăm codul clientului.

Definirea clientului

Când se accesează rădăcina aplicației web sau la adresa "/index.html", serverul va returna fișierul index.html. Prin urmare, în aceeași folderă cu fișierul serverului, definim fișierul index.html cu următorul cod:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width" />
   <title>FDC.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>Lista utilizatorilor</h2>
   <form name="userForm">
       <p>
           <label for="name">Nume:</label><br>
           <input name="name" />
       </p>
       <p>
           <label for="age">Vârstă:</label><br>
           <input name="age" type="number" min="1" max="110" />
       </p>
       <p>
           <button type="submit">Salvează</button>
           <button type="reset">Resetează</button>
       </p>
   </form>
   <table>
       <thead><tr><th>Id</th><th>Nume</th><th>Vârstă</th><th></th></tr></thead>
       <tbody>
       </tbody>
   </table>
   
   <script>
       let userId = 0;     // identificatorul utilizatorului care este editat
       const userForm = document.forms["userForm"];    // formularul de introducere
       // Obținerea tuturor utilizatorilor
       async function getUsers() {
           // trimite cererea și primește răspunsul
           const response = await fetch("/api/users", {
               method: "GET",
               headers: { "Accept": "application/json" }
           });
           // dacă cererea a trecut normal
           if (response.ok === true) {
               // primește datele
               const users = await response.json();
               const rows = document.querySelector("tbody");
               // adaugă elementele primite în tabel
               users.forEach(user => rows.append(row(user)));
           }
       }
       // Obținerea unui singur utilizator
       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;
           }
       }
       // Adăugarea unui utilizator
       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));
           }
       }
       // Modificarea unui utilizator
       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));
           }
       }
       // Ștergerea unui utilizator
       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();
           }
       }
 
       // resetarea formularului și a identificatorului curent al

utilizatorului
       function reset() {
           userForm.reset();
           userId = 0;
       }
       // crearea unui rând pentru tabel
       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("Modifică");
           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("Șterge");
           removeLink.addEventListener("click", async e => {
               e.preventDefault();
               await deleteUser(user.id);
           });
 
           linksTd.append(removeLink);
           tr.appendChild(linksTd);
 
           return tr;
       }
       // resetarea valorilor formularului
       userForm.addEventListener("reset", e => reset());
 
       // trimiterea formularului
       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);
       });
 
       // încărcarea utilizatorilor
       getUsers();
   </script>
</body>
</html>

Logica principală este încapsulată în codul JavaScript. La început, sunt definite datele globale:

  • Identificatorul utilizatorului care este editat (userId = 0)
  • Formularul de introducere (const userForm = document.forms["userForm"])

Constanta userForm reprezintă formularul pentru adăugarea sau editarea unui obiect. Cu ajutorul variabilei userId urmărim identificatorul utilizatorului încărcat. Dacă este egal cu 0, atunci utilizatorul este creat.

La încărcarea paginii, această variabilă este 0, deoarece niciun utilizator nu este încărcat pe formular. Dacă userId nu este egal cu 0, atunci utilizatorul a fost anterior încărcat cu ajutorul funcției getUser, și intenționăm să edităm acest utilizator.

La încărcarea paginii în browser, obținem toate obiectele din baza de date cu ajutorul funcției getUsers, folosind cereri la serverul nostru prin API pentru a interacționa cu datele utilizatorilor.

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;
   }
}

Pentru a adăuga rânduri în tabel se folosește funcția row(), care returnează un rând. În acest rând vor fi definite linkuri pentru modificarea și ștergerea utilizatorului.

Linkul pentru modificarea utilizatorului cu ajutorul funcției getUser() ,obține de pe server utilizatorul selectat:

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));
   }
}

Și utilizatorul selectat este adăugat în formularul de deasupra tabelului.

Dacă userId este egal cu 0, atunci se execută funcția createUser, care trimite datele într-o cerere 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));
   }
}

Însă, dacă anterior utilizatorul a fost încărcat pe formular, și în variabila userId s-a păstrat id-ul său, atunci se execută funcția editUser, care trimite o cerere 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));
   }
}

La final, vom lansa fișierul server server.js cu comanda:

node server.js

Accesăm în browser adresa "http://localhost:3000" și vom putea gestiona utilizatorii care sunt stocați în fișierul json.

← Lecția anterioară Lecția următoare →