Skip to main content

Guide

@octokit/rest vs @octokit/graphql vs github-api 2026

Compare @octokit/rest, @octokit/graphql, and github-api for GitHub API in Node.js. REST vs GraphQL, rate limiting, auth, and which GitHub client to use in 2026.

·PkgPulse Team·
0

TL;DR

@octokit/rest is GitHub's official REST API client — maintained by GitHub, TypeScript-first, excellent pagination and authentication support. @octokit/graphql is GitHub's official GraphQL client — when you need to fetch deeply nested data (repos + issues + comments in one query) with precise field selection. github-api is a popular community alternative — simpler API, slightly less feature-complete than Octokit. In 2026: use the official Octokit packages for anything serious. @octokit/rest for most tasks; @octokit/graphql when you need the GitHub GraphQL API's power; avoid github-api for new projects.

Key Takeaways

  • @octokit/rest: ~8M weekly downloads — official GitHub REST client, typed endpoints, auto-pagination
  • @octokit/graphql: ~4M weekly downloads — official GitHub GraphQL client, precise data fetching
  • github-api: ~200K weekly downloads — community alternative, less maintained
  • GitHub REST API: simpler, well-documented, rate limited at 5000 req/hr (authenticated)
  • GitHub GraphQL API: one query fetches nested data, counts against point budget (not just request count)
  • Both APIs require authentication — use a GitHub Personal Access Token or GitHub App

Authentication

// GitHub Personal Access Token (PAT) — simplest:
const token = process.env.GITHUB_TOKEN

// Scopes needed:
//   repo     — access private repos
//   read:org — read org data
//   read:user — read user data
//   public_repo — access public repos only

// Create at: GitHub → Settings → Developer settings → Personal access tokens
// Fine-grained PATs (2023+) allow per-repo, per-permission tokens

@octokit/rest

@octokit/rest — GitHub REST API client:

Setup

import { Octokit } from "@octokit/rest"

const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN,
})

// Or with more options:
const octokit = new Octokit({
  auth: process.env.GITHUB_TOKEN,
  userAgent: "PkgPulse/1.0",
  baseUrl: "https://api.github.com",  // Enterprise: "https://github.mycompany.com/api/v3"
  log: {
    debug: console.debug,
    info: console.info,
    warn: console.warn,
    error: console.error,
  },
})

Common REST operations

// Get a repository:
const { data: repo } = await octokit.repos.get({
  owner: "facebook",
  repo: "react",
})
console.log(repo.stargazers_count)  // ~230000
console.log(repo.language)          // "JavaScript"
console.log(repo.topics)            // ["react", "javascript", "ui"]

// List repository issues:
const { data: issues } = await octokit.issues.listForRepo({
  owner: "facebook",
  repo: "react",
  state: "open",
  labels: "bug",
  per_page: 30,
  page: 1,
})

// Create an issue:
const { data: newIssue } = await octokit.issues.create({
  owner: "myorg",
  repo: "myrepo",
  title: "Bug: health score calculation error",
  body: "When the download count exceeds...",
  labels: ["bug", "high-priority"],
  assignees: ["royce"],
})
console.log(`Created issue #${newIssue.number}`)

// Get file content:
const { data: file } = await octokit.repos.getContent({
  owner: "facebook",
  repo: "react",
  path: "package.json",
})

// Decode base64 content:
if (file.type === "file") {
  const content = Buffer.from(file.content, "base64").toString("utf8")
  const pkg = JSON.parse(content)
  console.log(pkg.version)
}

Auto-pagination

// Manual pagination is tedious — use paginate():
const allIssues = await octokit.paginate(octokit.issues.listForRepo, {
  owner: "facebook",
  repo: "react",
  state: "all",
  per_page: 100,
})
// Fetches all pages automatically!
console.log(`Total issues: ${allIssues.length}`)

// Iterator for memory efficiency (don't load all pages into memory):
const iterator = octokit.paginate.iterator(octokit.repos.listForOrg, {
  org: "vercel",
  per_page: 100,
})

for await (const { data: repos } of iterator) {
  for (const repo of repos) {
    console.log(repo.full_name)
  }
}

GitHub Apps authentication

import { createAppAuth } from "@octokit/auth-app"
import { Octokit } from "@octokit/rest"

// GitHub App authentication (for higher rate limits + installation-scoped tokens):
const octokit = new Octokit({
  authStrategy: createAppAuth,
  auth: {
    appId: process.env.GITHUB_APP_ID!,
    privateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
    installationId: parseInt(process.env.GITHUB_INSTALLATION_ID!),
  },
})

// Rate limits: GitHub Apps get 5000+ req/hr per installation
// vs 5000 req/hr for PATs

TypeScript types

import { Octokit } from "@octokit/rest"
import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods"

// Full TypeScript types for all endpoints:
type RepoResponse = RestEndpointMethodTypes["repos"]["get"]["response"]
type IssueData = RestEndpointMethodTypes["issues"]["create"]["parameters"]

// The Octokit class is fully typed — IDE autocomplete for all 400+ endpoints
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })

const { data } = await octokit.repos.get({ owner: "facebook", repo: "react" })
//              ^^ Fully typed: RestEndpointMethodTypes["repos"]["get"]["response"]["data"]

@octokit/graphql

@octokit/graphql — GitHub GraphQL API:

Why use GraphQL instead of REST?

# REST: 3 separate requests for this data:
# GET /repos/:owner/:repo
# GET /repos/:owner/:repo/issues?state=open
# GET /repos/:owner/:repo/releases/latest

# GraphQL: ONE request for all of it:
query GetRepoOverview($owner: String!, $name: String!) {
  repository(owner: $owner, name: $name) {
    stargazerCount
    forkCount
    openIssues: issues(states: OPEN) {
      totalCount
    }
    latestRelease {
      tagName
      publishedAt
    }
    languages(first: 5) {
      nodes { name color }
    }
  }
}

Setup and queries

import { graphql } from "@octokit/graphql"

const graphqlWithAuth = graphql.defaults({
  headers: {
    authorization: `token ${process.env.GITHUB_TOKEN}`,
  },
})

// Query with TypeScript types:
interface RepoOverview {
  repository: {
    stargazerCount: number
    forkCount: number
    openIssues: { totalCount: number }
    latestRelease: { tagName: string; publishedAt: string } | null
    languages: {
      nodes: Array<{ name: string; color: string }>
    }
  }
}

const result = await graphqlWithAuth<RepoOverview>(
  `
  query GetRepoOverview($owner: String!, $name: String!) {
    repository(owner: $owner, name: $name) {
      stargazerCount
      forkCount
      openIssues: issues(states: OPEN) { totalCount }
      latestRelease { tagName publishedAt }
      languages(first: 5) { nodes { name color } }
    }
  }
`,
  {
    owner: "facebook",
    name: "react",
  }
)

console.log(result.repository.stargazerCount)
console.log(result.repository.openIssues.totalCount)

Pagination with GraphQL cursors

// GitHub GraphQL uses cursor-based pagination:
const getAllIssues = async (owner: string, repo: string) => {
  const issues: Issue[] = []
  let cursor: string | null = null
  let hasNextPage = true

  while (hasNextPage) {
    const result = await graphqlWithAuth<{ repository: { issues: IssueConnection } }>(
      `
      query GetIssues($owner: String!, $repo: String!, $cursor: String) {
        repository(owner: $owner, name: $repo) {
          issues(first: 100, after: $cursor, states: OPEN) {
            nodes {
              number
              title
              createdAt
              labels(first: 10) {
                nodes { name }
              }
            }
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
      }
    `,
      { owner, repo, cursor }
    )

    const { nodes, pageInfo } = result.repository.issues
    issues.push(...nodes)
    hasNextPage = pageInfo.hasNextPage
    cursor = pageInfo.endCursor
  }

  return issues
}

github-api (community package)

import GitHub from "github-api"

