Skip to main content

@octokit/rest vs @octokit/graphql vs github-api: GitHub API Clients (2026)

·PkgPulse Team

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 } }`)

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 →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.