Zero-dep client for the Hacker News Firebase API.
Project description
hacker-news-client
One design contract. Six idiomatic libraries. Zero surprises.
A production-quality client suite for the official Hacker News Firebase API — JavaScript, TypeScript, Python, Ruby, Go, and Rust — sharing one wire contract, one mock server, one fixture set, and one cross-language verification harness.
Quick-start · Architecture · Design contract · Research · Contribute · Changelog · Security
Why this exists
- Idiomatic, not uniform. Every library feels native to its language — JS uses
fetch+AbortController, Rust usestokio+reqwest, Go usescontext.Context+ tagged interface, Python uses@dataclass+match— but they all implement the same conceptual API against the same wire protocol. - Zero runtime dependencies where the stdlib can do the job. Only Rust ships dependencies (
tokio,reqwest,serde,thiserror) because those are the de-facto ecosystem baseline. - Test-first, cross-language. A shared Node-based mock server and fixture set drive byte-identical behavior checks across every library.
Table of contents
- Feature matrix
- Quick-start
- Repository layout
- Development
- Coverage
- Documentation
- Roadmap
- Contributing
- Security
- Community
- License
Feature matrix
| Capability | JS | TS | Python | Go | Ruby | Rust |
|---|---|---|---|---|---|---|
| Single-item fetch | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Batch fetch with bounded concurrency, order-preserving | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Fail-fast on first batch error | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Discriminated / tagged item types | JSDoc | union | dataclass | interface | subclass | enum |
All six *_story_ids + hydrated *_stories(limit) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Recursive comment_tree with deleted-node pruning |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| User profile fetch | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
max_item, updates (typed record) |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| 10s total timeout, budget enforced end-to-end | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Injectable transport for tests / middleware | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| Doc comments on every public symbol | JSDoc | TSDoc | godoc | YARD | rustdoc | |
| Strict-mode linter | Biome | Biome | ruff | go vet+gofmt |
RuboCop | clippy+fmt |
Quick-start
JavaScript (Node 20+)
npm install hacker-news-client
import { HackerNewsClient } from 'hacker-news-client';
const client = new HackerNewsClient();
const story = await client.item(1);
console.log(story?.title);
const top = await client.topStories(10);
const tree = await client.commentTree(8863);
More: js/README.md
TypeScript (Node 22.6+, strict)
npm install @hacker-news/client-ts
import { HackerNewsClient, type Item } from '@hacker-news/client-ts';
const client = new HackerNewsClient();
const item = await client.item(1);
if (item?.type === 'story') {
console.log(item.title, item.score);
}
More: ts/README.md
Python (3.10+)
pip install hacker-news-client
from hacker_news_client import HackerNewsClient, Story
client = HackerNewsClient()
item = client.item(1)
match item:
case Story(title=t, by=b, score=s):
print(f"{t} — {b} ({s})")
More: python/README.md
Ruby (3.1+)
gem install hacker-news-client
require 'hacker/news/client'
client = Hacker::News::Client.new
item = client.item(1)
puts item.title if item.is_a?(Hacker::News::Story)
More: ruby/README.md
Go (1.22+)
go get github.com/hammadxcm/hacker-news-client/go
import (
"context"
"fmt"
hackernews "github.com/hammadxcm/hacker-news-client/go"
)
c := hackernews.New(hackernews.Options{})
item, err := c.Item(context.Background(), 1)
if err != nil { /* handle */ }
if s, ok := item.(hackernews.Story); ok {
fmt.Println(s.Title)
}
More: go/README.md
Rust (2021, tokio)
cargo add hacker-news-client
use hacker_news_client::{HackerNewsClient, Item, Options};
# #[tokio::main]
# async fn main() -> hacker_news_client::Result<()> {
let client = HackerNewsClient::new(Options::default())?;
if let Some(Item::Story(s)) = client.item(1).await? {
println!("{}", s.title.unwrap_or_default());
}
# Ok(()) }
More: rust/README.md
Repository layout
hacker-news-client/
├── README.md ← you are here
├── CONTRIBUTING.md ← how to contribute
├── CODE_OF_CONDUCT.md ← Contributor Covenant 2.1
├── SECURITY.md ← vulnerability disclosure
├── CHANGELOG.md ← versioned release notes
├── SUPPORT.md ← where to get help
├── LICENSE ← MIT
├── VERSION ← single source of truth, "0.1.0"
├── RESEARCH.md ← API reference + prior-art survey
├── DESIGN.md ← locked cross-language contract
├── docs/
│ └── ARCHITECTURE.md ← contributor-facing system overview
├── js/ → idiomatic ESM JavaScript client
├── ts/ → strict TypeScript client with tagged unions
├── python/ → stdlib + optional httpx async extra
├── ruby/ → stdlib Net::HTTP gem
├── go/ → stdlib net/http + context.Context
├── rust/ → tokio + reqwest + serde
├── test/ → shared mock server + JSON fixtures
│ ├── mock-server.js
│ └── fixtures/*.json
├── scripts/
│ ├── verify.sh ← cross-language test matrix
│ └── bump-version.sh
└── .github/ ← CI workflows, issue/PR templates
Development
Two-command onboarding:
npm install # installs husky + eslint + prettier + c8 at the root
npm test # runs scripts/verify.sh — all six language suites
Per-language iteration:
npm run lint # Biome, ruff, rubocop, go vet+gofmt, clippy, cargo fmt
npm run coverage # generates coverage reports per language
node --test test/*.test.js # mock server only
cd js && node --test test/*.test.js # js unit + integration
cd python && python3 -m unittest discover # python
cd ruby && rake test # ruby
cd go && go test ./... # go
cd rust && cargo test # rust
A Husky pre-commit hook runs lint-staged on touched files plus a mock-server smoke test. A pre-push hook runs the full cross-language verification harness.
Coverage
Measured per language in CI and locally via npm run coverage:
| Language | Line | Branch | Tool |
|---|---|---|---|
| JavaScript | 100% | 100% | c8 |
| TypeScript | 100% | 100% | c8 |
| Python | 100% | — | coverage.py |
| Go | 98.3% | — | go test -cover |
| Ruby | 100% | 96.96% | SimpleCov |
| Rust | 94.4% | — | cargo-llvm-cov |
The sub-100% cells are language-tooling quirks: Go's coverage tracer doesn't track inlined no-op tag methods, and Rust has a few concurrency-race branches that aren't deterministically reachable from tests.
Quality and security tooling
Every PR runs through an extensive free-for-OSS tooling stack. Every tool below is free for open-source projects and requires no paid tier or secret.
| Tool | What it catches |
|---|---|
| Biome | JavaScript + TypeScript lint + format + import sorting (single Rust-based tool; 10–20× faster than ESLint+Prettier) |
| ruff | Python style + correctness (replaces flake8 / isort / pylint) |
| mypy --strict | Python static types |
| RuboCop | Ruby style + correctness |
| go vet + gofmt | Go style + correctness (stdlib) |
| clippy + rustfmt | Rust style + correctness |
| markdownlint-cli2 | Documentation style |
| actionlint | GitHub Actions workflow correctness |
| shellcheck | Shell script bugs + portability |
| editorconfig-checker | Consistent line endings, indentation |
| CodeQL | Semantic security analysis (JS/TS/Python/Ruby/Go) |
| OSSF Scorecard | Open-source best-practices scoring |
| Dependabot | Weekly dependency updates across all ecosystems |
| dependency-review-action | Per-PR diff of new dependencies + CVEs |
| gitleaks | Secret scanning across full git history |
| npm audit | Node.js CVE scanning |
| pip-audit | Python CVE scanning (PyPA) |
| bundler-audit | Ruby CVE scanning (RubySec) |
| govulncheck | Go CVE scanning (official) |
| cargo-audit | Rust CVE scanning (RustSec) |
| Codecov | Per-language coverage upload + PR comments |
| Husky + lint-staged | Local pre-commit + pre-push gates |
All of these run automatically on every PR via the workflows under .github/workflows/.
Documentation
| Document | Audience | What it covers |
|---|---|---|
README.md |
Everyone | Overview, quick-start, matrix |
docs/ARCHITECTURE.md |
Contributors | How the libraries are organized; how to add a method |
DESIGN.md |
Implementers | Locked cross-language contract, type model, error mapping |
RESEARCH.md |
Reviewers | Evidence-backed HN API reference, prior-art survey |
CHANGELOG.md |
Users | Versioned release notes (Keep a Changelog) |
CONTRIBUTING.md |
Contributors | Dev setup, branching, commit convention, PR workflow |
SECURITY.md |
Security researchers | Private vulnerability disclosure |
Roadmap
v1 is intentionally minimal. The following are reserved extension points (see DESIGN.md §10):
client.search.*— Algolia HN Search API wrapper (full-text,/items/:idsingle-call tree).client.updates.stream(...)— Firebase REST SSE for live/updatesand/maxitem.client.updates.poll(...)— polling helper that matches the future stream API shape.- Retries / rate-limiting / caching middleware — plugs into the already-exposed transport abstraction.
- Publishing to registries — automated releases via tagged commits.
Want to contribute one of these? See CONTRIBUTING.md.
Contributing
Contributions are welcome and deeply appreciated. Start with CONTRIBUTING.md — it covers dev setup, the monorepo workflow, the commit convention, and what to do when adding a new method (hint: it's six implementations at once).
By participating in this project you agree to abide by our Code of Conduct.
Security
If you've found a vulnerability, please do not open a public issue. Report privately via GitHub Security Advisories. Full policy: SECURITY.md.
Community
- Discussions — GitHub Discussions for questions and ideas.
- Issues — GitHub Issues for bugs and feature requests.
- Code of Conduct — Contributor Covenant 2.1.
- Support — see SUPPORT.md for the full guide to where-to-ask.
License
MIT © hacker-news-client contributors — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hn_api_client-0.1.0.tar.gz.
File metadata
- Download URL: hn_api_client-0.1.0.tar.gz
- Upload date:
- Size: 17.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07fe1dfebbf2530461fb5223120f4d707836e759d6970a7f001e746f69d5b0c1
|
|
| MD5 |
596a1a9dae62fd00d00352ed80f314e8
|
|
| BLAKE2b-256 |
543fad86822370292870e0a6dea9d88909edd5783a9b940463634adaab58d8f7
|
File details
Details for the file hn_api_client-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hn_api_client-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc78e53543444e65e31d58bb57359b5a270b27742ef31fc0f83eebb32936f44a
|
|
| MD5 |
c52cafa0d155568be68bedcdad474128
|
|
| BLAKE2b-256 |
8ea479fc25ca551739c7cd7aff09b3ead3d9c6ba154763d9faffbdf1bf8ced81
|