<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pratham Israni</title>
    <description>The latest articles on DEV Community by Pratham Israni (@pratham_kumar_8cc837c51ed).</description>
    <link>https://dev.to/pratham_kumar_8cc837c51ed</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3892652%2Ffe4039fd-b8f6-49b7-a92e-49ef0328205a.jpg</url>
      <title>DEV Community: Pratham Israni</title>
      <link>https://dev.to/pratham_kumar_8cc837c51ed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pratham_kumar_8cc837c51ed"/>
    <language>en</language>
    <item>
      <title>A jobs API with 1M live listings, straight from company career pages</title>
      <dc:creator>Pratham Israni</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:42:04 +0000</pubDate>
      <link>https://dev.to/pratham_kumar_8cc837c51ed/a-jobs-api-with-1m-live-listings-straight-from-company-career-pages-91p</link>
      <guid>https://dev.to/pratham_kumar_8cc837c51ed/a-jobs-api-with-1m-live-listings-straight-from-company-career-pages-91p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmeh2bfkep1kps1l1oq0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmeh2bfkep1kps1l1oq0o.png" alt="Cover: 1M live jobs, straight from employers' career pages" width="800" height="420"&gt;&lt;/a&gt;&lt;br&gt;
I started this for myself. I wanted the open roles at every big company in one place, without visiting hundreds of career pages. It now holds about 1 million live jobs from 35,000+ companies, and it's on RapidAPI.&lt;/p&gt;

&lt;p&gt;The jobs aren't scraped from job boards. They come from the employers' own career-site platforms (Greenhouse, Lever, Ashby, Workday, SmartRecruiters and 20 more sources), so every &lt;code&gt;applyUrl&lt;/code&gt; points to the company's own application page. A nightly refresh adds new postings, about 30,000 a day, and drops the ones that have closed.&lt;/p&gt;
&lt;h2&gt;
  
  
  The filters are the reason it exists
&lt;/h2&gt;

&lt;p&gt;The job-search APIs I compared filter by keyword, location and sometimes experience, but not by department or category. I wanted to ask for "senior DevOps, remote, open to people in India" and get exactly that. So this one has 19 categories with 114 subcategories, five experience levels, country, state and city, remote, hybrid or onsite, keyword include and exclude, company, source, and posted-within-days.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkl7ytjrxjsbrbddt9re6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkl7ytjrxjsbrbddt9re6.png" alt="One request filtered by category, experience level, country and work mode, with the first job of the response" width="800" height="430"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s1"&gt;'https://company-career-sites-jobs-api1.p.rapidapi.com/api/public/v1/jobs?categories=DevOps%2C%20Cloud%20%26%20Infrastructure&amp;amp;experienceLevels=Senior&amp;amp;countries=India&amp;amp;workModes=remote&amp;amp;limit=2'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'x-rapidapi-host: company-career-sites-jobs-api1.p.rapidapi.com'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'x-rapidapi-key: YOUR_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each response carries a &lt;code&gt;nextCursor&lt;/code&gt;. Pass it back as &lt;code&gt;cursor&lt;/code&gt; to get the next page.&lt;/p&gt;

&lt;p&gt;One edge case to know about: remote jobs that are open to anyone, anywhere, have no country, so a country filter drops them. &lt;code&gt;includeWorldwideRemote=true&lt;/code&gt; brings them back, and &lt;code&gt;includeUnspecifiedLocation=true&lt;/code&gt; does the same for jobs that list no location at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database took three tries
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6iini6ruxisb2oo5xpqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6iini6ruxisb2oo5xpqn.png" alt="The job data moved from GitHub JSON files to Supabase to Postgres on an Oracle VM" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first the data lived in GitHub JSON files inside an app, and the first load took far too long. I moved it to Supabase, hit the 500 MB limit, and queries slowed down as the data grew. Now it's Postgres on an Oracle VM with more RAM and storage, and queries are very fast. Getting the VM took a workflow of its own. One filtered India query needed about 3 seconds until I added indexes on the lowercased columns; it now runs in under half a second.&lt;/p&gt;

