๐Ÿš€ kepler-http โ€“ REST API๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๋‹ค

์กฐ์ค€ํ˜•ยท2025๋…„ 4์›” 6์ผ
0

CHOP!

๋ชฉ๋ก ๋ณด๊ธฐ
10/20
post-thumbnail

๊ฐœ๋ฐœ ์ดˆ๋ฐ˜์—๋Š” ์ฃผ๋กœ ๋ชฉ์—… ๋ฐ์ดํ„ฐ๋กœ UI๋งŒ ๋น ๋ฅด๊ฒŒ ๊ตฌ์„ฑํ•ด์™”์ง€๋งŒ,

์ด์ œ๋Š” ์‹ค์ œ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•ด์„œ ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ , ์ƒํƒœ์— ๋”ฐ๋ผ ํ™”๋ฉด์ด ๋ฐ”๋€Œ๋„๋ก ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

์šฐ๋ฆฌ๋Š” Swagger๋กœ ์ •์˜๋œ REST API๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ–ˆ๊ณ ,
์ฒ˜์Œ์—” ๋‹น์—ฐํžˆ axios.get(), axios.post()๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๋˜์ง€ ์•Š์„๊นŒ ์ƒ๊ฐํ–ˆ๋‹ค.
ํ•˜์ง€๋งŒ API๊ฐ€ ๋Š˜์–ด๋‚˜๊ณ  ํ™”๋ฉด์ด ๋งŽ์•„์ง€๋ฉด์„œ,
โ€œ์ด๊ฑธ ์ผ์ผ์ด ๋‹ค ์งœ๋Š” ๊ฑด ๋„ˆ๋ฌด ๊ท€์ฐฎ๊ณ , ์œ ์ง€๋ณด์ˆ˜๋„ ์–ด๋ ต๋‹คโ€๋Š” ๊ฒฐ๋ก ์— ๋„๋‹ฌํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์ง์ ‘ ๋งŒ๋“  API ํ†ต์‹  ๊ตฌ์กฐ์ธ kepler-http ๋ฅผ ๋„์ž…ํ–ˆ๋‹ค.

๐Ÿ“ฆ kepler-http๋ž€?

๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด, kepler-http๋Š”
ํ•˜๋‚˜์˜ ์š”์ฒญ ๋‹จ์œ„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜ํ•˜๊ณ ,
fetcher ํ•จ์ˆ˜ ํ•˜๋‚˜๋กœ ๋ชจ๋“  ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋งŒ๋“  ํ†ต์‹  ๋ฏธ๋“ค์›จ์–ด๋‹ค.

์šฐ๋ฆฌ๋Š” ์š”์ฒญ ๋‹จ์œ„๋ฅผ "Wing(์œ™)"์ด๋ผ๊ณ  ๋ถ€๋ฅด๊ณ ,
fetcher ํ•จ์ˆ˜๋Š” "ShootingStar(์ŠˆํŒ…์Šคํƒ€)"๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.
์ด๋ฆ„์€ ๊ฝค ์œ ์พŒํ•˜์ง€๋งŒ, ๊ธฐ๋Šฅ์€ ์•„์ฃผ ๊ตฌ์กฐ์ ์ด๋‹ค.

โš™๏ธ kepler-http ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ

kepler-http๋Š” ๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ํด๋ž˜์Šค๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค:

  • Config โ€“ ๊ณตํ†ต axios ์„ค์ • (baseURL, timeout, ์ธ์ฆ ๋“ฑ)

  • Auth โ€“ ํ† ํฐ ์ €์žฅ ๋ฐ ๋ฆฌํ”„๋ ˆ์‹œ ์„ค์ •

  • Wing โ€“ ์š”์ฒญ ๋‹จ์œ„ (axios config ๊ฐ์ฒด ์—ญํ• )

์ด ์„ธ ๊ฐ€์ง€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์š”์ฒญ ํ๋ฆ„์ด ๋™์ž‘ํ•˜๋ฉฐ,
์ดํ›„์— ๋‚˜์˜ค๋Š” ShootingStar๋Š” ์ด ๊ตฌ์กฐ ์œ„์—์„œ ์‹ค์ œ ์š”์ฒญ์„ ๋‚ ๋ฆฌ๋Š” fetcher๋‹ค.

๐Ÿ“Œ Config โ€“ ํ”„๋กœ์ ํŠธ ์ „์ฒด axios ์ธ์Šคํ„ด์Šค ์„ค์ •

