Przejdź do głównej treści

Klient usług HTTP i API REST

Wprowadzenie​

W ekosystemie technologii Netuno dostarczanych jest kilka modułów w NPM, takich jak:

Zobacz także repozytorium GIT Service Client.

Ten moduł służy do realizacji integracji z usługami opracowanymi w Netuno, czyli endpointami API REST opracowanymi w backendzie Netuno, wewnątrz folderu server/services w aplikacjach Netuno.

Często podczas tworzenia usług używa się programów takich jak:

do wykonywania testów żądań HTTP do usług.

W kodzie frontendu do integracji tych usług moglibyśmy użyć fetch, requests, AXIOS i wielu innych.

Jednak Netuno dostarcza swojego własnego klienta, który działa zarówno podczas tworzenia frontendu JavaScript, jak i w backendzie NodeJS.

Powodem, dla którego Netuno ma własną implementację klienta HTTP dla usług, jest zapewnienie większej praktyczności, uproszczenie implementacji kodu oraz zagwarantowanie większej zwinności w rozwoju.

Instalacja​

Instalacja klienta usług jest jak instalacja każdego innego modułu.

Tam, gdzie mamy plik package.json, wykonujemy polecenie instalacji:

bun add @netuno/service-client

Importowanie​

Aby zaimportować klienta usług w nowoczesnym rozwoju JavaScript:

import _service from '@netuno/service-client';

Aby zaimportować klienta usług w klasycznym rozwoju JavaScript:

const _service = require('@netuno/service-client');

Zauważ, że wewnętrzna nazwa w kodzie to _service.

Domyślnie Netuno używa _ jako prefiksu, aby wskazać, że coś jest wewnętrzne lub specyficzne, jak zasób lub narzędzie jego ekosystemu.

Dlatego, aby zachować standard, powinniśmy zawsze używać nazwy _service.

Konfiguracja​

Konfiguracja wykonywana jest globalnie — definiuje się ją tylko raz, warto wykonać ją w początkowym punkcie ogólnego kodu.

To będzie konfiguracja minimalna:

_service.config({
prefix: 'http://localhost:9000/services/'
});

Czyli definiuje prefiks adresów usług, co pozwala uniknąć konieczności każdorazowego podawania pełnego URL — dzięki temu w wywołaniach usług możemy podać tylko resztę URL.

Pełna konfiguracja​

Możemy skonfigurować wartości domyślne dla wszystkich wywołań usług, przy czym w wywołaniu każdej usługi możemy te wartości nadpisać.

Zobacz wszystkie parametry, które możemy skonfigurować:

_service.config({
prefix: '',
url: '',
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
start: () => { },
success: (data) => { },
fail: (data) => {},
end: () => { }
});

prefix​

To globalny prefiks URL.

url​

To część URL, która jest dodawana do prefiksu — konkretny adres usługi.

method​

Metoda HTTP, która będzie użyta w żądaniu HTTP; domyślnie jest to GET.

credentials​

Określa zachowanie poświadczeń w wywołaniach HTTP:

  • omit - Nigdy nie wysyła ani nie odbiera poświadczeń.
  • same-origin - Wysyła i dołącza poświadczenia tylko wtedy, gdy żądania kierowane są do tej samej domeny.
  • include - Zawsze powinien dołączać poświadczenia, nawet jeśli domena jest inna.

Domyślnie wartością jest include, ponieważ ma to najwięcej sensu w standardowym rozwoju — backend może, ale nie musi być w tej samej domenie, zależy to od architektury, a w rozwoju lokalnym często różni się od produkcji. Dlatego, aby uniknąć niespodzianek, zalecamy użycie include.

headers​

W headers możemy zdefiniować klucze i wartości nagłówka HTTP.

Domyślnie zdefiniowane jest, że dane będą wysyłane i odbierane w formacie JSON.

start​

Funkcja callback wykonywana, gdy żądanie HTTP ma się rozpocząć.

success​

Funkcja callback wykonywana, gdy żądanie HTTP zostało wykonane pomyślnie na serwerze backendu.

Treść odpowiedzi jest dostępna w pierwszym argumencie funkcji.

fail​

Funkcja callback wykonywana, gdy żądanie HTTP wygenerowało jakiś błąd.

Szczegóły błędu są dostępne w pierwszym argumencie funkcji.

end​

Funkcja callback wykonywana, gdy żądanie HTTP zostało zakończone.

Jest wykonywana zawsze, niezależnie od tego, czy wystąpił sukces, czy błąd.

Użycie​

Aby wykonać usługę za pomocą żądania HTTP, definiujemy wartości parametrów, które konfigurują wymagania usługi.

W kluczu data wysyłamy same dane, czyli wszystkie parametry danych, które wyślemy do usługi backendu.

Zobacz podstawowy przykład żądania:

import _service from '@netuno/service-client';

_service({
url: "moja-usluga",
data: { id: 1, przyklad: "Witaj backendzie." },
start: () => {
console.log("Żądanie się rozpocznie.");
},
success: (response) => {
if (response.text) {
console.info("Odpowiedź tekstowa:", response.text);
}
if (response.json) {
console.info("Odpowiedź w JSON:", response.json);
}
},
fail: (error) => {
console.error("Nie udało się, błąd:", error);
},
end: () => {
console.log("Żądanie zostało zakończone.");
}
});

Końcowy URL uwzględni prefiks zdefiniowany globalnie w konfiguracji ogólnej.

Ponieważ nie zdefiniowano metody HTTP, żądanie zostanie wysłane z metodą domyślną zdefiniowaną w konfiguracji ogólnej, która domyślnie to GET.

Prosty​

Prosty przykład wykonania żądania typu POST:

import _service from '@netuno/service-client';

_service({
method: "POST",
url: "produkt",
data: { nazwa: "Kapelusz słomiany", ilosc: 20 },
success: ({json}) => {
console.info("Produkt utworzony pomyślnie:", json);
},
fail: (error) => {
console.error("Nie udało się utworzyć produktu:", error);
}
});

Formularz​

Kiedy używamy formularzy, możemy załadować klasę FormData z zawartością formularza, zobacz przykład:

import _service from '@netuno/service-client';

const formData = new FormData(document.getElementById("formularz"));
_service({
method: "PUT",
url: "klient",
data: formData,
success: ({json}) => {
if (json.result === true) {
alert("Klient zaktualizowany pomyślnie.");
} else {
alert("Nieprawidłowa odpowiedź.");
}
},
fail: (e) => {
console.log("Błąd", e);
}
});

Przesyłanie​

Prosty przykład przesyłania plików za pomocą klasy FormData:

import _service from '@netuno/service-client';

const formData = new FormData();
formData.append('uid', 'c476964a-eb8b-40d1-8907-4fee2e1532fb');
formData.append('plik', document.getElementById("polePlik").files[0]);
formData.append('innePole', 'wartosc...');
_service({
method: "PUT",
url: "/upload",
data: formData,
success: ({json}) => {
alert('Plik przesłany pomyślnie.');
},
fail: (e) => {
console.log("Błąd odpowiedzi", e);
}
});

Pobieranie​

Możemy pobierać pliki generowane dynamicznie przez usługi w sposób zintegrowany w kontekście strony.

BLOB - Duży Obiekt Binarny​

Aby pobrać odpowiedzi binarne, musimy ustawić blob na wartość true w podstawowej parametryzacji, a wygenerowane bajty otrzymujemy w blob odpowiedzi.

import _service from '@netuno/service-client';

_service({
url: "/download",
method: 'POST',
data: { uid: "98e25c93-d662-4b5c-8b89-d19b42e2c998", type: "pdf" },
blob: true, // Określa, że odpowiedź jest binarna.
success: ({blob}) => {
if (blob) {
const file = window.URL.createObjectURL(blob);
window.location.assign(file);
} else {
alert("Nieprawidłowa odpowiedź.");
}
},
fail: (e) => {
console.log("Błąd", e);
}
});

Niestandardowy plik​

Nazwa i typ pliku mogą być dostosowane we frontendzie; zależność DownloadJS jest bardzo przydatna w tym procesie.

Zainstaluj zależność DownloadJS:

bun add downloadjs

W kodzie można to wykorzystać w następujący sposób:

import _service from '@netuno/service-client';
import download from 'downloadjs';

