Klien HTTP

Pertama, jelaskan cara menggunakan Core client secara langsung, kemudian jelaskan contoh integrasi opsional dari adapter HTTP umum yang digunakan oleh layanan otomatis.


Penggunaan langsung Core client.

Paths yang disuntikkan ke createHttpClient<Paths>() adalah kontrak permintaan. URL dan metode berasal dari kunci Paths, queryString dan pathVariable berasal dari parameters, dan body ditentukan dari requestBody.

Tipe respons ditentukan oleh R yang dipanggil oleh callApi<R>(). Core tidak secara otomatis menyimpulkan tipe respons dari respons OpenAPI.

httpClient.ts
ts
import {
  createHeaderStore,
  createHttpClient,
} from "@byeolnaerim/typed-rx-http";
import type { paths as Paths } from "./@types/ApiTypes";

export const headerStore = createHeaderStore({
  "Content-Type": "application/json",
});

export const client = createHttpClient<Paths>({
  baseUrl: process.env.NEXT_PUBLIC_API_URL!,
  headerStore,
});

export const getAdminList = () => {
  return client.callApi<{ items: any[]; total: number }>({
    url: "/api/account/search/admin/get-list",
    method: "get",
    queryString: { pageNumber: 1, pageSize: 20 },
  });
};

Bentuk respons dapat dipilih per API.

typed-rx-http tidak memaksa ResponseWrapper. Jika respons server adalah wrapper, tentukan tipe wrapper sebagai R, jika tidak, tentukan tipe respons yang sebenarnya.

ts
type ResponseWrapper<T> = { data: T; message?: string; resultType?: string };

return client.callApi<ResponseWrapper<{ requestId: string }>>({
  url: "/api/bid/create/bid/request",
  method: "post",
  body,
});

Opsional: adapter umum proyek + layanan otomatis.

Konfigurasi commonService di bawah ini bukanlah struktur wajib dari Core, tetapi pola integrasi proyek yang ingin menerapkan otentikasi, header SSR, dan kebijakan cache sekaligus ke beberapa layanan yang dihasilkan.

Apa yang dilakukan commonService proyek.

commonService bukanlah nama kelas khusus yang disediakan oleh @byeolnaerim/typed-rx-http, tetapi merujuk pada file milik pengguna yang digunakan untuk mengumpulkan kebijakan HTTP umum proyek di satu tempat. Nama file bebas, dan dalam contoh di bawah ini menggunakan nama proyek nyata yaitu rxjsHttpService.ts.

Tanpa mengulangi createHttpClient di layar dan service yang dihasilkan otomatis, file ini menggunakan callApi dan callApiStream yang dihasilkan sebagai titik masuk umum. Oleh karena itu, jika aturan pembaruan otentikasi, pengiriman header SSR, cache, dan penanganan kesalahan dimodifikasi sekali, maka akan diterapkan secara seragam ke semua service yang dihasilkan.

Lihat kode lengkap commonService/rxjsHttpService.

1. Minimum lengkap

Inti dari commonService adalah kode di bawah ini. Buat headerStore dan klien HTTP bertipe sekali, lalu ekspor callApi/callApiStream yang digunakan secara umum oleh layanan yang dihasilkan. uploadFile dan createSSEObservable juga dapat diekspos dari klien yang sama.

rxjsHttpService.ts
ts
import {
	createHeaderStore,
	createHttpClient,
	type ServiceArguments,
} from "@byeolnaerim/typed-rx-http";
import type { Observable } from "rxjs";

import type { paths } from "./@types/ApiTypes";

export const headerStore = createHeaderStore({
	"Content-Type": "application/json",
});

export const service = createHttpClient<paths>({
	baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
	headerStore,
});

export const callApi = <
	R,
	TPath extends keyof paths = keyof paths,
	TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
	serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
): Observable<R> => {
	return service.callApi<R, TPath, TMethod>(serviceArguments);
};

export const callApiStream = <
	R,
	TPath extends keyof paths = keyof paths,
	TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
	serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
): Observable<R> => {
	return service.callApiStream<R, TPath, TMethod>(serviceArguments);
};

export const uploadFile = service.uploadFile;
export const createSSEObservable = service.createSSEObservable;
2. Next.js + otentikasi sesi + cache CSR/SSR yang dapat diperluas

Di bawah ini adalah contoh proyek lengkap yang menambahkan otentikasi sesi, header SSR, dan cache CSR/SSR ke adapter umum yang sama. Jika proyek yang dihasilkan mengimpor pembungkus cache, bentuk ini menjadi acuan. Cukup salin, lalu sesuaikan setLogin import, posisi ApiTypes dan CacheNames, jalur API otentikasi, variabel lingkungan, dan jalur tidak sah sesuai dengan proyek Anda.