export class Config {
  baseUrl: string;
  timeout: number;
  headers: KeplerHeaders;
  auth: Auth;
}

Config๋Š” axios ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ณตํ†ต ์„ค์ • ๊ฐ์ฒด๋‹ค.

  • baseUrl: ๋ชจ๋“  API ์š”์ฒญ์˜ ๊ธฐ๋ณธ URL

  • timeout: ์š”์ฒญ ์ œํ•œ ์‹œ๊ฐ„

  • headers: ๊ณตํ†ต ํ—ค๋” (Authorization ๋“ฑ)

  • auth: ์ธ์ฆ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๋‹ด์€ Auth ์ธ์Šคํ„ด์Šค

์ด๊ฑด ๋ณดํ†ต ์•ฑ ์‹คํ–‰ ์ดˆ๊ธฐ์— ํ•œ ๋ฒˆ ์ƒ์„ฑ๋˜๊ณ ,
ShootingStar์˜ ์ƒ์„ฑ์ž์— ์ „๋‹ฌ๋œ๋‹ค:

const config = new Config('https://api.example.com', 10000, auth);
const shootingStar = new ShootingStar(config);

๐Ÿ” Auth โ€“ ํ† ํฐ ์ €์žฅ ๋ฐ refresh ํ๋ฆ„ ์„ค์ •

export class Auth {
  refreshUrl: string;
}
  • ๋กœ๊ทธ์ธ ์‹œ ๋ฐ›์€ accessToken, refreshToken์„ localStorage์— ์ €์žฅ

  • ๋กœ๊ทธ์•„์›ƒ ์‹œ localStorage ์ดˆ๊ธฐํ™”

  • ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ์‚ฌ์šฉํ•  refresh URL์„ ๊ด€๋ฆฌ

const auth = new Auth('/v1/auth/refresh', accessToken, refreshToken);

ShootingStar๋Š” ์š”์ฒญ ์ค‘ 401 ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด,
์ด Auth.refreshUrl์„ ์ด์šฉํ•ด ์ž๋™์œผ๋กœ ํ† ํฐ์„ ๊ฐฑ์‹ ํ•œ๋‹ค.

๐Ÿ“จ Wing โ€“ ํ•˜๋‚˜์˜ ์š”์ฒญ ๋‹จ์œ„ (axios config ๊ฐ์ฒด ์—ญํ• )

export class Wing {
  url: string;
  method: Method;
  needAuth: boolean;
  headers: KeplerHeaders;
  params: KeplerParam;
  data: KeplerBody;
}

Wing์€ ํ•˜๋‚˜์˜ ์š”์ฒญ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๋‹ด๊ณ  ์žˆ๋Š” ํด๋ž˜์Šค๋‹ค.
๋ง ๊ทธ๋Œ€๋กœ axios์˜ config ๊ฐ์ฒด๋ฅผ ํด๋ž˜์Šค๋กœ ๊ฐ์‹ผ ๊ฒƒ์ด๋‹ค.

const wing = new Wing(
  '/v1/users/123/example',
  Method.GET,
  false,
  exampleParam, // param
  {}           // body
);

ShootingStar.launch()๋Š” ์ด wing์„ ๋ฐ›์•„์„œ axios ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๐Ÿ“‹ ์ •๋ฆฌํ‘œ

ํด๋ž˜์Šค	์—ญํ• 											์‚ฌ์šฉ ์œ„์น˜
Config	axios ์ธ์Šคํ„ด์Šค ์ดˆ๊ธฐํ™”์šฉ ์„ค์ •						new ShootingStar(config)
Auth	ํ† ํฐ ์ €์žฅ ๋ฐ refresh ๋กœ์ง						config.auth, refresh ์‹œ ์‚ฌ์šฉ
Wing	์‹ค์ œ ์š”์ฒญ ๋‹จ์œ„ (url, method, param, body ํฌํ•จ)	shootingStar.launch(wing)

๐ŸŒ  ํ•ต์‹ฌ ๋กœ์ง: ShootingStar ์ฝ”๋“œ ๋ฆฌ๋ทฐ

์šฐ์„  ์ „์ฒด ๊ตฌ์กฐ ์ค‘ ๊ฐ€์žฅ ํ•ต์‹ฌ์ธ ShootingStar ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜์”ฉ ๋œฏ์–ด๋ณด์ž.
์ด fetcher๋Š” ๊ฒฐ๊ตญ axios ๋ž˜ํผ์ง€๋งŒ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ญํ• ์„ ๋” ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค:

  • ์š”์ฒญ ์ „์ฒ˜๋ฆฌ

  • ํ† ํฐ ํ•„์š” ์—ฌ๋ถ€ ํ™•์ธ ๋ฐ ํ—ค๋” ์„ค์ •

  • ์‘๋‹ต ์‹คํŒจ ์‹œ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰

  • ๋‹ค์‹œ ์žฌ์š”์ฒญ

