Skip to main content

REST API 및 JavaScript를 사용하여 스크립팅

Octokit.js SDK를 사용하여 REST API와 상호 작용하는 스크립트를 작성합니다.

Octokit.js 정보

JavaScript를 사용하여 GitHub의 REST API와 상호 작용하는 스크립트를 작성하려면 GitHub에서 Octokit.js SDK를 사용하는 것이 좋습니다. Octokit.js는 GitHub에 의해 유지됨니다. SDK는 모범 사례를 구현하고 JavaScript를 통해 REST API와 보다 쉽게 상호 작용할 수 있도록 합니다. Octokit.js는 모든 최신 브라우저, Node.js 및 Deno에서 작동합니다. Octokit.js에 대한 자세한 정보는 Octokit.js 추가 정보를 참조하세요.

필수 조건

이 가이드에서는 JavaScript 및 GitHub REST API에 익숙하다고 가정합니다. REST API에 대한 자세한 정보는 “REST API 시작”을(를) 참조하세요.

참고: Octokit.js 라이브러리를 사용하려면 octokit을(를) 설치하고 가져와야 합니다. 이 가이드에서는 ES6에 따라 import 문을 사용합니다. 다양한 설치 및 가져오기 메서드에 대한 자세한 정보는 Octokit.js 추가 정보 사용 섹션을 참조하세요.

인스턴스화 및 인증

경고: 인증 자격 증명을 암호처럼 처리합니다.

자격 증명을 안전하게 유지하기 위해 비밀로 저장하고 GitHub Actions를 통해 스크립트를 실행할 수 있습니다. 자세한 내용은 "GitHub Actions에서 비밀 사용"을(를) 참조하세요.

이(가) 불가능하며, 다른 CLI 서비스를 사용하여 자격 증명을 안전하게 저장하는 것이 좋습니다.

personal access token을(를) 사용하여 인증

개인용으로 GitHub REST API를 사용하려는 경우 personal access token를 만들 수 있습니다. personal access token을(를) 만드는 방법에 대한 자세한 정보는 "개인용 액세스 토큰 관리"을(를) 참조하세요.

먼저 octokit에서 Octokit를 가져옵니다. 그런 다음 Octokit 인스턴스를 만들 때 personal access token을(를) 패스합니다. 다음 예제에서는 YOUR-TOKEN을 personal access token에 대한 참조로 치환합니다. HOSTNAME을 GitHub Enterprise Server 인스턴스의 이름으로 치환합니다.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
  auth: 'YOUR-TOKEN',
});

GitHub App(으)로 인증

조직 또는 다른 사용자를 대신하여 API를 사용하려는 경우 GitHub에서 GitHub App을(를) 사용하는 것이 좋습니다. GitHub Apps에 엔드포인트를 사용할 수 있는 경우 해당 엔드포인트에 대한 REST 참조 설명서에 필요한 GitHub App 토큰 유형이 표시됩니다. 자세한 내용은 "GitHub 앱 등록" 및 "GitHub 앱을 사용한 인증 정보"을(를) 참조하세요.

octokit에서 Octokit를 가져오는 대신 App을 가져옵니다. 다음 예제에서는 APP_ID을(를) 앱 ID에 대한 참조로 바꿉니다. 앱의 프라이빗 키에 대한 참조로 PRIVATE_KEY를 대체합니다. INSTALLATION_ID를 대신하여 인증하려는 앱의 설치의 ID로 바꿉니다. 앱 ID를 찾고 앱의 설정 페이지 설정에서 프라이빗 키를 생성할 수 있습니다. 자세한 내용은 "GitHub 앱에 대한 프라이빗 키 관리"을(를) 참조하세요. GET /users/{username}/installation, GET /repos/{owner}/{repo}/installation, GET /orgs/{org}/installation 엔드포인트를 사용하여 설치 ID를 가져올 수 있습니다. 자세한 내용은 "GitHub Apps에 대한 REST API 엔드포인트" 항목을 참조하세요. HOSTNAME을(를) GitHub Enterprise Server 인스턴스의 이름으로 바꿉니다.

