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.
MIT