Skip to content

Comments

🔧 Service / http-service#156

Open
ancientbag wants to merge 18 commits intomasterfrom
feature/http-service
Open

🔧 Service / http-service#156
ancientbag wants to merge 18 commits intomasterfrom
feature/http-service

Conversation

@ancientbag
Copy link
Contributor

@ancientbag ancientbag commented May 12, 2025

@byndyusoft-ui/http-service

Http service

Installation

npm i @byndyusoft-ui/http-service

Usage

To start using this service you need to create a new class instance of HttpService and provide restController option.

There are two classes ready to be used as restControllers: HttpRestControllerFetch and HttpRestControllerAxios. For fetch and axios.

    const restController = new HttpRestControllerFetch();
    const httpService = new HttpService({
        restController
    });

    httpService.get("http://localhost:3000/api/");

Example of usage with HttpRestControllerFetch

    const restController = new HttpRestControllerFetch();
    const httpService = new HttpService({
        restController
    });

    const handleGetProducts = async (): Promise<void> => {
        const products = await httpService
            .get('http://localhost:3322/products')
            .then(async r => (await r.json()) as IProduct[]);

        setProducts(products);
    };

Example of usage with HttpRestControllerAxios

    const restController = new HttpRestControllerAxios();
    const httpService = new HttpService({
        restController
    });

    const handleGetProducts = async (): Promise<void> => {
        const products = await httpService.get<IProduct[]>('http://localhost:3322/products').then(r => r.data);

        setProducts(products);
    };

You can define own HttpRestController and pass it like this

    // myOwnHttpRestController.ts
    import { HttpRestController } from '@byndyusoft-ui/http-service';

    class HttpRestControllerCustom extends HttpRestController {
        constructor() {
            super();
        }

        get = async <R>(url: string, headers?: Headers): Promise<R> => {
            return fetch(url, { method: 'GET', headers: { ...headers } }) as Promise<R>;
        };

        post = async <R>(url: string, body: object, headers?: Headers): Promise<R> => {
            return fetch(url, {
                method: 'POST',
                body: JSON.stringify(body),
                headers: { ...headers }
            }) as Promise<R>;
        };

        patch = async <R>(url: string, body: object, headers?: Headers): Promise<R> => {
            return fetch(url, {
                method: 'PATCH',
                body: JSON.stringify(body),
                headers: { ...headers }
            }) as Promise<R>;
        };

        put = async <R>(url: string, body: object, headers?: Headers): Promise<R> => {
            return fetch(url, {
                method: 'PUT',
                body: JSON.stringify(body),
                headers: { ...headers }
            }) as Promise<R>;
        };

        delete = async <R>(url: string, body: object = {}, headers?: Headers): Promise<R> => {
            return fetch(url, {
                method: 'DELETE',
                body: JSON.stringify(body),
                headers: { ...headers }
            }) as Promise<R>;
        };
    }

    export default HttpRestControllerCustom;
    // httpService.ts
    import HttpRestControllerCustom from './myOwnHttpRestController.ts'

    const restController = new HttpRestControllerCustom();
    const httpService = new HttpService({
        restController
    });

    httpService.get("http://localhost:3000/api/");

@ancientbag ancientbag marked this pull request as ready for review May 12, 2025 12:02
@ancientbag ancientbag changed the title Service / http-service 🔧 Service / http-service May 12, 2025
Copy link
Member

@sadcitizen sadcitizen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Идея с единым фасадом для axios и fetch мне нравится, но в текущей реализации сам сервис по сути ничего не делает, а вся логика лежит в контроллерах.

Хочется сначала собрать все варианты использования этого сервиса. Их много в наших проектах. И от них уже отталкиваться при разработке.