JavaScript
import { App } from "octokit";

const app = new App({
  appId: APP_ID,
  privateKey: PRIVATE_KEY,
  Octokit: Octokit.defaults({
    baseUrl: "http(s)://HOSTNAME/api/v3",
  }),
});

const octokit = await app.getInstallationOctokit(INSTALLATION_ID);

GitHub Actions으로 인증

GitHub Actions 워크플로에서 API를 사용하려면 GitHub에서 토큰을 만드는 대신 기본 제공 GITHUB_TOKEN으로 인증하는 것이 좋습니다. permissions 키를 사용하여 GITHUB_TOKEN에 대한 사용 권한을 부여할 수 있습니다. GITHUB_TOKEN에 대한 자세한 정보는 "자동 토큰 인증"을(를) 참조하세요.

워크플로가 워크플로 리포지토리 외부의 리소스에 액세스해야 하는 경우 GITHUB_TOKEN을 사용할 수 없습니다. 이 경우 자격 증명을 비밀로 저장하고 아래 예제의 GITHUB_TOKEN을 비밀의 이름으로 바꿉니다. 비밀에 대한 자세한 정보는 "GitHub Actions에서 비밀 사용"을(를) 참조하세요.

또한 run 키워드를 사용하여GitHub Actions 워크플로에서 JavaScript 스크립트를 실행할 수 있으며 GITHUB_TOKEN의 값을 환경 변수로 저장할 수 있습니다. 스크립트는 환경 변수에 process.env.VARIABLE_NAME로 액세스할 수 있습니다.

예를 들어 이 워크플로 단계에서 TOKEN이라는 환경 변수에 GITHUB_TOKEN을 저장합니다.

- name: Run script
  env:
    TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    node .github/actions-scripts/use-the-api.mjs

워크플로가 실행하는 스크립트는 process.env.TOKEN을 인증하는 데 사용합니다.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
  auth: process.env.TOKEN,
});

인증 없는 인스턴스화

트래픽률 제한이 낮고 일부 엔드포인트를 사용할 수 없어도 인증 없이 REST API를 사용할 수 있습니다. 인증하지 않고 Octokit 인스턴스 를 만들려면 auth 인수를 패스하지 마세요. 기준 URL을 http(s)://HOSTNAME/api/v3로 설정합니다. [hostname]을 GitHub Enterprise Server 인스턴스의 이름으로 바꿉니다.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
});

요청 수행

Octokit은 요청을 만드는 여러 가지 방법을 지원합니다. 엔드포인트에 대한 HTTP 동사와 경로를 알고 있는 경우 request 메서드를 사용하여 요청을 수행할 수 있습니다. IDE에서 자동 완성 및 입력을 이용하려는 경우 rest 메서드를 사용할 수 있습니다. 페이지를 매긴 엔드포인트의 경우 paginate 메서드를 사용하여 여러 데이터 페이지를 요청할 수 있습니다.

request 메서드를 사용하여 요청을 수행합니다.

request 메서드를 사용하여 요청을 만들려면 HTTP 메서드와 경로를 첫 번째 인수로 전달합니다. 개체의 본문, 쿼리, 경로 매개 변수를 두 번째 인수로 패스합니다. 예를 들어 /repos/{owner}/{repo}/issuesGET을 요청하고 owner, repo, per_page 매개변수를 전달하려면 다음을 수행하세요.

JavaScript
await octokit.request("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 2
});

request 메서드는 자동으로 Accept: application/vnd.github+json 머리글을 전달합니다. 추가 헤더 또는 다른 Accept 머리글을 전달하려면 두 번째 인수로 전달되는 개체에 headers 속성을 추가합니다. headers 속성의 값은 헤더 이름을 키로, 헤더 값을 값으로 가지고 있는 개체입니다. 예를 들어 값이 text/plaincontent-type 머리글과 값이 2022-11-28x-github-api-version 머리글을 보내려면 다음을 수행합니다.

JavaScript
await octokit.request("POST /markdown/raw", {
  text: "Hello **world**",
  headers: {
    "content-type": "text/plain",
    "x-github-api-version": "2022-11-28",
  },
});

rest 엔드포인트 메서드를 사용하여 요청

모든 REST API 엔드포인트에는 Octokit에 연결된 rest 엔드포인트 메서드가 있습니다. 이러한 메서드는 일반적으로 편의를 위해 IDE에서 자동 완성됩니다. 모든 매개 변수를 개체로 메서드에 전달할 수 있습니다.

JavaScript
await octokit.rest.issues.listForRepo({
  owner: "github",
  repo: "docs",
  per_page: 2
});

또한 TypeScript와 같은 형식화된 언어를 사용하는 경우 이러한 메서드와 함께 사용할 형식을 가져올 수 있습니다. 자세한 정보는 plugin-rest-endpoint-methods.js 추가 정보에서 TypeScript 섹션을 참조하세요.

페이지 매긴 요청 만들기

엔드포인트의 페이지가 매겨지고 둘 이상의 결과 페이지를 페치하려는 경우 paginate 메서드를 사용할 수 있습니다. paginate는 마지막 페이지에 도달할 때까지 결과의 다음 페이지를 가져온 다음 모든 결과를 단일 배열로 반환합니다. 페이지가 매겨진 결과를 배열로 반환하는 대신, 몇 개의 엔드포인트는 페이지를 매긴 결과를 개체의 배열로 반환합니다. 원시 결과가 개체인 경우에도 paginate는 항상 항목 배열을 반환합니다.

예를 들어 다음 예제에서는 github/docs 리포지토리에서 모든 이슈를 가져옵니다. 한 번에 100개의 이슈를 요청하지만 함수는 데이터의 마지막 페이지에 도달할 때까지 반환되지 않습니다.

JavaScript
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2022-11-28",
  },
});

paginate 메서드는 응답에서 원하는 데이터만 수집하는 데 사용할 수 있는 선택적 맵 함수를 수락합니다. 이렇게 하면 스크립트의 메모리 사용량이 줄어듭니다. map 함수는 마지막 페이지에 도달하기 전에 페이지 매김을 종료하기 위해 호출할 수 있는 두 번째 인수 done을 사용할 수 있습니다. 이렇게 하면 페이지의 하위 집합을 가져올 수 있습니다. 예를 들어 다음 예제에서는 제목에 "test"가 포함된 이슈가 반환될 때까지 결과를 계속 가져옵니다. 반환된 데이터 페이지의 경우 이슈 제목과 작성자만 저장됩니다.

JavaScript
const issueData = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2022-11-28",
  },
},
    (response, done) => response.data.map((issue) => {
    if (issue.title.includes("test")) {
      done()
    }
    return ({title: issue.title, author: issue.user.login})
  })
);

모든 결과를 한 번에 가져오는 대신 한 번에 단일 페이지를 반복하는 데 octokit.paginate.iterator()를 사용할 수 있습니다. 예를 들어 다음 예제에서는 결과의 한 페이지를 한 번에 가져오고 다음 페이지를 가져오기 전에 페이지에서 각 개체를 처리합니다. 제목에 "test"가 포함된 이슈에 도달하면 스크립트는 반복을 중지하고 처리된 각 개체의 이슈 제목 및 이슈 작성자를 반환합니다. 반복기는 페이지를 매긴 데이터를 가져오기 위한 가장 메모리 효율적인 메서드입니다.

JavaScript
const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2022-11-28",
  },
});

let issueData = []
let breakLoop = false
for await (const {data} of iterator) {
  if (breakLoop) break
  for (const issue of data) {
    if (issue.title.includes("test")) {
      breakLoop = true
      break
    } else {
      issueData = [...issueData, {title: issue.title, author: issue.user.login}];
    }
  }
}

rest 엔드포인트 메서드에서도 paginate 메서드를 사용할 수 있습니다. rest 엔드포인트 메서드를 첫 번째 인수로 전달합니다. 매개 변수를 두 번째 인수로 전달합니다.

