## Why You Need Link Previews Every chat app, social platform, and messaging service shows link previews. You paste https://github.com, and you get a card with GitHub's logo, title, and description. That's not magic — it's parsing HTML and extracting Open Graph tags. Building this yourself sucks. You'll spend days handling: - Malformed HTML - Missing og:image tags - Slow-loading pages that timeout - Different encodings - Redirects and weird edge cases I've been there. Here's how to skip the pain using an API. ## Setup (5 minutes) ```bash npm install toolcenter ``` Grab an API key from ToolCenter. The free tier gives you 100 calls/month — plenty for testing. ```javascript import ToolCenter from "toolcenter"; const tc = new ToolCenter("your-api-key"); const preview = await tc.linkPreview({ url: "https://github.com" }); console.log(preview); // { // title: "GitHub: Let's build from here", // description: "GitHub is where over 100 million developers...", // image: "https://github.githubassets.com/images/...", // favicon: "https://github.githubassets.com/favicons/favicon.svg", // domain: "github.com" // } ``` That's it. No HTML parsing libraries, no regex nightmares. ## Building the API Here's a basic Express endpoint: ```javascript import express from "express"; import ToolCenter from "toolcenter"; const app = express(); const tc = new ToolCenter(process.env.TOOLCENTER_API_KEY); app.get("/preview", async (req, res) => { const { url } = req.query; if (!url || !url.startsWith("http")) { return res.status(400).json({ error: "Valid URL required" }); } try { const preview = await tc.linkPreview({ url }); res.json(preview); } catch (error) { // Don't expose internal errors to users res.status(500).json({ error: "Failed to fetch preview", fallback: { title: new URL(url).hostname } }); } }); app.listen(3000); ``` ## Caching (Do This From Day 1) Don't hit the API every time. Cache results: ```javascript const NodeCache = require("node-cache"); const cache = new NodeCache({ stdTTL: 3600 }); // 1 hour app.get("/preview", async (req, res) => { const { url } = req.query; const cached = cache.get(url); if (cached) { return res.json(cached); } const preview = await tc.linkPreview({ url }); cache.set(url, preview); res.json(preview); }); ``` For production, use Redis instead of in-memory cache. ## Handling Failures Like a Pro APIs fail. Networks timeout. Handle it gracefully: ```javascript async function getPreviewWithFallback(url) { try { return await tc.linkPreview({ url }); } catch (error) { // Rate limited? Wait and retry once if (error.status === 429) { await new Promise(r => setTimeout(r, 1000)); try { return await tc.linkPreview({ url }); } catch (e) { // Still failed, use fallback } } // Return minimal fallback const domain = new URL(url).hostname; return { title: domain, description: null, image: null, favicon: null, url }; } } ``` ## Real-World Optimizations **Batch requests** when possible: ```javascript const previews = await Promise.allSettled([ tc.linkPreview({ url: url1 }), tc.linkPreview({ url: url2 }), tc.linkPreview({ url: url3 }) ]); ``` **Validate URLs** before sending: ```javascript function isValidUrl(string) { try { const url = new URL(string); return url.protocol === "http:" || url.protocol === "https:"; } catch (_) { return false; } } ``` **Set timeouts** for your API calls: ```javascript const preview = await tc.linkPreview({ url, timeout: 10000 // 10 seconds max }); ``` ## The Alternative (Don't Do This) You could build this yourself with Cheerio and Puppeteer: ```javascript // Please don't const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); const title = await page.$eval("title", el => el.textContent); // ... 200 more lines of edge case handling ``` I've tried this. You'll spend weeks on edge cases and still miss things. ## Cost Reality Check ToolCenter's free tier (100 calls/month) costs $0. Paid plans start at €9/month for 10,000 calls. Building this yourself costs weeks of developer time plus infrastructure. Your call. ## Final Code Here's the complete service with error handling, caching, and validation: ```javascript import express from "express"; import ToolCenter from "toolcenter"; import NodeCache from "node-cache"; const app = express(); const tc = new ToolCenter(process.env.TOOLCENTER_API_KEY); const cache = new NodeCache({ stdTTL: 3600 }); function isValidUrl(string) { try { const url = new URL(string); return url.protocol === "http:" || url.protocol === "https:"; } catch (_) { return false; } } app.get("/preview", async (req, res) => { const { url } = req.query; if (!url || !isValidUrl(url)) { return res.status(400).json({ error: "Valid URL required" }); } const cached = cache.get(url); if (cached) { return res.json(cached); } try { const preview = await tc.linkPreview({ url, timeout: 10000 }); cache.set(url, preview); res.json(preview); } catch (error) { const fallback = { title: new URL(url).hostname, description: null, image: null, url }; res.json(fallback); } }); app.listen(3000); ``` Ship it. --- *ToolCenter has SDKs for [Node.js](https://www.npmjs.com/package/toolcenter), [Python](https://pypi.org/project/toolcenter/), and [PHP](https://packagist.org/packages/toolcenter/sdk). The link preview API is part of a larger suite — you also get screenshot, PDF, QR code, and email validation APIs under the same plan.*