@@ -0,0 +1,16 @@
abstract class HttpRestController {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для чего слово Rest в названии?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Предполагалось что данная сущность будет рулить GET, POST, ..., запросами.

@@ -0,0 +1,16 @@
abstract class HttpRestController {
abstract get<R>(...args: any[]): Promise<R>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы у этих методов задал более жесткие контракты. У каждого метода есть url, есть параметры, у некоторых точно должно быть тело запроса или хотя бы null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно попробовать. В текущей реализции просто больше свободы

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если нужна свобода, то тогда зачем типизация? Она ограничивает свободу.

abstract patch<R>(...args: any[]): Promise<R>;
abstract put<R>(...args: any[]): Promise<R>;
abstract delete<R>(...args: any[]): Promise<R>;
public setHeader: (...args: any[]) => void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А как мне задать baseUrl или timeout для запросов?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это можно добавить. Пока только напрямую через инстанс контроллера

import { IHttpServiceOptions } from './httpService.types';
import HttpRestController from './restController';

class HttpService<RestController extends HttpRestController> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем нужна ещё одна сущность, если запросы можно контроллером дергать?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хотел сделать чтобы главный класс делал базовые штуки, по типу установки хэдеров, таймаутов, бейсурл, но в конечном счете, кажется, что действительно, проще через описанный класс контроллера это сделать =(

this.axiosInstance.defaults.headers[key] = value;
};

get = async <T, R = AxiosResponse<T>>(url: string, params: object = {}, options: object = {}): Promise<R> => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут слабая типизация и тип AxiosResponse будет торчать наружу.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не понял проблемы

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я этот подход взял с ЛМ АТ :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

object использовать нельзя

post = async <R = Response>(url: string, body: object, headers?: Headers): Promise<R> => {
return fetch(url, {
method: 'POST',
body: JSON.stringify(body),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А если body нет?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

body обязательный параметр

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я могу методом post мутировать какое-то состояние на сервере, при этом в запросе не будет никакого тела. Например, запрос POST https://blablabla.com/things/1/do-something будет менять статус сущности согласно статусной модели, но в самом запросе нет тела.

@sadcitizen
Copy link
Member

Класс или тип ошибки тоже, кстати, можно свои определить. Можно учесть network error.

@ancientbag
Copy link
Contributor Author

Класс или тип ошибки тоже, кстати, можно свои определить. Можно учесть network error.

Я не вижу как типы ошибок использовать в этом сервисе. Он больше про инфраструктурный сетап http пакетов. Например, если я буду использовать use-query от tanstack, то там в ответе можно добраться до кода ошибки, единственное, что он у них там не типизирован, там просто Number (во всяком случае, в тех версиях, которыми я пользовался)

@ancientbag
Copy link
Contributor Author

Идея с единым фасадом для axios и fetch мне нравится, но в текущей реализации сам сервис по сути ничего не делает, а вся логика лежит в контроллерах.

Хочется сначала собрать все варианты использования этого сервиса. Их много в наших проектах. И от них уже отталкиваться при разработке.

Думаю, можно убрать этот слой и оставить классы для fetch и axios'а. Пока не представляю для чего может HttpService понадобиться, как будто он излишен

@sadcitizen
Copy link
Member

Класс или тип ошибки тоже, кстати, можно свои определить. Можно учесть network error.

Я не вижу как типы ошибок использовать в этом сервисе. Он больше про инфраструктурный сетап http пакетов. Например, если я буду использовать use-query от tanstack, то там в ответе можно добраться до кода ошибки, единственное, что он у них там не типизирован, там просто Number (во всяком случае, в тех версиях, которыми я пользовался)

У тебя запрос может выполнится удачно или с ошибкой. То есть ошибка это результат вызова, почему её надо где-то отдельно держать? А тип ошибки это параметр самой ошибки.

@sadcitizen
Copy link
Member

Идея с единым фасадом для axios и fetch мне нравится, но в текущей реализации сам сервис по сути ничего не делает, а вся логика лежит в контроллерах.
Хочется сначала собрать все варианты использования этого сервиса. Их много в наших проектах. И от них уже отталкиваться при разработке.

Думаю, можно убрать этот слой и оставить классы для fetch и axios'а. Пока не представляю для чего может HttpService понадобиться, как будто он излишен

Нужно описать контракт сервиса, который бы покрыл большинство типовых сценариев. А сервисы на базе fetch или axios будут реализациями этого контракта.

@sadcitizen
Copy link
Member

Я бы ещё переименовал пакет в http-client. Общепринятое название.

@SmorodinVik
Copy link
Contributor

Изучил как мы используем axios на наших проектах и есть ощущение, что в большинстве случаев его можно заменить на
более легкую обертку над fetch.

Список того что нам нужно:

  1. Запросы get, post, patch, put, delete
  2. Возможность определния базового url
  3. Возможность определение токена авторизации
  4. Механизм обновления токена
  5. Отменяемые запросы
  6. Типизация возвращаемых ответов и ошибок
  7. Возможность передавать объект с параметрами запроса которые преобразуются в query params и добавляются к урлу

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants