<!-- PkgPulse AI-readable guide source -->
<!-- Canonical: https://www.pkgpulse.com/guides/octokit-rest-vs-octokit-graphql-vs-github-api-github-2026 -->
<!-- Raw Markdown: https://www.pkgpulse.com/guides/octokit-rest-vs-octokit-graphql-vs-github-api-github-2026/raw.md -->
<!-- Source path: content/guides/octokit-rest-vs-octokit-graphql-vs-github-api-github-2026.mdx -->

---
og_image: "/images/guides/octokit-rest-vs-octokit-graphql-vs-github-api-github-2026.webp"
title: "@octokit/rest vs @octokit/graphql vs github-api 2026"
description: "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."
date: "2026-03-09"
author: "PkgPulse Team"
tags: ["nodejs", "typescript", "api", "developer-tools"]
---

## 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

```typescript
// 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](https://octokit.github.io/rest.js/) — GitHub REST API client:

### Setup

```typescript
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

```typescript
// 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

```typescript
// 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

```typescript
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

```typescript
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](https://github.com/octokit/graphql.js) — GitHub GraphQL API:

### Why use GraphQL instead of REST?

```graphql
# 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

```typescript
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

```typescript
// 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)

```typescript
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/graphql | github-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:**
```typescript
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:

```typescript
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 →](https://www.pkgpulse.com)*

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](/guides/graphql-yoga-vs-apollo-server-vs-mercurius-graphql-2026) and [DataLoader vs p-batch vs graphql-batch](/guides/dataloader-vs-p-batch-vs-graphql-batch-batching-2026), [better-sqlite3 vs libsql vs sql.js](/guides/better-sqlite3-vs-libsql-vs-sql-js-sqlite-nodejs-2026).*