&lt;p&gt;If your dataset is heading toward a million rows, start on the machine you'll end up on. I paid for two migrations to learn that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it isn't
&lt;/h2&gt;

&lt;p&gt;It isn't every job on the internet. Aggregators that index the whole web have a bigger catalogue than mine, and if that's what you need, use one. This is the opposite trade: about 1M jobs, all from employers' own pages, with filters that go further. There is no UI and nothing decorative, only structured data.&lt;/p&gt;

&lt;p&gt;That suits a new job board filling its first listings, a niche board that needs a backfill for one category and region, or an alert tool that watches one filter every morning. Checking company pages by hand every day takes hours. One call replaces it.&lt;/p&gt;

&lt;p&gt;On RapidAPI each call returns up to 10 jobs plus a cursor, which works for search, alerts and filling a niche. If you need the whole index, the same data is available on &lt;a href="https://apify.com/pk07007/ats-jobs-api" rel="noopener noreferrer"&gt;Apify&lt;/a&gt;, billed per result.&lt;/p&gt;

&lt;p&gt;There's a free Basic plan to try it: &lt;a href="https://rapidapi.com/pk2732004/api/company-career-sites-jobs-api1?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=rapidapi_launch" rel="noopener noreferrer"&gt;Company Career Sites Jobs API on RapidAPI&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>rapidapi</category>
      <category>showdev</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Moving 530k Jobs from Supabase to Postgres on a Free Oracle VM (14s -&gt; 47ms)</title>
      <dc:creator>Pratham Israni</dc:creator>
      <pubDate>Sat, 10 Oct 2026 06:12:05 +0000</pubDate>
      <link>https://dev.to/pratham_kumar_8cc837c51ed/moving-530k-jobs-from-supabase-to-postgres-on-a-free-oracle-vm-14s-47ms-342f</link>
      <guid>https://dev.to/pratham_kumar_8cc837c51ed/moving-530k-jobs-from-supabase-to-postgres-on-a-free-oracle-vm-14s-47ms-342f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmnb23fts0gjig73a25ej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmnb23fts0gjig73a25ej.png" alt="Latency of apis before the migration" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built a job feed. It pulls jobs straight from company career pages into one searchable list. No login, no redirects through job boards.&lt;/p&gt;

&lt;p&gt;In a few months it grew from 10,000 jobs to over 530,000. Every time the number jumped, something broke. Here's what broke and how I fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Career pages, then ATS platforms
&lt;/h2&gt;

&lt;p&gt;I started by writing a scraper for each company's career page. One company, one scraper. Slow.&lt;/p&gt;

&lt;p&gt;Then I noticed most companies don't build their own job board. They use an applicant tracking system (ATS) like Greenhouse, Lever or Ashby, and these have public job data. One integration covered thousands of companies. The count went 10.5k → 16.5k → 100k → 300k → 530k, and it's still growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub as the database
&lt;/h2&gt;

&lt;p&gt;At first, GitHub was my database. A scheduled GitHub Action fetched jobs, saved one JSON file per company, and committed it. The site read those files. Free, versioned, nothing to manage.&lt;/p&gt;

&lt;p&gt;Up to around 100k jobs, it was fast.&lt;/p&gt;

&lt;p&gt;The problem: a browser can only filter data it already has. So the first load had to download everything. As data grew, first paint went to 13–15 seconds. Filters were instant after that, but nobody waits 15 seconds to see them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving to Supabase
&lt;/h2&gt;

&lt;p&gt;So I moved to Postgres on Supabase. The GitHub Action synced data into it every night. The page loaded 50 jobs at a time, and filters became SQL queries.&lt;/p&gt;

&lt;p&gt;First load got better. But every filter now took 5–7 seconds. Then at 530k jobs, the database crossed the 500 MB free limit.&lt;/p&gt;

&lt;p&gt;When I checked, two queries were the real problem: counting all jobs, and grouping jobs by company for the filter list. Each took about 14 seconds. That's not Supabase's fault. A small shared free instance just isn't made to scan half a million rows on every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting a free Oracle server
&lt;/h2&gt;