๐Ÿ” 1. launch() ๋ฉ”์„œ๋“œ: ์š”์ฒญ์„ ๋‚ ๋ฆฌ๋Š” ์ง„์งœ fetcher

async launch<KeplerData>(wing: Wing): Promise<KeplerResponse<KeplerData>> {

์ด ํ•จ์ˆ˜๋Š” ๋ชจ๋“  ์š”์ฒญ์„ ๋‹ค ์ด ํ•จ์ˆ˜ ํ•˜๋‚˜๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.
wing ๊ฐ์ฒด์— url, method, param, body, header ๋“ฑ์ด ๋‹ค ๋“ค์–ด ์žˆ๊ณ ,
ํ•„์š”ํ•˜๋ฉด ์ž๋™์œผ๋กœ accessToken๋„ ๋ถ™๋Š”๋‹ค.

์ฃผ์š” ํ๋ฆ„ ์ •๋ฆฌ:

if (wing.needAuth) {
  if (localStorage.getItem('accessToken') == '') throw new Error("UnAuthorizedRequest");
  wing.headers = {
    common: {
      Authorization: `Bearer ${localStorage.getItem('accessToken')}`
    },
    post: {}
  };
}

์ธ์ฆ์ด ํ•„์š”ํ•œ ์š”์ฒญ์ด๋ฉด localStorage์—์„œ accessToken์„ ๊บผ๋‚ด๊ณ ,

๊ทธ๊ฑธ Authorization ํ—ค๋”์— ๋ถ™์ธ๋‹ค.

๊ทธ๋ฆฌ๊ณ  axios ์š”์ฒญ:

return await this._instance.request(wing);

๐Ÿ” 2. ํ† ํฐ ๋งŒ๋ฃŒ ๋Œ€์‘: ์‹คํŒจํ•˜๋ฉด refresh ํ›„ ์žฌ์š”์ฒญ

if (e.response.status === axios.HttpStatusCode.Unauthorized) {
  let access_token = await this._refreshToken();
  wing.headers.common.Authorization = `Bearer ${access_token}`;
  return await this._instance.request(wing);
}

์ฒ˜์Œ ์š”์ฒญ์— ์‹คํŒจํ–ˆ๋”๋ผ๋„,
401 Unauthorized๋ผ๋ฉด refreshToken์„ ์‚ฌ์šฉํ•ด์„œ accessToken์„ ์ƒˆ๋กœ ๋ฐ›๊ณ ,
๋‹ค์‹œ ๋™์ผ ์š”์ฒญ์„ ์žฌ์‹œ๋„ํ•œ๋‹ค.

๐Ÿ” 3. _refreshToken(): accessToken ๊ฐฑ์‹ 

private async _refreshToken(): Promise<string> {
  let wing = new Wing(this._config.auth.refreshUrl, Method.POST, true, {}, {
    refresh_token: localStorage.getItem('refreshToken')
  });
  ...
  localStorage.setItem('accessToken', res.data.access_token);
  return res.data.access_token;
}

refresh token์„ ์‚ฌ์šฉํ•ด์„œ ์ƒˆ ํ† ํฐ์„ ๋ฐ›์•„์˜ค๊ณ ,
localStorage์— ์ €์žฅ ํ›„ ๋ฐ˜ํ™˜.

๋•๋ถ„์— ์šฐ๋ฆฌ๋Š” ํ† ํฐ ๋งŒ๋ฃŒ๋ฅผ ์‹ ๊ฒฝ ์“ธ ํ•„์š” ์—†์ด launch()๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿงฑ ํ•จ๊ป˜ ์“ฐ์ด๋Š” ํƒ€์ž… ๊ตฌ์กฐ๋“ค

kepler-http๋Š” API ์š”์ฒญ์„ ๊ตฌ์„ฑํ•  ๋•Œ
๋ชจ๋“  ์š”์†Œ(body, param, headers, response)๋ฅผ ๋ช…ํ™•ํ•œ ํƒ€์ž…์œผ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์ •์˜ํ•œ๋‹ค.

์ด๊ฑด ๋‹จ์ˆœํžˆ ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์œ„ํ•œ ๊ฒƒ๋งŒ์ด ์•„๋‹ˆ๋ผ,
์š”์ฒญ์„ ๋ชจ๋“ˆํ™”ํ•˜๊ณ  ์ผ๊ด€๋œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์„ค๊ณ„๋‹ค.

๐Ÿ“Œ KeplerHeaders.ts

export default interface KeplerHeaders {
  common: { Authorization: string };
  post: {};
}

์š”์ฒญ ํ—ค๋”๋ฅผ ๊ณตํ†ต(common)๊ณผ POST ์ „์šฉ(post)์œผ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ํ™•์žฅ์„ฑ ํ™•๋ณด

๋Œ€๋ถ€๋ถ„์˜ ์š”์ฒญ์—์„œ๋Š” common.Authorization๋งŒ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฉ”์„œ๋“œ๋ณ„ ํ—ค๋” ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๊ฐ€๋Šฅ

๐Ÿ“Œ KeplerResponse.ts

export default interface KeplerResponse<T extends KeplerData> {
  status: number;
  data: T;
}

๋ชจ๋“  ์‘๋‹ต์€ status์™€ data๋กœ ๊ตฌ์„ฑ

data๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ง€์ •ํ•œ ํƒ€์ž… (ProfileData, PostData ๋“ฑ)์„ ์ œ๋„ค๋ฆญ์œผ๋กœ ์ „๋‹ฌ

๐Ÿ“Œ KeplerData.ts

export default interface KeplerData {
  [key: string]: any;
}

๋ชจ๋“  ์‘๋‹ต ํƒ€์ž…์˜ ๋ฒ ์ด์Šค ์ธํ„ฐํŽ˜์ด์Šค

์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ํ™•์žฅํ•ด์„œ ๊ตฌ์ฒด์ ์ธ ์‘๋‹ต ๊ตฌ์กฐ๋ฅผ ์ •์˜

์˜ˆ์‹œ: schemas/profile/Data.ts
export interface ProfileData extends KeplerData {
  id: string;
  nickname: string;
  profileImage: string;
  level: number;
}

๐Ÿ“Œ KeplerParam.ts

export default interface KeplerParam {
  [key: string]: string | number;
}

URL ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์šฉ ํƒ€์ž…

์š”์ฒญ๋งˆ๋‹ค ํ•„์š”ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋”ฐ๋กœ ์ •์˜ํ•ด์„œ ๋„˜๊ธด๋‹ค

์˜ˆ์‹œ: schemas/profile/Param.ts
export interface GetProfilesParam extends KeplerParam {
  world_id: string;
  game_id: string;
}

๐Ÿ“Œ KeplerBody.ts

export default interface KeplerBody {
  [key: string]: any;
}

์š”์ฒญ body ํƒ€์ž…์˜ ๋ฒ ์ด์Šค

POST, PATCH ๋“ฑ์—์„œ ์‚ฌ์šฉํ•˜๋Š” payload ์ •์˜

์˜ˆ์‹œ: schemas/profile/Body.ts
export interface EditProfileBody extends KeplerBody {
  nickname: string;
  profileImage: string;
}

โœ… ์š”์•ฝ
ํŒŒ์ผ ์„ค๋ช… ์˜ˆ์‹œ

KeplerHeaders.ts	ํ—ค๋” ๊ตฌ์กฐ ์ •์˜	Authorization ๋“ฑ
KeplerResponse.ts	๋ชจ๋“  ์‘๋‹ต์˜ ๊ณตํ†ต ํ˜•ํƒœ	status, data
KeplerData.ts		์‘๋‹ต ํƒ€์ž…์˜ base	ProfileData, PostData
KeplerParam.ts		URL ํŒŒ๋ผ๋ฏธํ„ฐ ์ •์˜	world_id, game_id
KeplerBody.ts		์š”์ฒญ ๋ฐ”๋”” ์ •์˜	nickname, profileImage

์ด๋Ÿฐ ํƒ€์ž… ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ
๊ฐ ์š”์ฒญ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์กฐ๊ฐ์„ ์กฐ๋ฆฝํ•œ ๋’ค
Wing ๊ฐ์ฒด๋กœ ์š”์ฒญ์„ ์ƒ์„ฑํ•˜๊ณ ,
ShootingStar.launch()๋กœ ์ „์†กํ•˜๋Š” ๊ตฌ์กฐ๋‹ค.

profile
์ฝ”๋ฆฐ์ด

0๊ฐœ์˜ ๋Œ“๊ธ€