rxjsHttpService.ts
ts
import {
	createCsrCache,
	createHeaderStore,
	createHttpClient,
	createSessionAuth,
} from "@byeolnaerim/typed-rx-http";

import type {
	CacheForService,
	ServiceArguments,
} from "@byeolnaerim/typed-rx-http";

import { defer, Observable } from "rxjs";

import { setLogin } from "@ui/handler/hooks/useAccounts";

import type { paths } from "./@types/ApiTypes";

import type { CacheNames } from "./@types/CacheNames";

export const redirectToUnauthorizedOnServer401 = async () => {
	const { headers } = await import("next/headers");
	const { redirect } = await import("next/navigation");

	const h = await headers();
	const pageUrl = h.get("x-page-url") || "/";
	const redirectUri = encodeURIComponent(pageUrl);

	redirect(`/unauthorized?redirect_uri=${redirectUri}&logout=true`);
};

export const headerStore = createHeaderStore({
	"Content-Type": "application/json",
});

const headersProvider = async () => {
	if (typeof window !== "undefined") {
		/**
		 * 라이브러리쪽 코드가
		 * if (headersProvider) return headersProvider();
		 * if (headerStore) return headerStore.get();
		 * 이렇게 되어있음 같이 못씀, 방향자체는 맞음. 라이브러리쪽에서 marge해버리면 csr/ssr 구분못함
		 */
		return headerStore.get();
	}

	const { cookies } = await import("next/headers");

	const cookieStore = await cookies();
	const cookieHeader = cookieStore
		.getAll()
		.map((c) => `${c.name}=${c.value}`)
		.join("; ");

	const authorization = cookieStore.get("Authorization")?.value;
	const accessToken = cookieStore.get("accessToken")?.value;

	return {
		...(cookieHeader ? { Cookie: cookieHeader } : {}),
		...(authorization
			? {
					Authorization: authorization.startsWith("Bearer ")
						? authorization
						: `Bearer ${authorization}`,
				}
			: {}),
		...(!authorization && accessToken
			? {
					Authorization: accessToken.startsWith("Bearer ")
						? accessToken
						: `Bearer ${accessToken}`,
				}
			: {}),
	};
};

export const service = createHttpClient<paths>({
	baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
	onServer401: redirectToUnauthorizedOnServer401,
	defaultErrorMessage: () => {
		return "알 수 없는 오류가 발생했습니다.";
	},
	headerStore,
	headersProvider,
	dropAuthWhenCacheControl: true,
});

const sessionAuth = createSessionAuth({
	headerStore,
	tokenUrl: "/api/auth/token",
	refreshUrl: "/api/auth/token/refresh",
	logoutUrl: "/api/auth/logout",
	onLoginChange: (loggedIn) => {
		if (typeof window !== "undefined") {
			setLogin(loggedIn);
		}
	},
});

export const callApi = <
	R,
	TPath extends keyof paths = keyof paths,
	TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
	serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
): Observable<R> => {
	return defer(() =>
		service.callApi<R, TPath, TMethod>(serviceArguments),
	).pipe(sessionAuth.withSessionAuth());
};

export const callApiStream = <
	R,
	TPath extends keyof paths = keyof paths,
	TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
	serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
): Observable<R> => {
	return defer(() =>
		service.callApiStream<R, TPath, TMethod>(serviceArguments),
	).pipe(sessionAuth.withSessionAuth());
};

// CSR cache
const csrCache = createCsrCache<CacheNames>();

export const callApiClientCache = <
	R,
	TPath extends keyof paths = keyof paths,
	TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
	serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
	cacheForService: CacheForService<CacheNames>,
): Observable<R> => {
	return csrCache.callApiCsrCache(callApi, serviceArguments, cacheForService);
};

export const removeCsrCache = csrCache.removeCsrCache;

export const callApiServerCache = <
	R,
	TPath extends keyof paths = keyof paths,
	TMethod extends keyof paths[TPath] & string = keyof paths[TPath] & string,