&lt;p&gt;Oracle Cloud's free tier gives you an ARM server with up to 2 cores and 12 GB RAM. For a 532 MB database, that's more than enough.&lt;/p&gt;

&lt;p&gt;But it's hard to get. Every time I clicked Create, I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Out of host capacity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of refreshing all day, I wrote a script. It calls the Oracle CLI to create the server, and if the error is "no capacity" or a rate limit, it waits and tries again. A GitHub Action ran it every few minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in &lt;/span&gt;1 2 3 4 5&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;timeout &lt;/span&gt;60 oci &lt;span class="nt"&gt;--no-retry&lt;/span&gt; compute instance launch ... &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break
  sleep &lt;/span&gt;20
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It failed 124 times overnight. The 125th try worked, and I woke up to a free 12 GB server in Mumbai.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh0bdc5ouuf36k3qsi36w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh0bdc5ouuf36k3qsi36w.png" alt="The script's scoreboard: 57 capacity errors, 67 rate limits, 124 failures before it worked" width="775" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Postgres
&lt;/h2&gt;

&lt;p&gt;The next morning, it took me about 3–4 hours to get Postgres running on the new VM and start syncing the data.&lt;/p&gt;

&lt;p&gt;I ran one or two of the slow queries on it, and that was enough to convince me. I quickly pointed Vercel and my Cloudflare Worker (which serves my public API) to the new database and tested the site.&lt;/p&gt;

&lt;p&gt;The results were mind-blowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Same queries, run directly on each database:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Supabase (free)&lt;/th&gt;
&lt;th&gt;Oracle VM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Count all jobs&lt;/td&gt;
&lt;td&gt;13.7 s&lt;/td&gt;
&lt;td&gt;26 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group jobs by company&lt;/td&gt;
&lt;td&gt;13.9 s&lt;/td&gt;
&lt;td&gt;47 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword search&lt;/td&gt;
&lt;td&gt;177 ms&lt;/td&gt;
&lt;td&gt;5.5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First page of results&lt;/td&gt;
&lt;td&gt;22 ms&lt;/td&gt;
&lt;td&gt;0.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgw3ce68rqgspnoe7cemg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgw3ce68rqgspnoe7cemg.png" alt="Latency of apis after the migration" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the browser, pages that took 16–48 seconds now load in under a quarter second.&lt;/p&gt;

&lt;p&gt;To be fair, a lot of this is hardware. The whole database now fits in memory on a 12 GB machine, while the free instance was small and shared. Same Postgres, just more room.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Find the slow query before blaming the platform. Two queries caused almost everything.&lt;/li&gt;
&lt;li&gt;Check what a free tier actually limits. Mine capped storage, but compute is what hurt.&lt;/li&gt;
&lt;li&gt;If you have to wait for something, let a script do the waiting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can try the job-feed here (non-profit): &lt;a href="https://pk-toolbox.vercel.app/tools/dev-utils/job-feed" rel="noopener noreferrer"&gt;link&lt;/a&gt;. &lt;br&gt;
If you want the data in your own app, it's also on Apify as an API: &lt;a href="https://apify.com/pk07007/ats-jobs-api" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>apify</category>
      <category>career</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>My keyword search took 5 seconds over 355k jobs, and C++ matched random jobs</title>
      <dc:creator>Pratham Israni</dc:creator>
      <pubDate>Sun, 04 Oct 2026 18:26:51 +0000</pubDate>
      <link>https://dev.to/pratham_kumar_8cc837c51ed/my-keyword-search-took-5-seconds-over-355k-jobs-and-c-matched-random-jobs-5bjk</link>
      <guid>https://dev.to/pratham_kumar_8cc837c51ed/my-keyword-search-took-5-seconds-over-355k-jobs-and-c-matched-random-jobs-5bjk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxqzte60w9z4fpbkqu77i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxqzte60w9z4fpbkqu77i.png" alt="Architecture diagram of the job index. A nightly GitHub Action commits per-company job JSON files. A sync script loads them into Supabase Postgres (jobs, companies, job_countries). The free Next.js UI on Vercel queries Supabase directly. On the paid path, an Apify Actor is run by the buyer, calls a Cloudflare Worker, which reads Supabase through Hyperdrive with a read-only role. A Durable Object rate limiter, keyed by API key, sits beside the Worker. The Actor writes each result to its dataset." width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm building a searchable index of about 310,000 open jobs from roughly 6,800 company career pages, mostly Indian IT and services firms. Most of them publish their openings through public job feeds, so I pull from those.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5-second search
