+ Integration Guide
· Updated August 28, 2026
Reddit Scraping API with Bun: Complete Guide
How to scrape Reddit using Bun and the FetchLayer API. Fast, lightweight examples for searching posts, scraping comments, and monitoring subreddits.
Written by Alex P.
- Bun
- reddit scraping
- reddit API
- JavaScript
- API integration
Bun is a fast JavaScript runtime that’s great for API scripts and data pipelines. This guide shows how to use FetchLayer’s Reddit API with Bun — same API, faster runtime.
Setup
- Get a free API key (no credit card)
- Install Bun if you haven’t:
curl -fsSL https://bun.sh/install | bash - No npm packages needed — Bun has
fetchbuilt in.
API Client
const API_KEY = Bun.env.FETCHLAYER_API_KEY!;
const BASE = 'https://api.fetchlayer.dev/reddit';
async function reddit(endpoint: string, body: Record<string, unknown>) {
const res = await fetch(`${BASE}/${endpoint}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
}
Search Reddit
const data = await reddit('search', {
query: 'best bun packages 2026',
sort: 'top',
limit: 10,
});
for (const post of data.results) {
console.log(`${post.title} — r/${post.subreddit} — ${post.score} pts`);
}
Run it:
FETCHLAYER_API_KEY=ss-your-key bun run search.ts
Get Subreddit Posts
const data = await reddit('community-posts', {
subreddit: 'javascript',
sort: 'top',
time: 'week',
limit: 20,
});
for (const post of data.posts) {
console.log(`[${post.score}] ${post.title}`);
}
Scrape a Post with Comments
const thread = await reddit('post', {
url: 'https://www.reddit.com/r/programming/comments/abc123/some_post/',
pages: 2,
});
console.log(`${thread.title} — ${thread.comments.length} comments`);
Full Example: Reddit Keyword Monitor
Save as monitor.ts:
const API_KEY = Bun.env.FETCHLAYER_API_KEY!;
const BASE = 'https://api.fetchlayer.dev/reddit';
async function reddit(endpoint: string, body: Record<string, unknown>) {
const res = await fetch(`${BASE}/${endpoint}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
}
const keyword = Bun.argv[2] ?? 'your-product';
const subreddits = ['startups', 'SaaS', 'webdev', 'javascript'];
console.log(`\nSearching for "${keyword}"...\n`);
for (const sub of subreddits) {
const data = await reddit('search', {
query: keyword,
subreddit: sub,
sort: 'new',
limit: 5,
});
if (data.results?.length) {
console.log(`r/${sub}:`);
for (const post of data.results) {
console.log(` [${post.score}] ${post.title}`);
console.log(` ${post.url}\n`);
}
}
}
process.exit(0);
Run it:
FETCHLAYER_API_KEY=ss-your-key bun run monitor.ts "react server components"
Bun-Specific Tips
Use Bun.env instead of process.env — it’s faster and type-aware.
Write results to a file:
const data = await reddit('search', { query: 'bun runtime', sort: 'top' });
await Bun.write('results.json', JSON.stringify(data, null, 2));
Run on a schedule with cron:
# Add to crontab: every hour
0 * * * * cd /path/to/project && FETCHLAYER_API_KEY=ss-... bun run monitor.ts >> log.txt 2>&1
Handling Errors and Rate Limits
The client above throws a generic error for any non-OK response, which is fine for a script you’re watching, but a background job needs to tell a 429 apart from a 401. FetchLayer returns standard HTTP status codes: 401 for a missing or invalid API key, 400 when a required field like query or subreddit is missing, and 429 once you exceed your plan’s request rate:
async function reddit(endpoint: string, body: Record<string, unknown>, retries = 2): Promise<any> {
for (let attempt = 0; attempt <= retries; attempt++) {
const res = await fetch(`${BASE}/${endpoint}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (res.status === 429) {
if (attempt === retries) throw new Error('Rate limited — back off and try again later');
const wait = Number(res.headers.get('Retry-After') ?? 5);
await Bun.sleep(wait * 1000);
continue;
}
if (res.status === 401) throw new Error('Invalid or missing API key');
if (res.status === 400) throw new Error(`Bad request to /${endpoint}: ${await res.text()}`);
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
}
}
A 429 mid-run is expected behavior on the free tier, not a bug — the retry above respects Retry-After (via Bun.sleep) rather than hammering the endpoint again immediately.
Typing the responses
Bun runs TypeScript directly, so there’s no build step between you and typed responses. Declaring the shapes you actually use catches the common mistake of reading data.posts when the endpoint returned data.results:
interface RedditPost {
title: string;
subreddit: string;
score: number;
numComments: number;
url: string;
author: string;
createdUtc: number;
}
interface SearchResponse {
results: RedditPost[];
}
interface CommunityPostsResponse {
posts: RedditPost[];
}
async function reddit<T>(endpoint: string, body: Record<string, unknown>): Promise<T> {
const res = await fetch(`${BASE}/${endpoint}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json() as Promise<T>;
}
const { results } = await reddit<SearchResponse>('search', {
query: 'bun runtime',
sort: 'top',
limit: 10,
});
Note the different response keys per endpoint — search returns results, community-posts returns posts. Typing them separately makes that difference a compile error instead of an undefined at runtime.
Fetching several subreddits concurrently
The monitor above searches subreddits one at a time. Since each call is independent, running them concurrently turns a serial wait into a single round trip — this is where Bun’s runtime speed actually shows up in a data script:
const subreddits = ['startups', 'SaaS', 'webdev', 'javascript'];
const settled = await Promise.allSettled(
subreddits.map((subreddit) =>
reddit<SearchResponse>('search', {
query: keyword,
subreddit,
sort: 'new',
limit: 5,
}).then((data) => ({ subreddit, posts: data.results ?? [] })),
),
);
for (const outcome of settled) {
if (outcome.status === 'rejected') {
console.error('Failed:', outcome.reason.message);
continue;
}
const { subreddit, posts } = outcome.value;
if (posts.length) console.log(`r/${subreddit}: ${posts.length} hits`);
}
Use Promise.allSettled rather than Promise.all here. With Promise.all, one rate-limited subreddit rejects the whole batch and you lose the results that did succeed.
If you’re scanning a lot of subreddits, cap the concurrency so you don’t trip a 429 on the first tick:
async function mapLimit<T, R>(items: T[], limit: number, fn: (item: T) => Promise<R>) {
const results: R[] = [];
const queue = [...items];
await Promise.all(
Array.from({ length: limit }, async () => {
while (queue.length) results.push(await fn(queue.shift()!));
}),
);
return results;
}
const posts = await mapLimit(subreddits, 3, (s) =>
reddit<SearchResponse>('search', { query: keyword, subreddit: s, sort: 'new', limit: 5 }),
);
Persisting results with bun:sqlite
A monitor that only prints to stdout can’t tell you what’s new. Bun ships SQLite in the runtime — no native module to compile, no dependency to install — which makes deduplication about five lines of work:
import { Database } from 'bun:sqlite';
const db = new Database('reddit.sqlite');
db.run(`
CREATE TABLE IF NOT EXISTS posts (
url TEXT PRIMARY KEY,
title TEXT NOT NULL,
subreddit TEXT NOT NULL,
score INTEGER,
seen_at INTEGER NOT NULL
)
`);
const insert = db.prepare(`
INSERT INTO posts (url, title, subreddit, score, seen_at)
VALUES ($url, $title, $subreddit, $score, $seen_at)
ON CONFLICT(url) DO NOTHING
`);
function recordNew(posts: RedditPost[]) {
const fresh: RedditPost[] = [];
for (const post of posts) {
const { changes } = insert.run({
$url: post.url,
$title: post.title,
$subreddit: post.subreddit,
$score: post.score,
$seen_at: Date.now(),
});
// changes === 0 means the URL was already stored.
if (changes > 0) fresh.push(post);
}
return fresh;
}
ON CONFLICT DO NOTHING plus a primary key on url does the deduplication in the database rather than in application code, and changes tells you which rows were genuinely new — that’s your alert list.
Serving the results over HTTP
If you want to read the monitor’s output from somewhere other than a terminal, Bun.serve() turns the SQLite table into a small JSON endpoint without adding a framework:
Bun.serve({
port: 3000,
routes: {
'/api/posts': () => {
const rows = db.query('SELECT * FROM posts ORDER BY seen_at DESC LIMIT 50').all();
return Response.json(rows);
},
},
});
That’s the whole loop: cron runs the monitor, SQLite stores and deduplicates, Bun.serve() exposes it. No build step and no dependencies beyond the runtime.
What’s Next
- Reddit API with Node.js — Node.js version
- Reddit API with TypeScript — typed version
- Reddit API with Go — Go integration
- How to Scrape Reddit in 2026 — all scraping methods
- FetchLayer API Reference