Overview
TypeScript용 RxJS 기반 타입 세이프 HTTP client와 선택적 Next.js/RSocket adapter
@byeolnaerim/typed-rx-http는 OpenAPI-style Paths 타입을 주입해 URL, HTTP method, path/query parameter와 request body를 타입으로 검사하면서 요청하고, 결과를 RxJS Observable로 반환하는 HTTP client입니다.
제가 이 라이브러리에서 원했던 것은 거창한 HTTP 추상화가 아니었습니다. 백엔드에서 방금 작성한 URL과 request/response DTO를 프론트에서 또 작성하지 않고, 생성된 함수를 import해서 바로 호출하는 것이었습니다.
Core 기본 사용 방식
기본 흐름은 OpenAPI-style Paths 타입을 준비하고, 필요하면 HeaderStore를 만든 뒤 createHttpClient<Paths>()로 client를 생성하고 callApi<R>()를 호출하는 것입니다. 자동 service 생성기나 WebFlux 백엔드는 이 흐름의 필수 조건이 아닙니다.
요청 타입은 Paths에서 결정되고, 응답 타입은 callApi<R>()의 R을 호출자가 선택합니다. 특정 ResponseWrapper도 강제하지 않습니다. NDJSON, CSR cache, session auth, Next.js와 RSocket 기능은 필요한 경우에만 추가합니다.
Origin story
저는 프론트에서 Next.js와 TypeScript를 사용하고, 백엔드에서는 Java와 WebFlux를 사용합니다. 이 조합으로 프로젝트를 만들다 보니 똑같은 내용을 양쪽에서 반복해서 작성하는 일이 너무 많았습니다.
백엔드에서는 entity와 request/response DTO를 만들고 endpoint를 작성합니다. 그런데 프론트에서 그 endpoint를 호출하려면 URL을 다시 타이핑하고, 백엔드 DTO와 거의 같은 type이나 interface를 또 만들어야 했습니다. 솔직히 말하면 백엔드에서 제가 작성했던 것을 프론트에서 한 번 더 작성하는 것과 다름이 없었습니다.
이런 반복은 코드의 양만 늘리는 것이 아니라, 한쪽의 필드나 URL이 바뀌었을 때 다른 쪽을 빠뜨릴 가능성도 같이 늘렸습니다. 그래서 백엔드의 REST API 사양을 프론트 HTTP client 코드로 예측 가능하게 생성해야 할 필요성을 절실하게 느꼈습니다.
그 과정에서 실제 프로젝트인 nplauction.com에 백엔드용reactive-mongo-dsl과webflux-fe-dev-assistant의 프로토타입을, 프론트에는 typed-rx-http의 프로토타입을 먼저 적용했습니다. typed-rx-http는 처음부터 별개의 아이디어로 시작했다기보다, webflux-fe-dev-assistant를 만들면서 프론트에 필요한 짝을 함께 만들다 보니 부가적으로 태어난 라이브러리에 가깝습니다.
Swagger 자동 생성 프로젝트에서의 사용 방식
Swagger service 자동 생성을 사용하는 프로젝트에서는 화면 코드에서 createHttpClient나 URL을 매번 작성하지는 않습니다. 프로젝트에 commonService.ts라는 프로젝트 소유 공용 HTTP adapter를 한 번 만들고, OpenAPI에서 생성된 service 함수만 import해서 사용합니다. commonService.ts는 라이브러리가 생성하는 파일명이 아니라 프로젝트가 정하는 파일명이며, 시작하기/HTTP Client 문서에서 전체 코드를 먼저 확인할 수 있습니다.
const response = await firstValueFrom(
workplacesSearch({ params: { keyword: "kim" } }),
);위 코드에는 URL 문자열이나 수동으로 작성한 response interface가 없습니다. URL, HTTP method, parameter type과 response type은 생성된 service에 포함됩니다. 한 번의 응답은 firstValueFrom으로 받고, 작업 진행 상황처럼 여러 값이 이어지는 요청은subscribe로 받을 수 있습니다.
어디에서 가장 잘 맞을까
백엔드와 프론트가 분리되어 있고 백엔드에서 유효한swagger.json을 제공할 수 있다면 TypeScript 프론트엔드에서 반복 작업을 줄일 수 있습니다. Java WebFlux functional endpoint를 사용한다면 webflux-fe-dev-assistant를 조합해 문서 생성부터 service 생성까지 연결할 수 있습니다.
typed-rx-http Core는 WebFlux에 종속되지 않습니다. 백엔드 구현과 관계없이 OpenAPI 사양과 호환되는 TypeScript paths type이 있다면 문자열 URL과 요청 타입을 매번 손으로 작성하는 일을 줄이는 데에는 사용할 수 있습니다. RSocket client는 /rsocket 엔트리포인트에서 직접 사용할 수 있고, route/request/response service 자동 생성이 필요한 프로젝트에서만 AsyncAPI generator를 추가로 사용할 수 있습니다.