&lt;/h2&gt;

&lt;p&gt;My first keyword search used &lt;code&gt;ILIKE&lt;/code&gt; over title and company. On 355k rows, &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; showed a full sequential scan at about 5 seconds, which is too slow for a search box.&lt;/p&gt;

&lt;p&gt;Trigram indexes fixed the speed but used too much storage on the free tier. I switched to a GIN index over &lt;code&gt;to_tsvector('simple', ...)&lt;/code&gt;. I chose &lt;code&gt;simple&lt;/code&gt; over &lt;code&gt;english&lt;/code&gt; because &lt;code&gt;english&lt;/code&gt; stems words and drops stopwords, which makes results hard to predict.&lt;/p&gt;

&lt;h2&gt;
  
  
  The C++ problem
&lt;/h2&gt;

&lt;p&gt;With the new index, searching for C++ returned unrelated jobs. Postgres reduces &lt;code&gt;C++&lt;/code&gt; to a bare &lt;code&gt;c&lt;/code&gt; token under both text configs, and &lt;code&gt;.NET&lt;/code&gt; to &lt;code&gt;net&lt;/code&gt;. So a C++ search matched anything with a standalone "c" in it.&lt;/p&gt;

&lt;p&gt;The fix is a normalization step applied on both sides of the search:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;c++&lt;/code&gt; → &lt;code&gt;cpp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c#&lt;/code&gt; → &lt;code&gt;csharp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.net&lt;/code&gt; → &lt;code&gt;dotnet&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It runs in the index expression and in the query builder, so they stay in sync. The catch is that these rules live in two places, and I have to keep them matching by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limiting, what didn't work
&lt;/h2&gt;

&lt;p&gt;My first limiter was a &lt;code&gt;globalThis&lt;/code&gt; Map on Vercel. Every cold start reset it, so it wasn't really a limit. Cloudflare's native rate-limit binding didn't enforce for me either. I sent 220+ requests and got zero 429s.&lt;/p&gt;

&lt;p&gt;A Durable Object keyed by API key (&lt;code&gt;idFromName(apiKey)&lt;/code&gt;) worked. It started returning 429 around request 62, and other keys weren't affected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hyperdrive setup gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use Supabase's &lt;strong&gt;direct&lt;/strong&gt; connection (port 5432). Hyperdrive does its own pooling.&lt;/li&gt;
&lt;li&gt;Use one &lt;code&gt;pg&lt;/code&gt; &lt;code&gt;Client&lt;/code&gt; per request.&lt;/li&gt;
&lt;li&gt;On Supabase, &lt;code&gt;GRANT postgres TO hyperdrive_user&lt;/code&gt; isn't allowed. I granted scoped &lt;code&gt;SELECT&lt;/code&gt; and added an RLS policy for that role only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No job descriptions. Title, company, location, apply link, and dates only.&lt;/li&gt;
&lt;li&gt;Refreshes about once a day. Each row has &lt;code&gt;lastSeenAt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;About a third of rows have no city.&lt;/li&gt;
&lt;li&gt;No salary. The sources don't publish it consistently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've run a similar setup, I'd like to know how you handled keyword search over a large table without a trigram index.&lt;/p&gt;