JavaScript
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2022-11-28",
  },
});

페이지 매김에 대한 자세한 정보는 “REST API에서 페이지 매김 사용”을(를) 참조하세요.

오류 포착하기

모든 오류 포착하기

경우에 따라 GitHub REST API에서 오류를 반환합니다. 예를 들어 액세스 토큰이 만료되었거나 필수 매개 변수를 생략하면 오류가 발생합니다. Octokit.js는 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found422 Unprocessable Entity 이외의 오류가 발생하면 요청을 자동으로 다시 시도합니다. 재시도 후에도 API 오류가 발생하면 Octokit.js는 응답(response.status) 및 응답 헤더(response.headers)의 HTTP 상태 코드를 포함하는 오류를 throw합니다. 코드에서 이러한 오류를 처리해야 합니다. 예를 들어 try/catch 블록을 사용하여 오류를 포착할 수 있습니다.

JavaScript
let filesChanged = []

try {
  const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", {
    owner: "github",
    repo: "docs",
    pull_number: 22809,
    per_page: 100,
    headers: {
      "x-github-api-version": "2022-11-28",
    },
  });

  for await (const {data} of iterator) {
    filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)];
  }
} catch (error) {
  if (error.response) {
    console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
  }
  console.error(error)
}

의도한 오류 코드 처리하기

경우에 따라 GitHub은(는) 4xx 상태 코드를 사용하여 오류가 아닌 응답을 나타냅니다. 사용 중인 엔드포인트에서 이 작업을 수행하는 경우, 특정 오류에 대한 처리를 추가할 수 있습니다. 예를 들어 GET /user/starred/{owner}/{repo} 엔드포인트는 리포지토리에 별표를 하지 않은 경우 404를 반환합니다. 다음 예제에서는 404 응답을 사용하여 리포지토리가 별표 표시되지 않았음을 나타내고 다른 모든 오류 코드는 오류로 처리됩니다.

JavaScript
try {
  await octokit.request("GET /user/starred/{owner}/{repo}", {
    owner: "github",
    repo: "docs",
    headers: {
      "x-github-api-version": "2022-11-28",
    },
  });

  console.log(`The repository is starred by me`);

} catch (error) {
  if (error.status === 404) {
    console.log(`The repository is not starred by me`);
  } else {
    console.error(`An error occurred while checking if the repository is starred: ${error?.response?.data?.message}`);
  }
}

트래픽률 제한 오류 처리

트래픽률 제한 오류가 표시되는 경우 대기 후 요청을 다시 시도할 수 있습니다. 트래픽률이 제한되면 GitHub이(가) 403 Forbidden 오류로 응답하고 x-ratelimit-remaining 응답 헤더 값이 "0"이 됩니다. 응답 헤더에는 현재 트래픽률 제한 창이 재설정되는 시간을 UTC Epoch 초 단위로 알려주는 x-ratelimit-reset 헤더가 포함됩니다. x-ratelimit-reset에 지정된 시간 후에 요청을 다시 시도할 수 있습니다.

JavaScript
async function requestRetry(route, parameters) {
  try {
    const response = await octokit.request(route, parameters);
    return response
  } catch (error) {
    if (error.response && error.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') {
      const resetTimeEpochSeconds = error.response.headers['x-ratelimit-reset'];
      const currentTimeEpochSeconds = Math.floor(Date.now() / 1000);
      const secondsToWait = resetTimeEpochSeconds - currentTimeEpochSeconds;
      console.log(`You have exceeded your rate limit. Retrying in ${secondsToWait} seconds.`);
      setTimeout(requestRetry, secondsToWait * 1000, route, parameters);
    } else {
      console.error(error);
    }
  }
}

const response = await requestRetry("GET /repos/{owner}/{repo}/issues", {
    owner: "github",
    repo: "docs",
    per_page: 2
  })

응답 사용

