Twitter API with JavaScript: Get Tweets in Node.js (2026)
Call the Twitter API from JavaScript with built-in fetch: search posts, read a timeline, page with a cursor and save CSV, tested on real X data.
Tarun Khatri
Published · Updated · 7 min read
Using the Twitter API from JavaScript takes one fetch call in Node.js: send your key in a header and read the posts from the JSON.
This guide is for you if you need X (Twitter) posts in a Node.js script or a file. You get tested code for search, timelines and paging, then the official route. We run Weir, the API in the examples.
Save this as quick.mjs and run node quick.mjs:
const response = await fetch("https://api.weirapp.io/v1/x/search", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WEIR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query: "javascript", language: "en", limit: 20 }),
});
const { data } = await response.json();
for (const post of data) console.log(post.author.username, post.text.slice(0, 80));
You need Node.js and a free Weir key, and no npm packages. The map:
- Get a keySign up, copy a key, set WEIR_API_KEY.
- Search postsPOST /v1/x/search with a query or filters.
- Read a timelineGET /v1/x/users/{user}/posts.
- Page with the cursorSend meta.nextCursor back until it is null.
- Save the postsWrite JSON and CSV with node:fs.
Step 1: Get a key and keep it on the server
Sign up with no card, create a key under API keys, and set it in your shell:
export WEIR_API_KEY="weir_live_..."
Node.js has shipped fetch since version 18, but use a current LTS line: 22 or 24 as of October 2026.
Put one helper in weir.mjs. It sends your key, waits out a 429, and throws on any other error:
const key = process.env.WEIR_API_KEY;
export async function weir(path, body, tries = 3) {
const response = await fetch(`https://api.weirapp.io${path}`, {
method: body ? "POST" : "GET",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: body && JSON.stringify(body),
});
if (response.status === 429 && tries > 1) {
const seconds = Number(response.headers.get("retry-after") ?? 5);
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
return weir(path, body, tries - 1);
}
if (!response.ok) throw new Error(`Weir answered ${response.status}`); // 402: out of credits
return { page: await response.json(), credits: response.headers.get("x-credits-charged") };
}
Step 2: Search tweets with JavaScript
You search with POST /v1/x/search and a JSON body. Write X's search syntax in query, or use fields like fromUsers and minLikes. Save this as search.mjs:
import { weir } from "./weir.mjs";
const { page, credits } = await weir("/v1/x/search", {
fromUsers: ["nodejs"],
minLikes: 100,
onlyOriginal: true, // no replies
limit: 20,
});
for (const post of page.data) {
console.log(post.createdAt.slice(0, 10), post.likeCount, post.id);
}
console.log("credits:", credits);
We ran this query through Weir on 22 October 2026 with limit: 3 and got three @nodejs posts for 4 credits. Given that response, the script prints:
2026-08-27 567 2092987575971484048
2026-08-20 256 2090507554119946595
2026-08-05 294 2085047021002719399
credits: 4
One post, trimmed to the fields you'll use most:
{
"id": "2092987575971484048",
"text": "Node.js 24.20.0 and Node.js 26.8.1 are out now. 💚 ...",
"createdAt": "2026-08-27T14:48:18.000Z",
"author": { "username": "nodejs", "followersCount": 913142 },
"likeCount": 567,
"viewCount": 48188
}
Keep id as a string. It's bigger than Number.MAX_SAFE_INTEGER, so Number(id) would change your last digits.
minLikes is X's own filter, so a post just under it can slip through. Check likeCount too if the floor must be exact.
Step 3: Get a user's tweets in Node.js
You read a timeline with GET /v1/x/users/{user}/posts, newest first. {user} takes a username or a numeric id, and limit goes up to 20. In timeline.mjs:
import { weir } from "./weir.mjs";
const { page } = await weir("/v1/x/users/nodejs/posts?limit=20");
for (const post of page.data) {
const shown = post.retweetedPost ?? post; // a repost's own text is cut short
console.log(post.createdAt.slice(0, 10), shown.text.slice(0, 50));
}
Our @nodejs call with limit: 3 cost 6 credits, and the script prints:
2026-10-06 RT @openjsf: 🔄 Node.js is changing how it approac
2026-09-25 Finding vulnerabilities is only part of open sourc
2026-08-27 Node.js 24.20.0 and Node.js 26.8.1 are out now. 💚
Two were reposts, whose own text is cut short, so your code should read retweetedPost. Our first repost came back with retweetedPost: null, hence the fallback.
The 6 credits were 1 for the call, 2 for looking up the profile, and 3 for the posts. To reach further back, see how to download all tweets from a user.
Step 4: Page through results with the cursor
Every list response gives you meta.nextCursor. You send it back in the body for search, and as a cursor query parameter for GET endpoints.
Add this generator to weir.mjs:
export async function* searchPosts(body, maxPages = 10) {
let cursor;
for (let n = 0; n < maxPages; n++) {
const { page } = await weir("/v1/x/search", cursor ? { ...body, cursor } : body);
yield* page.data;
cursor = page.meta.nextCursor;
if (!cursor) return; // null: no more pages
}
}
We sent page one's cursor back on 22 October 2026 and got the next three posts, from late July and early August, for 4 more credits.
Three things this loop does for you:
- A page cap.
maxPagesstops a broad query from billing forever. - A null cursor ends it. When the results run out, Weir usually answers one more page, empty, with
nextCursor: null. That last call costs 1 credit. - The body never changes. A cursor is bound to its request; change a filter and you get a
400.
Step 5: Save tweets to JSON or CSV
node:fs writes both files. JSON keeps every field, and the CSV keeps the columns you pick. In save.mjs:
import { writeFile } from "node:fs/promises";
import { searchPosts } from "./weir.mjs";
const posts = await Array.fromAsync(searchPosts({ fromUsers: ["nodejs"], minLikes: 100 }, 5));
await writeFile("posts.json", JSON.stringify(posts, null, 2));
const columns = ["id", "createdAt", "username", "likeCount", "viewCount", "url", "text"];
const cell = (value) => `"${String(value ?? "").replaceAll('"', '""')}"`;
const lines = posts.map((post) => {
const row = { ...post, username: post.author.username };
return columns.map((column) => cell(row[column])).join(",");
});
await writeFile("posts.csv", "\uFEFF" + [columns.join(","), ...lines].join("\r\n"));
console.log(`saved ${posts.length} posts`);
We tested it on our two real pages plus an empty third. It made three calls and saved six posts, emoji intact.
The \uFEFF at the start marks the file as UTF-8. Microsoft says Excel opens your UTF-8 CSV normally when it has this mark.
The official route: Twitter API v2 in JavaScript
On X's own API, you need a developer account, prepaid credits and a bearer token. The long-standing Node.js client for the X API is twitter-api-v2, at version 1.29.1 since August 2026:
npm install twitter-api-v2
import { TwitterApi } from "twitter-api-v2";
const client = new TwitterApi(process.env.X_BEARER_TOKEN).readOnly;
const results = await client.v2.search("from:nodejs -is:retweet", {
max_results: 100,
"tweet.fields": "created_at,public_metrics",
});
for (const tweet of results.tweets) {
console.log(tweet.created_at, tweet.public_metrics?.like_count, tweet.id);
}
Recent search gives you the last 7 days, up to 100 posts per request. X's pricing page lists these pay-per-use rates as of October 2026:
For a timeline, get the id from client.v2.userByUsername(), then call client.v2.userTimeline(). X gives you up to 3,200 of an account's latest posts that way.
X also publishes its own TypeScript SDK, @xdevplatform/xdk, and you pay the same rates either way. Here is what 10,000 posts cost:
Weir's figure is 500 search calls plus 10,000 posts, or 10,500 credits, before your free ones. Every X rate is in our X API pricing guide.
If it doesn't work
fetch is not defined: your Node.js is older than 18. Install 22 or 24.Weir answered 401: the key is missing or wrong in this shell. Run theexportline again.Weir answered 402: you're out of credits, and retrying won't help. Top up, or wait for next month's free credits.Weir answered 400on page two: the body changed. A cursor works only with the request that made it.
When not to use Weir
- You need to post or act for a user. Weir is read-only; X's API, through
twitter-api-v2, can write. - You need a contract with X itself, or first-party data for compliance.
- You need 500 posts per archive request. X's full-archive search returns up to 500 per call.
- Price per post is all that matters. TwitterAPI.io lists $0.15 per 1,000 tweets and can post; see Twitter API alternatives.
FAQ
Is the Twitter API free for JavaScript developers?
No. X lists no free tier: you prepay credits, and new accounts get $20 after their first purchase. Weir gives you 5,000 free credits every month, about 4,760 search posts, with no card.
Can I call the Twitter API from browser JavaScript?
Not safely. Anyone who opens the page can read a key in browser code and spend your credits. Call the API from a server or serverless function, and send the browser only the posts.
How do I add TypeScript types?
Generate them from Weir's OpenAPI 3.1 spec with npx openapi-typescript https://api.weirapp.io/openapi.json -o weir.d.ts. The tool, at version 7.13.0, turns each path and response into types you import as paths.
Sources
All checked 22 October 2026:
- X API pricing: https://docs.x.com/x-api/getting-started/pricing
- X API search, fields and timelines: https://docs.x.com/x-api/posts/search/introduction, https://docs.x.com/x-api/fundamentals/fields and https://docs.x.com/x-api/posts/timelines/introduction
- X tools and libraries: https://docs.x.com/x-api/tools-and-libraries/overview
- twitter-api-v2 on npm (1.29.1, 4 August 2026) and its docs: https://www.npmjs.com/package/twitter-api-v2 and https://github.com/PLhery/node-twitter-api-v2
- Node.js fetch history and release status: https://nodejs.org/api/globals.html#fetch and https://nodejs.org/en/about/previous-releases
- openapi-typescript: https://openapi-ts.dev/introduction
- Microsoft, CSV UTF-8 files in Excel: https://support.microsoft.com/en-us/excel/opening-csv-utf-8-files-correctly-in-excel
- TwitterAPI.io pricing and posting: https://twitterapi.io/pricing and https://docs.twitterapi.io/api-reference/endpoint/create_tweet_v2
- Weir billing, pagination and errors: https://docs.weirapp.io/billing, https://docs.weirapp.io/pagination and https://docs.weirapp.io/errors
- Our @nodejs calls through Weir, 22 October 2026