A TypeScript-first, ergonomic wrapper around the GitHub REST API. Calls api.github.com directly using native fetch — no Octokit, no third-party HTTP library.
- Clean TypeScript domain types — camelCase interfaces instead of raw snake_case API responses
- Typed errors —
GithubApiError,InvalidTokenError,MissingConfigErrorinstead of guessing at response shapes - Service-based API —
github.pullRequests.list()instead of hand-building fetch calls - Built-in rate limiting, retries, and ETag caching — no extra config required
- PAT or GitHub App auth, swappable behind one interface
- Near-zero dependencies —
josefor GitHub App JWT signing is the only runtime dependency
npm install @lujax/github-sdkRequires Node.js 18 or later (uses native fetch).
import { GithubClient } from "@lujax/github-sdk";
const github = new GithubClient({
token: process.env.GITHUB_TOKEN,
owner: "lujax-dev",
repo: "github-sdk",
});
const pullRequests = await github.pullRequests.list();
const repo = await github.repositories.get();owner, repo, and org are all optional on GithubClientConfig — each service asserts the config it actually needs at construction time, throwing MissingConfigError immediately if something's missing rather than failing later on a request. A GithubClient instance is scoped to one owner/repo pair; to work with multiple repositories, create multiple clients.
The simplest option — pass a token string directly:
const github = new GithubClient({
token: process.env.GITHUB_TOKEN,
owner: "lujax-dev",
repo: "github-sdk",
});For production use as a GitHub App, pass a GitHubAppAuth instance via auth instead of token. It signs a JWT from your App's private key, exchanges it for an installation access token, and refreshes it automatically as it nears expiry — no extra code required:
import { GithubClient, GitHubAppAuth } from "@lujax/github-sdk";
const github = new GithubClient({
auth: new GitHubAppAuth({
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
installationId: process.env.GITHUB_APP_INSTALLATION_ID,
}),
owner: "lujax-dev",
repo: "github-sdk",
});token and auth are both optional individually, but at least one is required — GithubClient throws InvalidTokenError otherwise. If both are set, auth takes precedence.
Both strategies implement the same GitHubAuth interface ({ getToken(): Promise<string> }), so you can switch between them — e.g. PAT locally, GitHub App in production — without changing any other code. PatAuth (also exported) wraps a token identically to passing token directly; use auth: new PatAuth(token) if you'd rather keep every strategy behind the same auth field.
Each service is scoped to the owner/repo/org set on the client and exposed as a property:
| Service | Access | Requires | What it covers |
|---|---|---|---|
| Pull Requests | github.pullRequests |
owner, repo |
list/get/create/update/merge, commits, files, reviews, cycleTime(), isMerged() |
| Repositories | github.repositories |
owner, repo (some methods need org instead) |
get/update/delete, topics, tags, languages, contributors, security settings, org-scoped listing |
| Issues | github.issues |
owner, repo |
list/get/create/update, comments |
| Releases | github.releases |
owner, repo |
list/get/getLatest/getByTag/create/update/delete |
| Workflows | github.workflows |
owner, repo |
list/get workflows, list/get/cancel/re-run runs, trigger dispatch |
| Organizations | github.organizations |
org |
get/update, listings, membership management |
| Users | github.users |
— | authenticated user, lookup by username/ID, list |
There's no standalone commits service — list commits on a PR via github.pullRequests.listCommits(pullNumber).
Every public method has JSDoc with a runnable example — full signatures are in the source, e.g. PullRequestService.ts, RepositoryService.ts. For complete standalone scripts, see /examples.
All non-2xx API responses throw typed errors instead of returning a raw error shape:
import {
GithubApiError,
InvalidTokenError,
MissingConfigError,
} from "@lujax/github-sdk";
try {
await github.pullRequests.get(9999);
} catch (err) {
if (err instanceof GithubApiError) {
console.error(err.status, err.message, err.documentationUrl);
}
}GithubSdkError— base class for every error the SDK throwsGithubApiError— thrown on any non-ok GitHub API response; carries.status,.documentationUrl, and.detailsInvalidTokenError— thrown when aGithubClientis constructed withouttokenorauthMissingConfigError— thrown when a service is constructed without theowner/repo/orgit needs
Built into every request, with no configuration needed:
- Rate limiting — proactively waits out the reset window when
x-ratelimit-remaininghits 0, instead of letting requests fail with a 403 - Retries — 3 attempts with 1s/2s/4s backoff on
5xxand429responses - ETag caching — repeat
GETs sendIf-None-Match; a304returns the cached body for free and doesn't count against your rate limit
See CONTRIBUTING.md for setup, the module pattern, and the PR process. This project follows the Contributor Covenant Code of Conduct.
MIT