요청이 성공하면 request 메서드는 개체로 확인되는 Promise를 반환합니다. 개체 속성은 data(엔드포인트에서 반환된 응답 본문), status(HTTP 응답 코드), url(요청의 URL) 및 headers(응답 머리글을 포함하는 개체)입니다. 달리 지정하지 않는 한 응답 본문은 JSON 형식입니다. 일부 엔드포인트는 응답 본문을 반환하지 않습니다. 이 경우 data 속성은 생략됩니다.

JavaScript
const response = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}", {
  owner: "github",
  repo: "docs",
  issue_number: 11901,
  headers: {
    "x-github-api-version": "2022-11-28",
  },
});

console.log(`The status of the response is: ${response.status}`)
console.log(`The request URL was: ${response.url}`)
console.log(`The x-ratelimit-remaining response header is: ${response.headers["x-ratelimit-remaining"]}`)
console.log(`The issue title is: ${response.data.title}`)

마찬가지로 paginate 메서드는 프라미스를 반환합니다. 요청이 성공하면 프라미스는 엔드포인트에서 반환된 데이터 배열로 확인됩니다. request 메서드와 달리 paginate 메서드는 상태 코드, URL 또는 머리글을 반환하지 않습니다.

JavaScript
const data = await octokit.paginate("GET /repos/{owner}/{repo}/issues", {
  owner: "github",
  repo: "docs",
  per_page: 100,
  headers: {
    "x-github-api-version": "2022-11-28",
  },
});

console.log(`${data.length} issues were returned`)
console.log(`The title of the first issue is: ${data[0].title}`)

예제 스크립트

다음은 Octokit.js를 사용하는 전체 예제 스크립트입니다. 스크립트는 Octokit를 가져오고 새 인스턴스 Octokit를 만듭니다. personal access token 대신 GitHub App을(를) 사용하여 인증하려는 경우 Octokit 대신 App을 가져오고 인스턴스화합니다. 자세한 정보는 “GitHub App에서 인증”을 참조하세요.

getChangedFiles 함수는 끌어오기 요청에 대해 변경된 모든 파일을 가져옵니다. commentIfDataFilesChanged 함수는 getChangedFiles 함수를 호출합니다. 끌어오기 요청이 변경된 파일이 파일 경로에 /data/가 포함된 경우 함수는 끌어오기 요청에 대해 설명을 달게 됩니다.

JavaScript
import { Octokit } from "octokit";

const octokit = new Octokit({ 
  baseUrl: "http(s)://HOSTNAME/api/v3",
  auth: 'YOUR-TOKEN',
});

async function getChangedFiles({owner, repo, pullNumber}) {
  let filesChanged = []

  try {
    const iterator = octokit.paginate.iterator("GET /repos/{owner}/{repo}/pulls/{pull_number}/files", {
      owner: owner,
      repo: repo,
      pull_number: pullNumber,
      per_page: 100,
      headers: {
        "x-github-api-version": "2022-11-28",
      },
    });

    for await (const {data} of iterator) {
      filesChanged = [...filesChanged, ...data.map(fileData => fileData.filename)];
    }
  } catch (error) {
    if (error.response) {
      console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
    }
    console.error(error)
  }

  return filesChanged
}

async function commentIfDataFilesChanged({owner, repo, pullNumber}) {
  const changedFiles = await getChangedFiles({owner, repo, pullNumber});

  const filePathRegex = new RegExp(/\/data\//, "i");
  if (!changedFiles.some(fileName => filePathRegex.test(fileName))) {
    return;
  }

  try {
    const {data: comment} = await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", {
      owner: owner,
      repo: repo,
      issue_number: pullNumber,
      body: `It looks like you changed a data file. These files are auto-generated. \n\nYou must revert any changes to data files before your pull request will be reviewed.`,
      headers: {
        "x-github-api-version": "2022-11-28",
      },
    });

    return comment.html_url;
  } catch (error) {
    if (error.response) {
      console.error(`Error! Status: ${error.response.status}. Message: ${error.response.data.message}`)
    }
    console.error(error)
  }
}

await commentIfDataFilesChanged({owner: "github", repo: "docs", pullNumber: 191});

다음 단계