&lt;p&gt;The Actor is here if you want to look at the data: &lt;a href="https://apify.com/pk07007/ats-jobs-api" rel="noopener noreferrer"&gt;https://apify.com/pk07007/ats-jobs-api&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>discuss</category>
      <category>cloud</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Sonner vs. robot-toast: When "Invisible" UI Isn't Enough</title>
      <dc:creator>Pratham Israni</dc:creator>
      <pubDate>Thu, 14 May 2026 02:15:00 +0000</pubDate>
      <link>https://dev.to/pratham_kumar_8cc837c51ed/sonner-vs-robot-toast-when-invisible-ui-isnt-enough-2a6f</link>
      <guid>https://dev.to/pratham_kumar_8cc837c51ed/sonner-vs-robot-toast-when-invisible-ui-isnt-enough-2a6f</guid>
      <description>&lt;p&gt;I used &lt;code&gt;Sonner&lt;/code&gt; for almost everything before robot-toast existed.&lt;/p&gt;

&lt;p&gt;Genuinely liked it. Clean, premium, gets out of the way. You fire a toast, user gets the info, nobody thinks twice about it. Invisible by design and that's not a criticism, that's exactly what it's built for.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;react-hot-toast&lt;/code&gt; is the same philosophy but even more minimal. No config, no setup, just works. If you want something running in 30 seconds, that's your library.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Toastify&lt;/code&gt; is the opposite, it's been around forever, has every feature you could possibly need, and the bundle size shows it. It's the jQuery of toast libraries. Still used everywhere for a reason.&lt;/p&gt;

&lt;p&gt;But then I noticed something while building my portfolio and working on a startup. Platforms like &lt;em&gt;Codolio, CodeChef, Coding Ninjas&lt;/em&gt; — they all have a distinct personality. You feel it the moment you land. The colors, the tone, the micro-interactions. Everything is intentional.&lt;/p&gt;

&lt;p&gt;And then a toast pops up. A plain rectangle. No personality. Like a different product showed up for two seconds and left.&lt;/p&gt;

&lt;p&gt;That's where thought of making &lt;code&gt;robot-toast&lt;/code&gt; came from.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5oep7f00gwfi7puf6d2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5oep7f00gwfi7puf6d2.png" alt="Comparison between all the toasts i know" width="708" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Sonner&lt;/code&gt; is still my recommendation if you want invisible notifications. react-hot-toast if you want zero friction. &lt;code&gt;Toastify&lt;/code&gt; if you need every feature possible.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;robot-toast&lt;/code&gt; is for when invisible isn't enough, when your product has a personality and your notifications should too.&lt;/p&gt;

&lt;p&gt;package: &lt;a href="https://www.npmjs.com/package/robot-toast" rel="noopener noreferrer"&gt;npm i robot-toast&lt;/a&gt;&lt;br&gt;
playground: &lt;a href="https://robot-toast.vercel.app/playground" rel="noopener noreferrer"&gt;robot-toast.vercel.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>npm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I never expected this response ~robot-toast</title>
      <dc:creator>Pratham Israni</dc:creator>
      <pubDate>Fri, 08 May 2026 09:43:07 +0000</pubDate>
      <link>https://dev.to/pratham_kumar_8cc837c51ed/i-never-expected-this-response-robot-toast-3gh7</link>
      <guid>https://dev.to/pratham_kumar_8cc837c51ed/i-never-expected-this-response-robot-toast-3gh7</guid>
      <description>&lt;p&gt;It started with an SSO project.&lt;/p&gt;

&lt;p&gt;I was building an IDP server — the kind that handles authentication across connected domains. I wanted a Google-like widget, the one that shows up and says "&lt;em&gt;Hey, you switched accounts&lt;/em&gt;" or "&lt;em&gt;Welcome back, Pratham.&lt;/em&gt;" Clean. Branded. Satisfying.&lt;/p&gt;

&lt;p&gt;So I built a toast that shipped via a script tag directly from my &lt;em&gt;IDP server&lt;/em&gt;. It worked. But it was rigid, tightly coupled to my server, and offered almost zero flexibility for anything outside that one use case.&lt;br&gt;
That bothered me.&lt;/p&gt;

&lt;p&gt;So I asked myself — what if the toast could live anywhere, independent of my server? What if any project could use it?&lt;br&gt;
That's when I found out you can just... make your own npm package. I set a target of one night. And in that one night, &lt;code&gt;robot-toast@1.0.0-beta.0&lt;/code&gt; was born.&lt;br&gt;
I have no idea why I made them robots. It just felt right.&lt;/p&gt;