>(
	serviceArguments: ServiceArguments<paths, TPath, TMethod, R>,
	cacheForService: CacheForService<CacheNames>,
): Observable<R> => {
	if (typeof window !== "undefined") {
		return callApiClientCache(serviceArguments, cacheForService);
	}

	return new Observable<R>((subscriber) => {
		let closed = false;

		import("@byeolnaerim/typed-rx-http/next")
			.then(({ callApiSsrCache }) => {
				if (closed) {
					return;
				}

				const subscription = callApiSsrCache<paths, R, CacheNames>({
					baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
					serviceArguments: serviceArguments as ServiceArguments<
						paths,
						keyof paths,
						keyof paths[keyof paths] & string,
						R
					>,
					cacheForService,
					headersProvider,
					onServer401: redirectToUnauthorizedOnServer401,
				}).subscribe(subscriber);

				subscriber.add(() => {
					closed = true;
					subscription.unsubscribe();
				});
			})
			.catch((error) => {
				if (!closed) {
					subscriber.error(error);
				}
			});

		return () => {
			closed = true;
		};
	});
};

Peran berdasarkan konfigurasi kode

paths

Ini adalah tipe jalur OpenAPI yang digunakan saat createHttpClient dan ServiceArguments memeriksa URL, metode, variabel jalur, kueri, dan tipe body.

headerStore

Menyimpan header umum seperti Content-Type dan Authorization di browser dan membagikannya dengan otentikasi sesi serta koneksi RSocket.

headersProvider

CSR mengembalikan headerStore, sedangkan SSR membaca Cookie dan Authorization dari permintaan saat ini untuk memberikan header yang berbeda pada setiap permintaan.

service

Klien HTTP bertipe nyata dengan URL dasar, header umum, penanganan 401 server, dan pesan kesalahan dasar.

sessionAuth

Menyinkronkan token sesi sebelum permintaan dan jika 401, mencoba kembali Observable asli setelah refresh, dan jika gagal, menjalankan alur logout.

callApi / callApiStream

Fungsi umum yang diimpor oleh layanan REST yang dihasilkan secara otomatis dan layanan aliran NDJSON. Keduanya mengembalikan Observable RxJS.

callApiClientCache / callApiServerCache

Wrapper cache yang memanfaatkan cache CSR di browser dan memuat cache SSR dari @byeolnaerim/typed-rx-http/next secara dinamis di server.

Cara terhubung dengan layanan yang dihasilkan secara otomatis

Generator menghitung jalur relatif dari commonServiceFile dan menambahkan impor ke setiap layanan. Antarmuka tidak memanggil file umum secara langsung, tetapi mengimpor fungsi yang dihasilkan, di mana fungsi yang dihasilkan mengubah path, params, body menjadi pathVariable, queryString, body dari ServiceArguments dan meneruskannya ke callApi.

generateSwagger.cjs
js
generateSwaggerFromHttp({
  serviceDir: "./src/handler/service",
  typesDir: "./src/handler/service/@types",
  commonServiceFile:
    "./src/handler/service/rxjsHttpService.ts",
});

Satu respons adalah firstValueFrom

REST API yang merespons sekali dan selesai seperti pencarian, penyimpanan, dan penghapusan dapat menerima Observable yang dihasilkanfirstValueFromsehingga dapat digunakan dengan alami di dalam fungsi async yang ada.

BusinessSearchPage.tsx
tsx
import { workplacesSearch } from "@handler/service/auto/ApiBusinessService";
import { firstValueFrom } from "rxjs";

const response = await firstValueFrom(
  workplacesSearch({
    params: {
      source: "EIW",
      q: keyword,
      size: 20,
      nextToken: nextToken || undefined,
    },
  }),
);

Di layar hanya paramsyang terlihat, tetapi di dalam layanan yang dihasilkan terdapat URL, metode HTTP, dan tipe respons. Parameter kueri selalu{ params: { ... } }dikirim sebagai.

Beberapa respons adalah subscribe

Permintaan yang menerima beberapa nilai seperti kemajuan tugas atau aliran RSocketsubscribeakan diterima, dan langganan akan dibatalkan saat komponen menghilang.

BatchJobPage.tsx
tsx
const subscription = processBatchJobs({
  body: targets,
}).subscribe({
  next: (event) => {
    setProcessedCount(event.processedCount);
  },
  error: (error) => {
    setErrorMessage(error.message);
  },
  complete: () => {
    setIsSubmitting(false);
  },
});

return () => subscription.unsubscribe();

Jika menggunakan callApi secara langsung

Untuk proyek yang tidak menggunakan generator layanan atau memeriksa endpoint sementara sebelum pembuatan, Anda dapat memanggil callApi dari layanan umum secara langsung. Tipe permintaan diperiksa di jalur dan tipe respons ditentukan oleh pemanggil secara generik.

TemporaryService.ts
ts
return callApi<HealthResponse>({
  url: "/health",
  method: "get",
});
© 2026 Byeolnaerim. Semua hak dilindungi.PengenalanKebijakan Penanganan Data Pribadi