_service({
url: "/services/raport/excel",
blob: true,
success: ({ blob }) => {
if (blob) {
download(blob, "raport.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
},
fail: (e) => {
console.log("Błąd", e);
}
});

_service({
url: "/services/raport/pdf",
blob: true,
success: ({ blob }) => {
if (blob) {
download(blob, "raport.pdf", "application/pdf");
}
},
fail: (e) => {
console.log("Błąd", e);
}
});

URL do usług​

Są sytuacje, w których musimy wygenerować URL usług, na przykład gdy istnieją usługi dostarczające zasoby, które powinny być zintegrowane w HTML lub CSS.

Wyraźnym przykładem jest Awatar — użytkownik definiuje swój obraz awatara, a do zaprezentowania możemy wygenerować końcowy URL w następujący sposób:

const linkAwatara = _service.url(`/profil/awatar?uid=${uzytkownik.uid}`)

Wygenerowany URL można wstrzyknąć do HTML lub do właściwości CSS, takiej jak background.

React & Ant.Design​

Ponieważ często używamy Reacta i Ant.Design, oto kilka przydatnych wskazówek.

Klasyczne importy:

import React, {useState} from "react";
import { Form, notification } from 'antd';
import _service from '@netuno/service-client';

Zdefiniuj stan przetwarzania:

const [ladowanie, ustawLadowanie] = useState(false);

Implementacja zdarzenia zapisu formularza, które wyśle obiekt z wypełnionymi wartościami w formacie JSON:

const przyWyslaniu = (values) => {
const przyBledzie = () => {
notification.error({
title: 'Błąd',
description: 'Nie udało się zapisać danych.'
});
};
_service({
url: '/services/klient',
method: 'POST',
data: values,
start: () => {
ustawLadowanie(true);
},
success: ({json}) => {
if (json.result === true) {
notification.success({
title: 'Sukces',
description: 'Dane zostały zapisane pomyślnie.'
});
return;
}
przyBledzie();
},
fail: (e) => {
console.error("Żądanie usługi nie powiodło się.", e);
przyBledzie();
},
end: () => {
ustawLadowanie(false);
},
});
};

W komponencie formularza Ant.Design definiuje się właściwość akcji, która wysyła dane za pomocą wcześniej zaimplementowanego zdarzenia, na przykład:

<Form onFinish={przyWyslaniu}>
...
</Form>

Przesyłanie​

Oto kompletny przykład przesyłania plików za pomocą formularza w Ant.Design i FormData:

import {useState} from 'react';
import _service from '@netuno/service-client';
import {Button, Form, Input, Upload, notification} from 'antd';
import { PlusOutlined } from '@ant-design/icons';

function FormularzPrzesylania() {
const [ladowanie, ustawLadowanie] = useState(false);
const przyWyslaniu = (values)=> {
const formData = new FormData();
formData.append("tytul", values.tytul);
formData.append("plik", values.plik.fileList[0].originFileObj);
_service({
method: "POST",
url: "plik/zapisz",
data: formData,
start: () => {
ustawLadowanie(true);
},
success: () => {
notification.success({
title: 'Formularz',
description: 'Twoje dane zostały zapisane pomyślnie.'
});
},
fail: (e) => {
console.error("Przesyłanie formularza nie powiodło się", e);
notification.error({
title: 'Formularz',
description: 'Nie udało się zapisać Twoich danych.'
});
},
end: () => {
ustawLadowanie(false);
}
});
};
return (
<Form onFinish={przyWyslaniu} layout="vertical">
<Form.Item label="Tytuł" name="tytul"
rules={[{ required: true, message: 'Proszę podać tytuł.' }]}
>
<Input />
</Form.Item>
<Form.Item label="Plik" name="plik"
rules={[{ required: true, message: 'Proszę dodać plik.' }]}
>
<Upload listType="text" maxCount={1}>
<button type="button" style={{color: 'inherit', cursor: 'inherit', border: 0, background: 'inherit'}}>
<PlusOutlined />
<div style={{marginTop: 8}}>Prześlij</div>
</button>
</Upload>
</Form.Item>
<Form.Item label={null}>
<Button type="primary" htmlType="submit" loading={ladowanie}>
Zapisz
</Button>
</Form.Item>
</Form>
);
}

export default FormularzPrzesylania;

Podsumowanie​

Service Client przyspiesza integrację z backendem w zakresie pobierania i wysyłania danych oraz plików.

Możliwe jest użycie dowolnej innej natywnej metody lub innej zależności do wykonania tych operacji, jednak Service Client został opracowany, aby ułatwić i przyspieszyć integracje, będąc elastycznym i bardzo intuicyjnym.

Głównym celem dostarczania przez Netuno Service Client w swoim ekosystemie jest zapewnienie, że rozwój jest jasny i szybki.

Podczas korzystania z Service Client, jeśli napotkasz jakikolwiek problem, zgłoś go w Issues projektu.

⭐ Pamiętaj, aby dać gwiazdkę na GitHubie projektu: