Skip to main content

REST API์—์„œ ํŽ˜์ด์ง€ ๋งค๊น€ ์‚ฌ์šฉ

REST API์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ์‘๋‹ต์„ ํƒ์ƒ‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

ํŽ˜์ด์ง€ ๋งค๊น€ ์ •๋ณด

REST API์˜ ์‘๋‹ต์— ๋งŽ์€ ๊ฒฐ๊ณผ๊ฐ€ ํฌํ•จ๋˜๋ฉด GitHub์€(๋Š”) ๊ฒฐ๊ณผ๋ฅผ ํŽ˜์ด์ง€ ๋งค๊น€ํ•˜๊ณ  ๊ฒฐ๊ณผ์˜ ํ•˜์œ„ ์ง‘ํ•ฉ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด octocat/Spoon-Knife ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์—ด๋ ค ์žˆ๋Š” ์ด์Šˆ๊ฐ€ 1,600๊ฐœ๊ฐ€ ๋„˜๋Š” ๊ฒฝ์šฐ์—๋„ GET /repos/octocat/Spoon-Knife/issues ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ 30๊ฐœ์˜ ์ด์Šˆ๋งŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์„œ๋ฒ„ ๋ฐ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์‘๋‹ต์„ ๋” ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‘๋‹ต์˜ link ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—”๋“œํฌ์ธํŠธ๊ฐ€ per_page ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ ํŽ˜์ด์ง€์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฐ๊ณผ ์ˆ˜๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์„œ์—์„œ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ์‘๋‹ต์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ๊ฒฐ๊ณผ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋Š” ๋ฐฉ๋ฒ•, ๊ฐ ํŽ˜์ด์ง€์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฐ๊ณผ์˜ ์ˆ˜๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ• ๋ฐ ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ ์ค๋‹ˆ๋‹ค.

์‘๋‹ต์˜ ํŽ˜์ด์ง€๊ฐ€ ๋งค๊ฒจ์ง€๋ฉด ์‘๋‹ต ํ—ค๋”์— link ํ—ค๋”๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํŽ˜์ด์ง€ ๋งค๊น€์„ ์ง€์›ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ๋ชจ๋“  ๊ฒฐ๊ณผ๊ฐ€ ๋‹จ์ผ ํŽ˜์ด์ง€์— ๋งž๋Š” ๊ฒฝ์šฐ link ํ—ค๋”๋Š” ์ƒ๋žต๋ฉ๋‹ˆ๋‹ค.

link ํ—ค๋”์—๋Š” ๊ฒฐ๊ณผ์˜ ์ถ”๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” URL์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฒฐ๊ณผ์˜ ์ด์ „, ๋‹ค์Œ, ์ฒซ ๋ฒˆ์งธ ๋ฐ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŠน์ • ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ์‘๋‹ต ํ—ค๋”๋ฅผ ๋ณด๋ ค๋ฉด curl, GitHub CLI ๋˜๋Š” ์š”์ฒญ์„ ๋งŒ๋“œ๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ ์‘๋‹ต ํ—ค๋”๋ฅผ ๋ณด๋ ค๋ฉด ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•œ ์„ค๋ช…์„œ๋ฅผ ๋”ฐ๋ฅด์„ธ์š”. curl ๋˜๋Š” GitHub CLI๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์‘๋‹ต ํ—ค๋”๋ฅผ ๋ณด๋ ค๋ฉด ์š”์ฒญ์ด ์žˆ๋Š” --include ํ”Œ๋ž˜๊ทธ๋ฅผ ์ „๋‹ฌํ•˜์„ธ์š”. ์˜ˆ์‹œ:

curl --include --request GET \
--url "https://api.github.com/repos/octocat/Spoon-Knife/issues" \
--header "Accept: application/vnd.github+json"

์‘๋‹ต์ด ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๊ฒฝ์šฐ link ํ—ค๋”๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

link: <https://api.github.com/repositories/1300192/issues?page=2>; rel="prev", <https://api.github.com/repositories/1300192/issues?page=4>; rel="next", <https://api.github.com/repositories/1300192/issues?page=515>; rel="last", <https://api.github.com/repositories/1300192/issues?page=1>; rel="first"

link ํ—ค๋”๋Š” ๊ฒฐ๊ณผ์˜ ์ด์ „, ๋‹ค์Œ, ์ฒซ ๋ฒˆ์งธ ๋ฐ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ URL์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • ์ด์ „ ํŽ˜์ด์ง€์˜ URL ๋’ค์— rel="prev"์ด(๊ฐ€) ๋‚˜์˜ต๋‹ˆ๋‹ค.
  • ๋‹ค์Œ ํŽ˜์ด์ง€์˜ URL ๋’ค์— rel="next"๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์˜ URL ๋’ค์— rel="last"๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€์˜ URL ๋’ค์—๋Š” rel="first"๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฝ์šฐ์— ๋”ฐ๋ผ ์ด๋Ÿฌํ•œ ๋งํฌ์˜ ํ•˜์œ„ ์ง‘ํ•ฉ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ฒฐ๊ณผ์˜ ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€์— ์žˆ๋Š” ๊ฒฝ์šฐ ์ด์ „ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ๋งํฌ๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š์œผ๋ฉฐ, ๋งํฌ๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ๋งํฌ๋Š” ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

link ํ—ค๋”์˜ URL์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฐ๊ณผ์˜ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ด์ „ ์˜ˆ์ œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒฐ๊ณผ์˜ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋ ค๋ฉด ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.

curl --include --request GET \
--url "https://api.github.com/repositories/1300192/issues?page=515" \
--header "Accept: application/vnd.github+json"

link ํ—ค๋”์˜ URL์€ ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•  ๊ฒฐ๊ณผ์˜ ํŽ˜์ด์ง€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. link URL์˜ ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜๋Š” ์—”๋“œํฌ์ธํŠธ ๊ฐ„์— ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์ง€๋งŒ ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๊ฐ ์—”๋“œํฌ์ธํŠธ๋Š” page, before/after ๋˜๋Š” since ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (์ผ๋ถ€ ์—”๋“œํฌ์ธํŠธ๋Š” ํŽ˜์ด์ง€ ๋งค๊น€ ์ด์™ธ์˜ ๋‹ค๋ฅธ ํ•ญ๋ชฉ์— since ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.) ๋ชจ๋“  ๊ฒฝ์šฐ์— link ํ—ค๋”์˜ URL์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฐ๊ณผ์˜ ์ถ”๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ REST API ์‹œ์ž‘์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ ๋ณ€๊ฒฝ

์—”๋“œํฌ์ธํŠธ๊ฐ€ per_page ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ ํŽ˜์ด์ง€์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฐ๊ณผ ์ˆ˜๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ REST API ์‹œ์ž‘์„(๋ฅผ) ์ฐธ์กฐํ•˜์„ธ์š”.

์˜ˆ๋ฅผ ๋“ค์–ด ์ด ์š”์ฒญ์€ per_page ์ฟผ๋ฆฌ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€๋‹น ๋‘ ๊ฐœ์˜ ํ•ญ๋ชฉ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

curl --include --request GET \
--url "https://api.github.com/repos/octocat/Spoon-Knife/issues?per_page=2" \
--header "Accept: application/vnd.github+json"

per_page ๋งค๊ฐœ ๋ณ€์ˆ˜๋Š” link ํ—ค๋”์— ์ž๋™์œผ๋กœ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ์‹œ:

link: <https://api.github.com/repositories/1300192/issues?per_page=2&page=2>; rel="next", <https://api.github.com/repositories/1300192/issues?per_page=2&page=7715>; rel="last"

ํŽ˜์ด์ง€ ๋งค๊น€์„ ์‚ฌ์šฉํ•œ ์Šคํฌ๋ฆฝํŒ…

link ํ—ค๋”์—์„œ URL์„ ์ˆ˜๋™์œผ๋กœ ๋ณต์‚ฌํ•˜๋Š” ๋Œ€์‹  ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์˜ˆ์ œ๋Š” JavaScript ๋ฐ GitHub์˜ Octokit.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Octokit.js์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ REST API ์‹œ์ž‘ ๋ฐ Octokit.js README๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

Octokit.js ํŽ˜์ด์ง€ ๋งค๊น€ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์ œ