&lt;p&gt;I built a playground to see how it looked from the outside. Posted it on Reddit, LinkedIn, sent it to some friends. People had opinions. Good ones, bad ones, weird ones. I shipped fixes on weekends, mostly when I felt like it. No roadmap. No deadlines.&lt;br&gt;
Two months later, &lt;code&gt;v2.1.9&lt;/code&gt;. Sixteen variants. Four themes. Zero dependencies. A React hook. SSR safe.&lt;br&gt;
And somewhere along the way — &lt;code&gt;3,200&lt;/code&gt; downloads. More traction than the SSO project that started the whole thing.&lt;/p&gt;

&lt;p&gt;Here's the thing I want to be honest about: I didn't build this for you.&lt;/p&gt;

&lt;p&gt;I built it because I needed it.&lt;br&gt;
I open-sourced it because why not.&lt;br&gt;
I maintained it because the feedback was interesting, not because I felt obligated. There's no company behind it, no roadmap, no monetization plan.&lt;/p&gt;

&lt;p&gt;If you use it and love it — genuinely happy about that.&lt;br&gt;
If you use it and curse at it — valid, open a GitHub issue.&lt;br&gt;
If you never use it — also completely fine.&lt;/p&gt;

&lt;p&gt;That detachment, weirdly, made it better. Every update came from real curiosity, not pressure.&lt;/p&gt;

&lt;p&gt;If you want robots in your notifications - &lt;a href="https://robot-toast.vercel.app/" rel="noopener noreferrer"&gt;robot-toast demo&lt;/a&gt; , use &lt;a href="https://www.npmjs.com/package/robot-toast" rel="noopener noreferrer"&gt;npm i robot-toast&lt;/a&gt;&lt;br&gt;
If not, I hope your toast library of choice brings you peace.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>npm</category>
    </item>
    <item>
      <title>I Built a Toast Library Where a Robot Delivers Your Notifications 🤖</title>
      <dc:creator>Pratham Israni</dc:creator>
      <pubDate>Thu, 23 Apr 2026 16:39:17 +0000</pubDate>
      <link>https://dev.to/pratham_kumar_8cc837c51ed/i-built-a-toast-library-where-a-robot-delivers-your-notifications-54mo</link>
      <guid>https://dev.to/pratham_kumar_8cc837c51ed/i-built-a-toast-library-where-a-robot-delivers-your-notifications-54mo</guid>
      <description>&lt;p&gt;Every React app needs toast notifications. So there are roughly a thousand toast libraries on npm.&lt;/p&gt;

&lt;p&gt;I built another one. But this one has a robot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53bhj8kytjt963vbnzd5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F53bhj8kytjt963vbnzd5.gif" alt="Using robot-toast is Fun" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why though?
&lt;/h2&gt;

&lt;p&gt;I was building a side project and dropped in &lt;code&gt;react-toastify&lt;/code&gt;. Functional. Totally forgettable. Then tried &lt;code&gt;sonner&lt;/code&gt; — cleaner, but still just a rectangle sliding in from the corner.&lt;/p&gt;

&lt;p&gt;I wanted something that actually made users presents my persona on my site when something happened in the app. So I built &lt;code&gt;robot-toast&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What started as a weekend experiment is now a full-featured, production-ready library at v2.1.5 — 21 published versions, a demo playground, tests, and actual downloads from people I don't know.&lt;/p&gt;




&lt;h2&gt;
  
  
  What makes it different
&lt;/h2&gt;

&lt;p&gt;Most toast libraries compete on the same axis: bundle size, API ergonomics, animation smoothness. robot-toast does all of that, but the actual differentiator is that you can customize everything in your own way and bring personality to your product, and to get you a headsup, &lt;code&gt;robot-toast&lt;/code&gt; comes with 16 robot variants.&lt;/p&gt;