const gh = new GitHub({ token: process.env.GITHUB_TOKEN })

// Get a repository:
const repo = gh.getRepo("facebook", "react")
const { data: repoData } = await repo.getDetails()

// List issues:
const { data: issues } = await repo.listIssues({ state: "open" })

// Get user:
const user = gh.getUser("facebook")
const { data: profile } = await user.getProfile()

Why skip github-api for new projects

github-api cons:
  - Not maintained by GitHub — community project
  - Less frequently updated than @octokit packages
  - Doesn't cover as many endpoints as @octokit/rest
  - No auto-pagination built-in
  - Octokit is the de-facto standard (used by GitHub Actions, Probot, etc.)

If you see it in an existing project, it works — no urgent need to migrate.
For new projects: use @octokit/rest.

Feature Comparison

Feature@octokit/rest@octokit/graphqlgithub-api
Official GitHub SDK
Auto-pagination❌ (manual cursors)
TypeScript types✅ All endpoints⚠️ Partial
GitHub Apps auth
REST API
GraphQL API
Rate limit info⚠️
Weekly downloads~8M~4M~200K
Enterprise Server⚠️

When to Use Each

Choose @octokit/rest if:

  • Accessing standard GitHub REST API endpoints (repos, issues, PRs, users, orgs)
  • Need auto-pagination for large result sets
  • Building GitHub Apps or integrations
  • Default choice for GitHub API work in 2026

Choose @octokit/graphql if:

  • Need to fetch deeply nested data in a single request
  • Querying GitHub's advanced GraphQL features (project boards, discussions, sponsorship)
  • Want to reduce the number of API calls by combining multiple data fetches

Use both together:

import { Octokit } from "octokit"
// The "octokit" package combines REST, GraphQL, and webhooks:

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })

// REST:
await octokit.rest.repos.get({ owner: "facebook", repo: "react" })

// GraphQL:
await octokit.graphql(`query { viewer { login } }`)

Rate Limiting, Caching, and Staying Within GitHub's Quotas

GitHub's REST API allows 5,000 requests per hour for authenticated Personal Access Token requests and 15,000 for GitHub Apps. GraphQL requests consume a point budget rather than a request count — a query that fetches 100 issues with their labels and assignees may cost 101 points, where a paginated REST approach for the same data would cost multiple requests each counting against the hourly request limit.

@octokit/rest exposes rate limit information through response headers. After any API call, response.headers["x-ratelimit-remaining"] tells you how many requests remain in the current window, and x-ratelimit-reset gives the Unix timestamp when the window resets. For scripts that process large numbers of repositories, polling octokit.rateLimit.get() periodically and inserting await delays when remaining drops below a threshold prevents hitting the limit mid-run.

The throttling and retry plugins from the octokit package extend the base Octokit client with automatic rate limit handling. @octokit/plugin-throttling watches the response headers and automatically queues requests when the rate limit is low, draining the queue as capacity recovers. @octokit/plugin-retry retries requests that return 403 (secondary rate limit) or 5xx errors with exponential backoff. These plugins transform client-side rate limit handling from manual polling logic into a declarative configuration:

import { Octokit } from "octokit"
import { throttling } from "@octokit/plugin-throttling"

const ThrottledOctokit = Octokit.plugin(throttling)
const octokit = new ThrottledOctokit({
  auth: process.env.GITHUB_TOKEN,
  throttle: {
    onRateLimit: (retryAfter, options) => {
      if (options.request.retryCount < 2) return true  // Retry up to 2 times
    },
    onSecondaryRateLimit: (retryAfter, options) => true,
  },
})

Building GitHub Apps vs Using Personal Access Tokens

For production integrations that process data across many repositories or organizations, GitHub Apps provide substantially better operational characteristics than PATs. A GitHub App authenticates as an installation — the specific repositories it has been granted access to — and generates short-lived installation tokens (valid for one hour) rather than using a long-lived credential. This means a leaked token expires within an hour and is scoped to only the repositories the App was installed on.