Octokit.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด octokit.paginate()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. octokit.paginate()๋Š” ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๊ฒฐ๊ณผ์˜ ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜จ ๋‹ค์Œ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋‹จ์ผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๊ฐ€ ๋งค๊ฒจ์ง„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹ , ๋ช‡ ๊ฐœ์˜ ์—”๋“œํฌ์ธํŠธ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๊ฒฐ๊ณผ๋ฅผ ๊ฐœ์ฒด์˜ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์›์‹œ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐœ์ฒด์ธ ๊ฒฝ์šฐ์—๋„ octokit.paginate()๋Š” ํ•ญ์ƒ ํ•ญ๋ชฉ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์ด ์Šคํฌ๋ฆฝํŠธ๋Š” octocat/Spoon-Knife ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ ๋ชจ๋“  ์ด์Šˆ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ์— 100๊ฐœ์˜ ์ด์Šˆ๋ฅผ ์š”์ฒญํ•˜์ง€๋งŒ ํ•จ์ˆ˜๋Š” ๋ฐ์ดํ„ฐ์˜ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ });

const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "octocat",
  repo: "Spoon-Knife",
  per_page: 100,
  headers: {
    "X-GitHub-Api-Version": "2022-11-28",
  },
});

console.log(data)

octokit.paginate()์— ์„ ํƒ์  ๋งต ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์— ํŽ˜์ด์ง€ ๋งค๊น€์„ ์ข…๋ฃŒํ•˜๊ฑฐ๋‚˜ ์‘๋‹ต์˜ ํ•˜์œ„ ์ง‘ํ•ฉ๋งŒ ์œ ์ง€ํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋Š” ๋Œ€์‹  ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ๋ฐ octokit.paginate.iterator()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์ •๋ณด๋Š” Octokit.js ์„ค๋ช…์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

ํŽ˜์ด์ง€ ๋งค๊น€ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“œ๋Š” ์˜ˆ์ œ

ํŽ˜์ด์ง€ ๋งค๊น€ ๋ฉ”์„œ๋“œ๊ฐ€ ์—†๋Š” ๋‹ค๋ฅธ ์–ธ์–ด ๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ๊ณ ์œ ์˜ ํŽ˜์ด์ง€ ๋งค๊น€ ๋ฉ”์„œ๋“œ๋ฅผ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” ์—ฌ์ „ํžˆ Octokit.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ octokit.paginate()์— ์˜์กดํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

getPaginatedData ํ•จ์ˆ˜๋Š” octokit.request()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—”๋“œํฌ์ธํŠธ์— ์š”์ฒญ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์‘๋‹ต์˜ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ˜ํ™˜๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ๋˜๋Š” ๋ฐ˜ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐฐ์—ด ๋Œ€์‹  ๊ฐœ์ฒด์ธ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ parseData์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ฒ˜๋ฆฌ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ง€๊ธˆ๊นŒ์ง€ ์ˆ˜์ง‘๋œ ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ๋งค๊ธด ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ ๋ชฉ๋ก์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์‘๋‹ต์— link ํ—ค๋”๊ฐ€ ํฌํ•จ๋˜๊ณ  linkํ—ค๋”์— ๋‹ค์Œ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ๋งํฌ๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ, ํ•จ์ˆ˜๋Š” RegEx ํŒจํ„ด(nextPattern)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ ํŽ˜์ด์ง€์˜ URL์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ํ•จ์ˆ˜๋Š” ์ด ์ƒˆ URL์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „ ๋‹จ๊ณ„๋ฅผ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค. link ํ—ค๋”์— ๋‹ค์Œ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ๋งํฌ๊ฐ€ ๋” ์ด์ƒ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด ๋ชจ๋“  ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ });

async function getPaginatedData(url) {
  const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
  let pagesRemaining = true;
  let data = [];

  while (pagesRemaining) {
    const response = await octokit.request(`GET ${url}`, {
      per_page: 100,
      headers: {
        "X-GitHub-Api-Version":
          "2022-11-28",
      },
    });

    const parsedData = parseData(response.data)
    data = [...data, ...parsedData];

    const linkHeader = response.headers.link;

    pagesRemaining = linkHeader && linkHeader.includes(`rel=\"next\"`);

    if (pagesRemaining) {
      url = linkHeader.match(nextPattern)[0];
    }
  }

  return data;
}

function parseData(data) {
  // If the data is an array, return that
    if (Array.isArray(data)) {
      return data
    }

  // Some endpoints respond with 204 No Content instead of empty array
  //   when there is no data. In that case, return an empty array.
  if (!data) {
    return []
  }

  // Otherwise, the array of items that we want is in an object
  // Delete keys that don't include the array of items
  delete data.incomplete_results;
  delete data.repository_selection;
  delete data.total_count;
  // Pull out the array of items
  const namespaceKey = Object.keys(data)[0];
  data = data[namespaceKey];

  return data;
}

const data = await getPaginatedData("/repos/octocat/Spoon-Knife/issues");

console.log(data);