&lt;p&gt;It sounds gimmicky. It isn't. Here's why it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error states feel less brutal when a robot looks flustered instead of a red bar flashing&lt;/li&gt;
&lt;li&gt;Success feels genuinely rewarding when the robot waves&lt;/li&gt;
&lt;li&gt;Users notice and remember it — which matters for apps where UX polish is a competitive edge&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The full feature set (v2)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;robot-toast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Basic usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;robot-toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;wave&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;robot-toast/robots&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello world&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saved!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something broke&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Custom robot!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;robotVariant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wave&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  16 tree-shakeable robot variants
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fozuto7g3go4789tjp4xq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fozuto7g3go4789tjp4xq.png" alt="16 In built Robots: wave, base, base2, success, error,&lt;br&gt;
  angry, angry2, shock, think, search,&lt;br&gt;
  loading, sleep, headPalm, typing,&lt;br&gt;
  validation, validation2." width="561" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each robot is a separate subpath export. You only ship the ones you use — no dead SVG weight in your bundle.&lt;/p&gt;

&lt;p&gt;You can also pass any custom SVG/PNG/GIF path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Custom!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;robotVariant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/my-mascot.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  toast.promise()
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saving…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Saved as &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of my favourite patterns. Attach loading/success/error states to any promise in a single call. No managing state yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typewriter effect
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deploying your app…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;typeSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message types out character by character. I haven't seen this in any other toast library. Small touch, weirdly satisfying to watch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom action buttons
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;File deleted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Undo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;restoreFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Keep&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gray&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No limit on number of buttons. Most libraries restrict you to one action and one cancel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Notification text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;colored&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bounce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zoom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slide&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-left&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bottom-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;autoClose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;typeSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;hideProgressBar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;draggable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pauseOnHover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pauseOnFocusLoss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;nearScreen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rtl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="na"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Framework-agnostic core
&lt;/h2&gt;

&lt;p&gt;The core library has no framework dependency. React is an &lt;strong&gt;optional&lt;/strong&gt; peer dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Vanilla JS&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;robot-toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Works anywhere&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// React hook&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRobotToast&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;robot-toast/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRobotToast&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saved!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Save&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works with React, Next.js, Vue, Angular, Svelte, vanilla JS — one install, no adapter packages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Accessibility
&lt;/h2&gt;

&lt;p&gt;This part I'm genuinely proud of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;error&lt;/code&gt; / &lt;code&gt;warning&lt;/code&gt; toasts: &lt;code&gt;role="alert"&lt;/code&gt; + &lt;code&gt;aria-live="assertive"&lt;/code&gt; — announced immediately&lt;/li&gt;
&lt;li&gt;Other types: &lt;code&gt;role="status"&lt;/code&gt; + &lt;code&gt;aria-live="polite"&lt;/code&gt; — announced at next opportunity&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-atomic="true"&lt;/code&gt; — full message re-announced on update&lt;/li&gt;
&lt;li&gt;Labeled close button for screen readers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most indie toast libraries treat a11y as an afterthought. I didn't want to do that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison with the popular ones
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;robot-toast&lt;/th&gt;
&lt;th&gt;Sonner&lt;/th&gt;
&lt;th&gt;react-toastify&lt;/th&gt;
&lt;th&gt;react-hot-toast&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework agnostic&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animated character&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typewriter effect&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unlimited buttons&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;toast.promise()&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero prod deps&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree-shakeable assets&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full a11y&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;partial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sonner is the dominant player right now and it deserves to be — the stacking animation and shadcn integration are excellent. robot-toast isn't trying to replace it. It's for the apps where personality matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Programmatic control
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Working…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Update it&lt;/span&gt;
&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Dismiss everything&lt;/span&gt;
&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closeAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo &amp;amp; playground:&lt;/strong&gt; &lt;a href="https://robot-toast.vercel.app" rel="noopener noreferrer"&gt;robot-toast.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/robot-toast" rel="noopener noreferrer"&gt;npmjs.com/package/robot-toast&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Pratham2703005/robot-toast-package" rel="noopener noreferrer"&gt;github.com/Pratham2703005/robot-toast-package&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;robot-toast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If you use it, drop a comment — I'm genuinely curious what people build with it. And if something's broken or missing, open an issue. v2 exists because of feedback on v1.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>showdev</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