@octokit/rest supports GitHub App authentication through @octokit/auth-app. The App authenticates with a private key (an RSA PEM file generated in the App settings) to obtain installation tokens on demand. The createAppAuth strategy handles token refresh automatically — when an installation token expires, the next request transparently generates a new one. This token lifecycle management is complex to implement manually and is one of the clearest reasons to use the official Octokit packages over community alternatives.

GitHub Apps also have higher rate limits than PATs: 15,000 requests per hour per installation for REST, compared to 5,000 for PATs. For tools that process many repositories — security scanners, dependency update bots, CI integrations — this 3× headroom often determines whether the tool can complete its work within a reasonable time window without complex request throttling.

For @octokit/graphql, the authentication approach is the same — pass the installation token in the authorization header. Because installation tokens are short-lived, the pattern of re-instantiating graphql.defaults({ headers: { authorization: \token ${token}` } })` with a fresh token before each batch of queries is cleaner than sharing a single authenticated graphql instance across long-running processes.


Caching Strategies for GitHub API Responses

GitHub's API rate limits encourage caching, but naive caching causes stale data. The REST API includes ETag headers on responses — if you cache the response and send the ETag back in an If-None-Match header on the next request, GitHub returns a 304 Not Modified without consuming your rate limit quota if the resource hasn't changed. @octokit/rest does not implement ETag caching automatically, but the @octokit/plugin-rest-endpoint-methods response includes the raw headers, letting you build a caching layer on top. For high-frequency operations like checking whether a repository still exists or reading a file that rarely changes, ETag caching can reduce rate limit consumption by 80-90%.

Webhooks and Event-Driven GitHub Integrations

For applications that react to GitHub events rather than polling them — CI/CD systems, bot automations, repository analytics — the Octokit ecosystem extends beyond REST and GraphQL clients to include webhook handling. The @octokit/webhooks package provides typed event handling for all 200+ GitHub webhook event types, with TypeScript discriminated unions that narrow the event payload based on the event name and action. An event handler for pull_request.opened gets a fully typed PullRequestOpenedEvent payload with all fields from the GitHub API docs, while an event handler for push gets a PushEvent with the commits array and branch ref.

Webhook signature verification is a security requirement: every GitHub webhook delivery includes an X-Hub-Signature-256 header (HMAC-SHA256 of the payload using your webhook secret). Sending a webhook to a public endpoint without verifying the signature allows attackers to forge events and trigger your automation. @octokit/webhooks wraps the verification logic in webhooks.verifyAndReceive(event), making it the correct default rather than something developers must remember to implement manually. This is a meaningful improvement over the github-api community package, which has no webhook support at all.

The Probot framework, built on Octokit, provides a higher-level abstraction for GitHub Apps that respond to events. While not part of the core Octokit packages, Probot demonstrates the ecosystem's strength: a Probot app authenticates as a GitHub App (using @octokit/auth-app), receives webhook events (using @octokit/webhooks), and makes API calls (using @octokit/rest) — all with automatic authentication token refresh, retry logic, and rate limit handling. For teams building automation that goes beyond simple API calls — triage bots, code review tools, dependency update automation — the Probot/Octokit stack is the reference architecture in 2026.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on @octokit/rest v21.x, @octokit/graphql v8.x, and github-api v3.x.

Compare API client packages on PkgPulse →

In 2026, the choice is primarily between GitHub's official Octokit libraries and the community github package. For new projects, start with @octokit/rest — it is officially maintained by GitHub, has built-in TypeScript types, handles rate limiting and pagination, and integrates cleanly with GitHub Actions via GITHUB_TOKEN. Use @octokit/graphql when you need to query multiple resources in a single request or access data only available through the GraphQL API (like project boards and issue timelines). The github package is a legacy choice that works but lacks the ergonomics and active maintenance of the Octokit family.

See also: graphql-yoga vs apollo-server vs mercurius and DataLoader vs p-batch vs graphql-batch, better-sqlite3 vs libsql vs sql.js.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.