<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed"/>
    <language>en</language>
    <item>
      <title>Open Banking Was Built for the Wrong Future — and That's Why It's Perfect for AI Agents</title>
      <dc:creator>arun rajkumar</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:35:42 +0000</pubDate>
      <link>https://dev.to/mickyarun/open-banking-was-built-for-the-wrong-future-and-thats-why-its-perfect-for-ai-agents-4oha</link>
      <guid>https://dev.to/mickyarun/open-banking-was-built-for-the-wrong-future-and-thats-why-its-perfect-for-ai-agents-4oha</guid>
      <description>&lt;p&gt;Visa announced infrastructure for AI agents to make payments without asking you first.&lt;/p&gt;

&lt;p&gt;GoCardless shipped an MCP server in February so developers can talk to their payment platform in natural language.&lt;/p&gt;

&lt;p&gt;I build open banking payment infrastructure for the UK. I've been watching both of these announcements very closely.&lt;/p&gt;

&lt;p&gt;And I have a counterintuitive take: &lt;strong&gt;the payment rail everyone called "too complicated for normal users" might be the only one that actually works for AI agents.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Cards and AI Agents
&lt;/h2&gt;

&lt;p&gt;When an AI agent needs to make a payment on your behalf, the obvious infrastructure is what already exists. Cards. Stored credentials. The same rails your Netflix subscription uses.&lt;/p&gt;

&lt;p&gt;Here's the problem.&lt;/p&gt;

&lt;p&gt;Card authorisation is broad. When you give Stripe a card token, you're essentially giving that token permission to charge whatever you've authorised — subject to 3DS, fraud rules, and limits. But the authorisation scope isn't bound to a specific action.&lt;/p&gt;

&lt;p&gt;For an AI agent, that's dangerous.&lt;/p&gt;

&lt;p&gt;You want an agent to book a flight. It has your card token. Nothing technically stops it from booking the wrong flight, adding seat upgrades you didn't ask for, or — if the prompt is maliciously crafted — doing something you absolutely didn't intend.&lt;/p&gt;

&lt;p&gt;Visa understands this. Their Trusted Agent Protocol exists precisely to solve it: a way for merchants to verify that an agent is legitimate and acting within its authorised scope. It's clever engineering. But it's being bolted onto rails that weren't designed for it.&lt;/p&gt;

&lt;p&gt;Open banking wasn't designed for AI agents either. But its constraints happen to be exactly the right shape.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Open Banking Consent Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;When a customer pays via open banking — the way Atoa processes payments — here's what actually happens under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Merchant creates a payment consent object
   → amount: £49.99
   → merchant: Atoa test merchant
   → purpose: "Coffee subscription - April"

2. Customer is redirected to their bank
   → Bank shows: "Atoa wants to take £49.99 from your account"
   → Customer approves or declines
   → Bank issues a single-use authorisation code

3. Atoa exchanges the code for the payment
   → One payment. Specific amount. Specific purpose.
   → The authorisation is consumed. It cannot be reused.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every payment is its own consent event. Every consent is scoped to a specific amount and purpose. You can't overcharge. You can't quietly add extras. You can't reuse the authorisation.&lt;/p&gt;

&lt;p&gt;For a human user, this is friction. That's why open banking adoption was slow. Nobody wants to log into their banking app every time they buy something.&lt;/p&gt;

&lt;p&gt;For an AI agent, this friction is a feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Consent Model Fits AI Agents
&lt;/h2&gt;

&lt;p&gt;Think about what you actually want when an AI agent makes a payment on your behalf.&lt;/p&gt;

&lt;p&gt;You want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It to charge exactly the amount you authorised&lt;/li&gt;
&lt;li&gt;The scope to be limited to what you asked it to do&lt;/li&gt;
&lt;li&gt;The ability to revoke access without cancelling your card&lt;/li&gt;
&lt;li&gt;A clear audit trail showing what was authorised and when&lt;/li&gt;
&lt;li&gt;The payment to fail loudly if anything is out of scope — not silently proceed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open banking gives you all of that by default.&lt;/p&gt;

&lt;p&gt;Cards give you none of it by default, and you have to engineer it in.&lt;/p&gt;

&lt;p&gt;The FCA even made it better recently. They removed the 90-day re-authentication requirement that was causing 20-40% customer drop-off for third-party payment providers. Persistent consent — once granted to an agent — can now remain valid without forcing a re-authentication loop.&lt;/p&gt;

&lt;p&gt;That's a massive unlock for agentic payment flows.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Engineering Challenge: Consent Lifecycle for Agents
&lt;/h2&gt;

&lt;p&gt;Here's where it gets genuinely hard.&lt;/p&gt;

&lt;p&gt;When a human completes an open banking payment, the flow is synchronous: they go to the bank, they approve, they come back. Done.&lt;/p&gt;

&lt;p&gt;When an AI agent initiates a payment, the flow might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Book the Bangalore to London flight if the price drops below £600"

Agent: [Monitors prices for 3 days]
Agent: [Price hits £598 on a Wednesday morning]
Agent: [Attempts to initiate payment]
         → But the user's open banking consent was granted for a specific session
         → That session token expired 6 hours ago
         → Payment fails

Result: Agent missed the window. User wakes up to a "couldn't book your flight" message.
        Price is now £640.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consent that's synchronous and session-bound doesn't work for agents that act asynchronously.&lt;/p&gt;

&lt;p&gt;This is the real engineering problem. Not "can AI agents make payments?" — they clearly can. But "what does the consent model look like for an agent that might act hours or days after the user gave permission?"&lt;/p&gt;

&lt;p&gt;There are a few approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Pre-authorised payment mandates&lt;/strong&gt;&lt;br&gt;
Open banking supports Variable Recurring Payments (VRPs) — essentially mandates where the user sets a maximum amount and time window, and the payment provider can initiate within those bounds without re-authentication.&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;// Conceptual structure of a VRP mandate for an agent&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AgentPaymentMandate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxAmountPence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// Agent cannot exceed this&lt;/span&gt;
  &lt;span class="nl"&gt;validUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// Time-bounded consent&lt;/span&gt;
  &lt;span class="nl"&gt;allowedMerchants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// Scope: only these merchants&lt;/span&gt;
  &lt;span class="nl"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// What this mandate is for&lt;/span&gt;
  &lt;span class="nl"&gt;requiresConfirmation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Some actions still need approval&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent operates within a pre-defined envelope. The user sets the boundaries once. The agent acts within them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Payment intent + human gate&lt;/strong&gt;&lt;br&gt;
Agent identifies a payment opportunity, creates a payment intent, notifies the user. User approves in one tap. Agent executes.&lt;/p&gt;

&lt;p&gt;This is the pattern we're building toward at Atoa — merchant describes what they need in natural language, agent proposes the payment, human approves in one tap, open banking rails execute it.&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;// What an agent workflow might look like&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;paymentAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proposePayment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flight-booking-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;59800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// £598.00 in pence&lt;/span&gt;
  &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BLR→LHR flight, price hit target of £600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// 30 min window&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// User gets notified: "Your agent wants to book your flight for £598. Approve?"&lt;/span&gt;
&lt;span class="c1"&gt;// One tap. Payment executes.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 3: Programmatic consent with audit trail&lt;/strong&gt;&lt;br&gt;
For fully autonomous agents — the Visa Intelligent Commerce model — the agent holds delegated credentials scoped to specific actions, with every payment logged against the authorisation that permitted it.&lt;/p&gt;

&lt;p&gt;We're not here yet in open banking. But the architecture exists to get there.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Thinking About at Atoa
&lt;/h2&gt;

&lt;p&gt;We build open banking payment infrastructure. POS terminals, payment links, invoicing, online checkouts. Everything goes through bank payment rails.&lt;/p&gt;

&lt;p&gt;We're thinking about this differently to most — our payment surfaces each have different agent-readiness, and that's shaped how we're approaching the consent problem. When Visa announced Intelligent Commerce, our first question wasn't "can we compete with this?" It was: "which of our surfaces are ready right now, and which ones need the architecture to change?"&lt;/p&gt;

&lt;p&gt;Here's our honest assessment:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pay by Link — probably the most agent-ready thing we have.&lt;/strong&gt; An agent could generate a payment link, send it to a customer, and monitor completion. The consent event is triggered by the link recipient, not the agent. The agent just facilitates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment Pages — also strong.&lt;/strong&gt; A merchant's agent could build and publish a payment page with specific parameters. No card infrastructure needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POS Terminal — hardest.&lt;/strong&gt; The consent flow requires physical presence for SCA. An agent isn't physically present. This one needs new thinking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invoicing — interesting.&lt;/strong&gt; An agent managing a merchant's books could issue invoices and track payment status. The open banking payment confirmation is machine-readable. This is real today.&lt;/p&gt;

&lt;p&gt;The shape of "agentic commerce" looks different for each surface. There's no one-size-fits-all answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Question Every Open Banking Developer Should Be Asking
&lt;/h2&gt;

&lt;p&gt;GoCardless shipping an MCP server tells you something important: &lt;strong&gt;payment infrastructure companies are now thinking about developers' AI workflows as a first-class use case.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not just "can humans use our API?" but "can an AI agent use our API safely?"&lt;/p&gt;

&lt;p&gt;That's a different design question. An API designed for humans assumes there's a human reading error messages, handling edge cases, making judgment calls. An API designed for agents needs those things to be machine-readable, scoped, and predictable.&lt;/p&gt;

&lt;p&gt;Open banking has a head start here. The consent model is explicit. The amounts are bounded. The authorisation chain is auditable. Every payment has a "why" attached to it.&lt;/p&gt;

&lt;p&gt;The engineers who figure out the consent lifecycle problem — how do you grant an agent payment permissions that are time-bounded, amount-bounded, and purpose-bounded, without requiring the human to be present at the moment of execution — will be building the infrastructure that the next decade of agentic commerce runs on.&lt;/p&gt;

&lt;p&gt;That's the problem I'm thinking about.&lt;/p&gt;

&lt;p&gt;What's your take — does the card world catch up to open banking here, or does the consent model give open banking a structural advantage in the agent era?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Arun Rajkumar is CTO &amp;amp; Co-Founder of &lt;a href="https://paywithatoa.co.uk" rel="noopener noreferrer"&gt;Atoa&lt;/a&gt;, an FCA-authorised open banking payments platform in the UK. He writes about payments, fintech engineering, and building for the UK from India. &lt;a href="https://dev.to/mickyarun"&gt;@mickyarun&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>openbanking</category>
      <category>fintech</category>
    </item>
    <item>
      <title>The tech market is tough. Here's what actually matters.</title>
      <dc:creator>Erik Novikov</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:35:39 +0000</pubDate>
      <link>https://dev.to/eriknovikov/the-tech-market-is-tough-heres-what-actually-matters-4ekb</link>
      <guid>https://dev.to/eriknovikov/the-tech-market-is-tough-heres-what-actually-matters-4ekb</guid>
      <description>&lt;h2&gt;
  
  
  The situation
&lt;/h2&gt;

&lt;p&gt;Layoffs, an influx of new devs, and AI getting more capable every month. The market is genuinely harder than it was a few years ago — both at entry level and senior. Supply is up, so the average developer's value is down. That's just supply and demand.&lt;/p&gt;

&lt;p&gt;But the average developer is not you. Here's why it's not as grim as it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Competence still wins
&lt;/h2&gt;

&lt;p&gt;The market isn't homogeneous. Two developers with the same title and years of experience can be worlds apart in actual ability. What separates them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Problem-solving over knowledge accumulation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The developer who knows three frameworks but can't think through an unfamiliar problem is less valuable than the one with strong fundamentals who can adapt fast. Protocols, concurrency, system design, debugging, fixing and shipping under pressure — these transfer across stacks. Knowing by heart a specific library or framework doesn't.&lt;/p&gt;

&lt;p&gt;A concrete example: migrating a microservice from Java to Go. A developer who's never touched Java might freeze. A developer with solid fundamentals in how languages handle concurrency and how services communicate will figure it out. Be the second developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. AI leverage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use AI to plan, brainstorm, review, and code — but stay in the driver's seat. Vibe-coding your way to a shipped feature might look fast until you spend hours debugging a system you never actually understood. AI is a force multiplier, not a replacement for understanding what you're building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Learning rate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In tech, what you know today matters less than how fast you can learn something new and apply it. The half-life of any specific skill is shrinking. Your ability to pick things up quickly is the most durable asset you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final word
&lt;/h2&gt;

&lt;p&gt;Don't stress over the market. Get extremely good instead. Strong fundamentals, smart use of AI, and a fast learning rate will put you ahead of most. Focus on those and the rest takes care of itself.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What Is Browser Fingerprinting? How Websites Track You Without Cookies (2026)</title>
      <dc:creator>Hafiz Khurram Javid</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:34:43 +0000</pubDate>
      <link>https://dev.to/hafiz_khurramjavid_610cf/what-is-browser-fingerprinting-how-websites-track-you-without-cookies-2026-3dfn</link>
      <guid>https://dev.to/hafiz_khurramjavid_610cf/what-is-browser-fingerprinting-how-websites-track-you-without-cookies-2026-3dfn</guid>
      <description>&lt;p&gt;You cleared your cookies. You switched to private browsing. You even tried a VPN. And yet the website still knew it was you.&lt;/p&gt;

&lt;p&gt;Browser fingerprinting is why - and it works by reading signals from your device that you cannot delete, clear, or turn off.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Browser Fingerprinting?
&lt;/h2&gt;

&lt;p&gt;Browser fingerprinting identifies you by combining technical details about your browser and device into a unique profile. Your &lt;strong&gt;GPU model, installed fonts, screen resolution, audio hardware, timezone, and language settings&lt;/strong&gt; are all read silently - no permission prompt, no cookie banner, no storage on your device.&lt;/p&gt;

&lt;p&gt;Each individual signal is not unique. Millions of people have a 1920×1080 screen. Millions use Chrome on Windows. But when you combine 13 or more signals together, the resulting fingerprint is statistically unique for &lt;strong&gt;83–90% of users&lt;/strong&gt;, according to research from AmIUnique.org and the EFF's Panopticlick project.&lt;/p&gt;

&lt;p&gt;Unlike cookies, fingerprinting leaves no trace on your device. There is nothing to clear, nothing to block with standard privacy settings, and nothing that resets when you close your browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cookies vs. Fingerprinting: The Key Difference
&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;Cookies&lt;/th&gt;
&lt;th&gt;Fingerprinting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stored on your device?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you delete it?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocked by incognito?&lt;/td&gt;
&lt;td&gt;Partially&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocked by cookie banners?&lt;/td&gt;
&lt;td&gt;Yes (if compliant)&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires GDPR consent?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes - but often ignored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Survives browser reset?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The 13 Signals That Make Up Your Fingerprint
&lt;/h2&gt;

&lt;p&gt;Here are the main vectors websites use, ranked by how much identifying information each contributes (measured in entropy bits):&lt;/p&gt;

&lt;h3&gt;
  
  
  🔖 User Agent String - 10.5 bits (High)
&lt;/h3&gt;

&lt;p&gt;Your browser's identity card. Reports your exact browser name, version, OS, and CPU architecture. A single string that narrows you to a very small group.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎨 Canvas Fingerprint - 8.5 bits (High)
&lt;/h3&gt;

&lt;p&gt;The website draws invisible shapes on a hidden canvas element and reads back the pixel data. Your GPU and OS render these slightly differently, creating a hash unique to your hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔤 Installed Fonts - 7.5 bits (High)
&lt;/h3&gt;

&lt;p&gt;The exact set of fonts on your system is surprisingly unique. Design tools, games, and work applications all install custom fonts. The combination is often one-of-a-kind.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔺 WebGL / GPU Renderer - 7.2 bits (High)
&lt;/h3&gt;

&lt;p&gt;WebGL exposes your exact GPU model and driver version to every website without any permission required. Your graphics card is effectively signing every page you visit.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔊 Audio Fingerprint - 5.4 bits (High)
&lt;/h3&gt;

&lt;p&gt;A silent tone is processed through your audio hardware using the Web Audio API. The tiny differences in how your chip handles it create a unique signature - completely invisible and inaudible.&lt;/p&gt;

&lt;h3&gt;
  
  
  📐 Screen Resolution - 4.8 bits (Medium)
&lt;/h3&gt;

&lt;p&gt;Your screen width, height, color depth, and device pixel ratio. Multi-monitor and high-DPI configurations are especially distinctive.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌍 Browser Language - 4.2 bits (Medium)
&lt;/h3&gt;

&lt;p&gt;The languages your browser is configured to use. Multilingual users with uncommon language pairs can be nearly uniquely identified from this single vector.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Hardware Profile - 3.1 bits (Medium)
&lt;/h3&gt;

&lt;p&gt;CPU core count and RAM. Combined with other signals, this narrows your device to a small group.&lt;/p&gt;

&lt;h3&gt;
  
  
  🕐 Timezone - 3.8 bits (Medium)
&lt;/h3&gt;

&lt;p&gt;Your browser reports your real timezone even behind a VPN. A mismatch between your IP geolocation and timezone is a classic VPN detection signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Websites Actually Use Your Fingerprint
&lt;/h2&gt;

&lt;p&gt;The most common uses are &lt;strong&gt;ad tracking&lt;/strong&gt;, &lt;strong&gt;fraud detection&lt;/strong&gt;, and &lt;strong&gt;paywall enforcement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ad networks build profiles of your browsing behavior across thousands of sites and use fingerprinting to link those sessions together even when you clear cookies. Banks use fingerprinting as a fraud signal - a sudden change in fingerprint triggers verification challenges.&lt;/p&gt;

&lt;p&gt;News sites and streaming platforms use fingerprinting to enforce article limits and free trial periods. Clearing cookies resets the counter; fingerprinting does not.&lt;/p&gt;

&lt;p&gt;More troublingly, data brokers purchase fingerprint-linked browsing profiles and combine them with offline data. A 2025 investigation found that some brokers could link anonymous browsing sessions to real names and addresses through fingerprint data alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is Browser Fingerprinting Legal?
&lt;/h2&gt;

&lt;p&gt;Under &lt;strong&gt;GDPR&lt;/strong&gt;, fingerprinting constitutes processing of personal data because it creates a unique identifier. This means it requires a lawful basis - almost always consent - and must be disclosed. In practice, most websites outside the EU fingerprint without consent.&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;CCPA and CPRA&lt;/strong&gt;, browser fingerprints qualify as unique personal identifiers, giving California residents the right to opt out of their sale. Most US state privacy laws passed since 2023 include similar provisions.&lt;/p&gt;

&lt;p&gt;The EU's ePrivacy Directive specifically covers fingerprinting and requires consent, but enforcement has been inconsistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Reduce Your Browser Fingerprint
&lt;/h2&gt;

&lt;h3&gt;
  
  
  → Switch to Brave Browser &lt;em&gt;(highest impact)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Brave actively randomizes canvas, WebGL, audio, and font fingerprinting vectors on every page load. The only mainstream browser with built-in fingerprint randomization that changes per session.&lt;/p&gt;

&lt;h3&gt;
  
  
  → Use the Tor Browser &lt;em&gt;(maximum anonymity)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Tor makes every user appear identical by standardizing all fingerprinting vectors. Trade-off: significantly slower browsing.&lt;/p&gt;

&lt;h3&gt;
  
  
  → Firefox with Strict Mode &lt;em&gt;(good balance)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Firefox's Enhanced Tracking Protection in strict mode blocks known fingerprinting scripts and restricts font enumeration.&lt;/p&gt;

&lt;h3&gt;
  
  
  → Install CanvasBlocker &lt;em&gt;(partial)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;A Firefox extension that randomizes canvas fingerprinting output. Effective against canvas tracking but leaves WebGL and audio untouched.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Paradox of Anti-Fingerprinting
&lt;/h2&gt;

&lt;p&gt;There is a cruel irony at the heart of fingerprinting defense: some protective measures can make you &lt;em&gt;more&lt;/em&gt; distinctive.&lt;/p&gt;

&lt;p&gt;Enabling Do Not Track, for example, is set by only about 12% of users - meaning having it on is itself a fingerprinting signal. Using an uncommon browser or heavily customizing privacy settings can make your fingerprint more unique rather than less.&lt;/p&gt;

&lt;p&gt;The most effective defense is not customization but standardization - using browsers designed to make all users appear identical, like Tor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test Your Own Fingerprint
&lt;/h2&gt;

&lt;p&gt;The best way to understand your exposure is to see it directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://trustscan.dev/tools/browser-fingerprint-analyzer" rel="noopener noreferrer"&gt;TrustScan's Browser Fingerprint Analyzer&lt;/a&gt;&lt;/strong&gt; runs 13 tracking vectors against your browser, calculates your entropy score, and shows exactly which signals are most identifying - with specific reduction steps.&lt;/p&gt;

&lt;p&gt;It runs entirely in your browser. Nothing is collected or transmitted. Free, no account required.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Browser fingerprinting is the tracking technique that works after everything else fails. It does not use cookies. It cannot be cleared by standard browser settings. It is not defeated by a VPN alone. And it is used by thousands of advertising networks and data brokers right now.&lt;/p&gt;

&lt;p&gt;Understanding what it is - and testing your own exposure - is the first step to making informed decisions about your browser and your privacy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trustscan.dev/blog/what-is-browser-fingerprinting-2026" rel="noopener noreferrer"&gt;trustscan.dev&lt;/a&gt;. TrustScan is a free privacy and security toolkit - 100% client-side, no data collected.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>webdev</category>
      <category>browsers</category>
    </item>
    <item>
      <title>Quantum computing para el dev web que no estudió física: ¿cuándo preocuparse en serio?</title>
      <dc:creator>Juan Torchia</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:32:37 +0000</pubDate>
      <link>https://dev.to/jtorchia/quantum-computing-para-el-dev-web-que-no-estudio-fisica-cuando-preocuparse-en-serio-3ea4</link>
      <guid>https://dev.to/jtorchia/quantum-computing-para-el-dev-web-que-no-estudio-fisica-cuando-preocuparse-en-serio-3ea4</guid>
      <description>&lt;p&gt;Hay días que abrís Hacker News y encontrás un post que te hace sentir que sabés muy poco de algo que creías tener medianamente claro. Esta semana fue uno de esos días.&lt;/p&gt;

&lt;p&gt;Un criptógrafo cuántico posteó un análisis técnico sobre el estado actual del quantum computing aplicado a criptografía. 289 puntos. 340 comentarios. Yo entendí quizás el 30% del hilo. Y eso que llevo más de una hora intentando seguirlo.&lt;/p&gt;

&lt;p&gt;La pregunta que me quedó dando vueltas no es abstracta: &lt;strong&gt;¿cuándo debería yo — un full-stack developer que deployea en Railway, piensa en milisegundos de response time y la semana pasada optimizó &lt;a href="https://dev.to/blog/optimizacion-performance-nextjs-3s-a-300ms"&gt;una app Next.js de 3 segundos a 300ms&lt;/a&gt; — empezar a preocuparme en términos prácticos?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No sé la respuesta. Pero eso es exactamente por qué vale la pena escribir este post.&lt;/p&gt;




&lt;p&gt;El quantum computing es básicamente como tener un cerrajero que no intenta cada llave una por una, sino que de alguna manera prueba todas las llaves posibles al mismo tiempo. Y lo que hoy te lleva millones de años romper con fuerza bruta, con esa lógica podría llevar horas.&lt;/p&gt;

&lt;p&gt;Una vez que lo ves así, entendés por qué los criptógrafos se ponen nerviosos.&lt;/p&gt;

&lt;h2&gt;
  
  
  El quantum computing timeline para desarrolladores web: el estado real en 2025
&lt;/h2&gt;

&lt;p&gt;Primer aclaramiento importante: &lt;strong&gt;no estoy hablando de algo que pasa mañana&lt;/strong&gt;. El quantum computing que existe hoy es ruidoso, inestable, y no escala bien. Las máquinas de IBM o Google tienen decenas o cientos de qubits "reales" pero con tasas de error que hacen que la mayoría de los algoritmos criptográficamente relevantes sean imposibles de correr.&lt;/p&gt;

&lt;p&gt;Para romper RSA-2048 — el estándar que protege buena parte de HTTPS hoy — necesitarías aproximadamente 4000 qubits lógicos estables. Los qubits lógicos son distintos de los físicos; necesitás muchos físicos para hacer uno lógico confiable por culpa del error de corrección.&lt;/p&gt;

&lt;p&gt;Hoy estamos, dependiendo de a quién le preguntés, &lt;strong&gt;entre 10 y 20 años lejos&lt;/strong&gt; de tener máquinas con esa capacidad. Algunos dicen 15 años. Otros dicen que nunca llegamos. Otros dicen que ya hay actores estado-nación haciendo cosas que no vemos.&lt;/p&gt;

&lt;p&gt;Ese rango de incertidumbre es exactamente el problema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lo que el post de HN me hizo entender
&lt;/h3&gt;

&lt;p&gt;El hilo que mencioné giraba alrededor de algo que se llama &lt;strong&gt;"harvest now, decrypt later"&lt;/strong&gt; (HNDL). La idea es: aunque hoy no podés romper el cifrado, podés interceptar tráfico cifrado &lt;em&gt;ahora&lt;/em&gt; y guardarlo para cuando tengas la capacidad cuántica de descifrarlo en el futuro.&lt;/p&gt;

&lt;p&gt;Eso cambia la ecuación dramáticamente. Si alguien está capturando tu tráfico HTTPS de 2025 para descifrarlo en 2035, el timeline "10 a 20 años" se convierte en &lt;strong&gt;ahora mismo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;¿Quiénes hacen eso? Principalmente actores estado-nación. ¿Le importa eso a mi API de Next.js que muestra recetas de cocina? Probablemente no. ¿Le importa a un sistema de salud, a comunicaciones de defensa, a transacciones financieras de largo plazo? Absolutamente sí.&lt;/p&gt;

&lt;p&gt;Así que la primera respuesta práctica es: &lt;strong&gt;depende de qué estás construyendo&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que como developer full-stack debería saber sobre post-quantum cryptography
&lt;/h2&gt;

&lt;p&gt;Aquí está lo que aprendí intentando entender el hilo sin tener un PhD en física:&lt;/p&gt;

&lt;h3&gt;
  
  
  NIST ya tomó decisiones
&lt;/h3&gt;

&lt;p&gt;En 2024, el &lt;strong&gt;NIST (National Institute of Standards and Technology)&lt;/strong&gt; finalizó los primeros estándares de criptografía post-cuántica. Los algoritmos que pasaron son:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ML-KEM&lt;/strong&gt; (antes CRYSTALS-Kyber): para intercambio de claves&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML-DSA&lt;/strong&gt; (antes CRYSTALS-Dilithium): para firmas digitales
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SLH-DSA&lt;/strong&gt; (antes SPHINCS+): para firmas digitales&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esto es importante porque significa que el trabajo de estandarización ya pasó. No estamos esperando que los matemáticos se pongan de acuerdo — ya lo hicieron.&lt;/p&gt;

&lt;h3&gt;
  
  
  TLS 1.3 ya está preparándose
&lt;/h3&gt;

&lt;p&gt;Chrome, Firefox y algunos servidores ya están experimentando con &lt;strong&gt;hybrid key exchange&lt;/strong&gt; — combinan el algoritmo clásico (X25519) con uno post-cuántico (ML-KEM) en el mismo handshake. Si uno falla, el otro sigue funcionando. Si el cuántico resulta tener vulnerabilidades no descubiertas todavía, el clásico te cubre.&lt;/p&gt;

&lt;p&gt;Como dev web, probablemente esto te llegue transparentemente vía updates de OpenSSL, nginx, o Node.js. No tenés que hacer nada... todavía.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lo que SÍ tenés que pensar activamente
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Esto es lo que MUCHOS proyectos hacen hoy&lt;/span&gt;
&lt;span class="c1"&gt;// y que puede ser problemático en un horizonte post-cuántico&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Algoritmos que eventualmente van a ser vulnerables&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RS256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// RSA&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publicEncrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rsaPublicKey&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="c1"&gt;// RSA&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Algoritmos simétricos — estos están relativamente bien&lt;/span&gt;
&lt;span class="c1"&gt;// AES-256 sigue siendo seguro en un mundo cuántico&lt;/span&gt;
&lt;span class="c1"&gt;// (Grover's algorithm lo debilita pero no lo rompe — 256 bits -&amp;gt; 128 bits efectivos)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&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="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// OK por ahora&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aes-256-gcm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Más seguro&lt;/span&gt;

&lt;span class="c1"&gt;// 🤔 La pregunta real: ¿tus secrets/tokens tienen que durar décadas?&lt;/span&gt;
&lt;span class="c1"&gt;// Si un JWT expira en 1 hora, el riesgo post-cuántico es casi nulo&lt;/span&gt;
&lt;span class="c1"&gt;// Si estás firmando contratos que tienen que ser válidos en 2040...&lt;/span&gt;
&lt;span class="c1"&gt;// ahí sí hay que pensar distinto&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La clave práctica que saqué: &lt;strong&gt;el riesgo escala con el tiempo de vida de lo que firmás o cifrás&lt;/strong&gt;. Un access token de 15 minutos y un certificado de firma de documentos legales tienen riesgos completamente distintos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Los errores de framing más comunes cuando leés sobre quantum computing
&lt;/h2&gt;

&lt;p&gt;Acá está lo que me parece que la mayoría de los posts de divulgación hacen mal — incluyendo probablemente este:&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 1: Confundir "quantum advantage" con "quantum supremacy" con "cryptographically relevant"
&lt;/h3&gt;

&lt;p&gt;Cuando Google o IBM anuncian un hito cuántico, los medios lo presentan como "ya pueden romper el cifrado". Casi nunca es eso. Quantum advantage significa que resolvieron &lt;em&gt;algún problema específico&lt;/em&gt; más rápido que una computadora clásica. Ese problema específico suele ser artificioso y diseñado para que la computadora cuántica brille.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cryptographically relevant&lt;/strong&gt; quantum computing — el que realmente importa — es una barra mucho más alta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 2: Pensar que bcrypt o Argon2 están muertos
&lt;/h3&gt;

&lt;p&gt;El hashing de passwords como bcrypt, scrypt o Argon2 usa funciones hash simétricas. El algoritmo de Grover (el que aplica computación cuántica a búsqueda) efectivamente reduce su seguridad a la mitad — pero Argon2 con parámetros modernos tiene margen de sobra. &lt;strong&gt;No necesitás cambiar tu sistema de autenticación ahora mismo.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 3: Ignorarlo completamente porque "falta mucho"
&lt;/h3&gt;

&lt;p&gt;Este es el error que me parece más peligroso para devs que construyen infraestructura de largo plazo. Si estás construyendo algo que va a manejar datos sensibles por décadas, &lt;strong&gt;el timeline importa&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pensá en todo lo que construí durante &lt;a href="https://dev.to/blog/de-dos-a-cloud-mi-viaje-33-anos-1775496952619"&gt;el pivote a software development en 2020&lt;/a&gt; — la infraestructura que elegís hoy puede seguir corriendo en producción en 2035. Cuando elegís un stack de autenticación o cifrado hoy, estás eligiendo para ese horizonte de tiempo también.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error 4: Pensar que esto es solo un problema del devops o del sysadmin
&lt;/h3&gt;

&lt;p&gt;En el ecosistema de 2025 que describí cuando &lt;a href="https://dev.to/blog/como-construi-juanchi-dev"&gt;armé mi stack para juanchi.dev&lt;/a&gt;, los developers full-stack tomamos decisiones de arquitectura que antes eran de ops. Eso incluye qué librería de crypto usás, cómo firmás tokens, qué tipo de certificados pedís.&lt;/p&gt;

&lt;p&gt;No podés delegarlo completamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Código: qué revisar en tu proyecto hoy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Auditoría rápida de superficie de ataque post-cuántica&lt;/span&gt;
&lt;span class="c1"&gt;// Revisá estos patrones en tu codebase&lt;/span&gt;

&lt;span class="c1"&gt;// 1. ALGORITMOS ASIMÉTRICOS — los más vulnerables&lt;/span&gt;
&lt;span class="c1"&gt;// Buscá: RSA, ECDH, ECDSA, DH&lt;/span&gt;
&lt;span class="c1"&gt;// ¿Dónde aparecen?&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;generateKeyPairSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createSign&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;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// ❓ RSA — vulnerable a Shor's algorithm&lt;/span&gt;
&lt;span class="c1"&gt;// Si estos datos tienen que ser válidos en 2035+, pensalo&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateKeyPairSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rsa&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="na"&gt;modulusLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Esto eventualmente no va a ser suficiente&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// ❓ ECDSA — también vulnerable, aunque más eficiente hoy&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ecKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateKeyPairSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ec&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="na"&gt;namedCurve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prime256v1&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="c1"&gt;// 2. ALGORITMOS SIMÉTRICOS — relativamente OK&lt;/span&gt;
&lt;span class="c1"&gt;// AES-256, ChaCha20-Poly1305, SHA-256/384/512&lt;/span&gt;
&lt;span class="c1"&gt;// Estos necesitan el doble de bits para ser vulnerables (Grover)&lt;/span&gt;
&lt;span class="c1"&gt;// pero con 256 bits tenés colchón de sobra&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;randomBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createDecipheriv&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;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cifrarDatosLocales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;datos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clave&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// AES-256-GCM — esto sigue siendo seguro post-quantum&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&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;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aes-256-gcm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&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;cifrado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;datos&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthTag&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// El tag de autenticación es importante&lt;/span&gt;
  &lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cifrado&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 3. JWT — el caso práctico más común&lt;/span&gt;
&lt;span class="c1"&gt;// La respuesta corta: si expira en horas, no te preocupés&lt;/span&gt;
&lt;span class="c1"&gt;// Si firmás algo permanente con JWT... cuestioná el diseño&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evaluarRiesgoJWT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expiresInSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;años&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;expiresInSeconds&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;365&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;años&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Riesgo post-cuántico prácticamente nulo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;años&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Riesgo bajo, monitoreá el timeline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;años&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Riesgo moderado, considerá migración&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Riesgo alto — rediseñá este componente&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;evaluarRiesgoJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// "Riesgo post-cuántico prácticamente nulo"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;evaluarRiesgoJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// "Riesgo moderado..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La función &lt;code&gt;evaluarRiesgoJWT&lt;/code&gt; es una simplificación obvia, pero captura el punto central: &lt;strong&gt;el tiempo de vida de lo que firmás es la variable más importante&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cuando definís los &lt;a href="https://dev.to/blog/typescript-patrones-avanzados-que-uso"&gt;patrones de TypeScript que usás en producción&lt;/a&gt;, incluir tipos explícitos para el contexto de seguridad — cuánto tiempo vive un token, qué nivel de sensibilidad tienen los datos — es el tipo de diseño que ayuda cuando tenés que hacer una auditoría de esto en el futuro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué hacer concretamente hoy (sin entrar en pánico)
&lt;/h2&gt;

&lt;p&gt;Esta es mi lista personal, honesta, sin exagerar el riesgo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ahora mismo (sin importar el tipo de proyecto):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usá TLS 1.3 — ya implementa algunas mejoras de seguridad y va a recibir actualizaciones post-cuánticas&lt;/li&gt;
&lt;li&gt;Preferí AES-256 sobre AES-128 para cifrado simétrico&lt;/li&gt;
&lt;li&gt;Mantenés las dependencias actualizadas — la migración post-cuántica va a llegar vía updates de librerías&lt;/li&gt;
&lt;li&gt;No implementés crypto propio — en serio, nunca, quantum o no quantum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;En los próximos 1-2 años (si manejás datos sensibles de largo plazo):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inventariá qué en tu sistema usa criptografía asimétrica y cuánto tiempo tienen que vivir esos datos&lt;/li&gt;
&lt;li&gt;Empezá a leer sobre las librerías que van a adoptar los algoritmos NIST — Open Quantum Safe ya tiene implementaciones&lt;/li&gt;
&lt;li&gt;Considerá diseñar para "cripto-agilidad": que tu sistema pueda cambiar de algoritmo sin reescribir todo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Para el &lt;a href="https://dev.to/blog/stack-tecnologico-perfecto-2025"&gt;stack que elegiría en 2025&lt;/a&gt;:&lt;/strong&gt;&lt;br&gt;
Hoy elegiría librerías activamente mantenidas por organizaciones que ya tienen planes de migración post-cuántica documentados. Node.js y OpenSSL están en ese camino. Es un criterio más para sumar a la evaluación.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ: quantum computing y desarrollo web
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;¿HTTPS va a dejar de ser seguro por el quantum computing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No en el corto plazo, y probablemente no de golpe. TLS ya está siendo actualizado con algoritmos post-cuánticos (hybrid key exchange en TLS 1.3). El browser que usás hoy ya está recibiendo estas actualizaciones gradualmente. Lo que sí es una amenaza real es el ataque "harvest now, decrypt later" para datos muy sensibles — pero eso aplica a actores estado-nación, no al tráfico web promedio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Tengo que cambiar el sistema de login/passwords de mi app?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No urgentemente. bcrypt, Argon2, y scrypt usan funciones hash simétricas que son mucho más resistentes a quantum computing que los algoritmos asimétricos. Argon2id con parámetros modernos tiene margen de seguridad suficiente. La recomendación es seguir con las mejores prácticas actuales y mantenerte actualizado.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Los JWTs quedan obsoletos?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Depende de cómo los usés. Si firmás access tokens que expiran en 15 minutos o 1 hora, el riesgo es prácticamente nulo — para cuando exista quantum computing relevante, esos tokens llevan años vencidos. Si usás JWTs para firmar algo que tiene que ser válido por años (documentos, contratos), ahí sí hay que empezar a pensar en alternativas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Qué es "post-quantum cryptography" y en qué se diferencia de "quantum cryptography"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Post-quantum cryptography (PQC) son algoritmos clásicos — corren en computadoras normales — diseñados para ser resistentes a ataques de computadoras cuánticas. Quantum cryptography (QKD, quantum key distribution) usa principios cuánticos para la comunicación en sí. Como dev web, lo que te importa es PQC — es lo que vas a implementar en tu stack. QKD requiere hardware especializado y es un campo completamente distinto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿Cuándo debería empezar a usar las librerías post-cuánticas en producción?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para la mayoría de las aplicaciones web: cuando lleguen via updates de tus dependencias actuales, que es probablemente lo que va a pasar. Node.js, OpenSSL y los providers de TLS van a implementar los estándares NIST gradualmente. Si manejás datos críticos de largo plazo, vale la pena explorar Open Quantum Safe hoy y empezar a hacer pruebas. Para el resto, mantenete actualizado y no entres en pánico.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¿El quantum computing afecta a blockchain y crypto también?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sí, y bastante. Las criptomonedas usan ECDSA para firmar transacciones — vulnerable a Shor's algorithm. Bitcoin y Ethereum tendrían que migrar sus sistemas de firma antes de que quantum computing sea relevante. Es uno de los debates más activos en esas comunidades. Como dato de color: las wallets activas están más expuestas que las inactivas, porque las activas exponen la clave pública en cada transacción.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusión: la ignorancia honesta como posición válida
&lt;/h2&gt;

&lt;p&gt;Cuando empecé a escribir este post no sabía bien qué iba a concluir. Sigo sin saber si en 10 años estamos re-cifrando todo el internet o si el quantum computing sigue siendo una promesa que no termina de llegar.&lt;/p&gt;

&lt;p&gt;Lo que sí saqué en limpio:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;El timeline importa más que el tema&lt;/strong&gt;. No es un problema binario de "hay que preocuparse" o "no hay que preocuparse". Es una función del tiempo de vida de tus datos y la sensibilidad de los mismos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La industria ya se está moviendo&lt;/strong&gt;. NIST finalizó estándares. TLS ya está experimentando con algoritmos híbridos. No vas a tener que hacer todo a mano — va a llegar via ecosistema.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La cripto-agilidad es la mejor inversión&lt;/strong&gt;. Diseñar tus sistemas para que puedan cambiar de algoritmo sin reescritura total es buena práctica con o sin quantum computing. La historia de la criptografía es la historia de algoritmos que se rompen y se reemplazan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Para la mayoría de los proyectos: mantenete actualizado y no entres en pánico&lt;/strong&gt;. Si tu app maneja credenciales de usuarios que expiran, tokens de sesión normales, y datos que no tienen que ser válidos por décadas — el riesgo hoy es bajo. Aplicá las mejores prácticas actuales.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Lo que me queda pendiente es seguir leyendo. El hilo de HN que disparó esto tenía respuestas de gente con décadas de experiencia en criptografía que no se ponía de acuerdo. Eso me dice que la humildad epistémica es la postura correcta.&lt;/p&gt;

&lt;p&gt;Si sos el tipo de developer que, como yo, viene de diagnosticar redes a las 11pm en un cyber o de tirar un servidor de producción con &lt;code&gt;rm -rf&lt;/code&gt; en la primera semana de trabajo — sabés que la mejor preparación para los problemas grandes no es entrar en pánico cuando aparecen, sino construir sistemas que puedan adaptarse.&lt;/p&gt;

&lt;p&gt;Eso aplica acá también.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Este artículo fue publicado originalmente en &lt;a href="https://juanchi.dev/blog/quantum-computing-timeline-desarrolladores-web" rel="noopener noreferrer"&gt;juanchi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>fullstack</category>
      <category>quantumcomputing</category>
      <category>criptografa</category>
    </item>
    <item>
      <title>What Happens If You Build Your Own Trading Bot? I Tried It</title>
      <dc:creator>AdarshGzz...</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:30:27 +0000</pubDate>
      <link>https://dev.to/adarshgzz/what-happens-if-you-build-your-own-trading-bot-i-tried-it-3ho9</link>
      <guid>https://dev.to/adarshgzz/what-happens-if-you-build-your-own-trading-bot-i-tried-it-3ho9</guid>
      <description>&lt;p&gt;I Built a Real-Time Paper Trading Bot (To Understand How Trading Systems Work and can i automate it) and it took me 3 weeks.&lt;/p&gt;

&lt;p&gt;so recently i wanted to understand how trading systems actually work behind the scenes&lt;/p&gt;

&lt;p&gt;not just charts… but like:&lt;br&gt;
    • how data comes in&lt;br&gt;
    • how decisions are made&lt;br&gt;
    • how trades are executed&lt;/p&gt;

&lt;p&gt;so i ended up building a paper trading bot for BTC/USDT&lt;/p&gt;

&lt;p&gt;(no real money involved 😅)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;what it does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;it connects to Binance WebSocket and gets live market data&lt;/p&gt;

&lt;p&gt;then on every 5-minute candle, it decides:&lt;br&gt;
    • go LONG&lt;br&gt;
    • go SHORT&lt;br&gt;
    • or do nothing&lt;/p&gt;

&lt;p&gt;based on a simple strategy&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;the strategy *&lt;/em&gt;(kept it simple)&lt;/p&gt;

&lt;p&gt;i didn’t want anything too complex&lt;/p&gt;

&lt;p&gt;so i used:&lt;br&gt;
    • EMA (50) → trend&lt;br&gt;
    • RSI (14) → momentum&lt;/p&gt;

&lt;p&gt;entry logic:&lt;br&gt;
    • LONG → price above EMA + RSI &amp;gt; 50 + green candle&lt;br&gt;
    • SHORT → price below EMA + RSI &amp;lt; 50 + red candle&lt;/p&gt;

&lt;p&gt;entry happens exactly on candle close&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;risk management&lt;/strong&gt; (most important part)&lt;/p&gt;

&lt;p&gt;this was actually interesting to implement&lt;br&gt;
    • only 1% risk per trade&lt;br&gt;
    • stop loss based on structure (swing high/low)&lt;br&gt;
    • target = 1.5x risk&lt;br&gt;
    • auto stop trading if:&lt;br&gt;
    • -3% loss&lt;br&gt;
    • +5% profit&lt;/p&gt;

&lt;p&gt;also restricted trading hours to avoid random moves&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tech i used&lt;/strong&gt;&lt;br&gt;
    • Node.js → backend&lt;br&gt;
    • WebSockets → real-time data&lt;br&gt;
    • PostgreSQL (Neon) → store trades + candles&lt;br&gt;
    • React → dashboard&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;what i learned&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;this project was less about trading and more about systems&lt;br&gt;
    • handling real-time streams is tricky&lt;br&gt;
    • small delays can affect decisions&lt;br&gt;
    • syncing backend + frontend in real time is not easy&lt;br&gt;
    • logic looks simple but edge cases are everywhere&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;live project&lt;/strong&gt;&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%2Fhfyzm8gs8f0clsnqg166.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%2Fhfyzm8gs8f0clsnqg166.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;if you want to see it:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://paper-trader-drab.vercel.app/" rel="noopener noreferrer"&gt;https://paper-trader-drab.vercel.app/&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/AdarshGzz/Paper-Trader" rel="noopener noreferrer"&gt;https://github.com/AdarshGzz/Paper-Trader&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;final thought&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;it’s still a simple bot and not something for real trading&lt;/p&gt;

&lt;p&gt;but building it gave me a better idea of:&lt;br&gt;
    • how trading engines work&lt;br&gt;
    • how data flows in real-time systems&lt;/p&gt;

&lt;p&gt;planning to improve it more (maybe better strategies or analytics)&lt;/p&gt;

&lt;p&gt;if you’ve built something similar or have ideas, would love to hear 👍&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>opensource</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Hello Noir! [Part 2]</title>
      <dc:creator>0xluk3</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:29:00 +0000</pubDate>
      <link>https://dev.to/0xluk3/hello-noir-part-2-36l7</link>
      <guid>https://dev.to/0xluk3/hello-noir-part-2-36l7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In &lt;a href="https://dev.to/0xluk3/hello-noir-part-1-4m18"&gt;Part 1&lt;/a&gt; we wrote a circuit, compiled it with nargo, and got two artifacts: a circuit definition and a witness. Now we will use them to actually perform the cryptographic proof check.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Where we left off
&lt;/h2&gt;

&lt;p&gt;At the end of Part 1, we had two files in &lt;code&gt;target/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hello_world.json&lt;/code&gt;&lt;/strong&gt; - the compiled circuit (ACIR bytecode)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hello_world.gz&lt;/code&gt;&lt;/strong&gt; - the witness (our specific input values that satisfy the constraints)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All nargo told us was "yes, these inputs work." That's not a proof anyone else can verify - it's just a local check. To produce an actual cryptographic proof, we need Barretenberg (&lt;code&gt;bb&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Generating a proof with Barretenberg
&lt;/h2&gt;

&lt;p&gt;With our &lt;code&gt;Prover.toml&lt;/code&gt; set to &lt;code&gt;x = "2"&lt;/code&gt; and &lt;code&gt;y = "1"&lt;/code&gt; (recall: &lt;code&gt;x&lt;/code&gt; is private, &lt;code&gt;y&lt;/code&gt; is public), we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bb prove &lt;span class="nt"&gt;-b&lt;/span&gt; ./target/hello_world.json &lt;span class="nt"&gt;-w&lt;/span&gt; ./target/hello_world.gz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--write_vk&lt;/span&gt; &lt;span class="nt"&gt;--verifier_target&lt;/span&gt; evm &lt;span class="nt"&gt;-o&lt;/span&gt; ./target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scheme is: ultra_honk, num threads: 8 (mem: 8.10 MiB)
CircuitProve: Proving key computed in 29 ms (mem: 24.21 MiB)
Public inputs saved to "./target/public_inputs" (mem: 28.56 MiB)
Proof saved to "./target/proof" (mem: 28.56 MiB)
VK saved to "./target/vk" (mem: 28.56 MiB)
VK Hash saved to "./target/vk_hash" (mem: 28.56 MiB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-b&lt;/code&gt; - path to the compiled circuit (ACIR bytecode)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-w&lt;/code&gt; - path to the witness&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--write_vk&lt;/code&gt; - also generate the verification key alongside the proof&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--verifier_target evm&lt;/code&gt; - target the EVM. This does two things: uses Keccak256 (the EVM has a dedicated opcode for it, making verification gas-efficient) and enables the zero-knowledge property (the proof reveals nothing about private inputs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-o&lt;/code&gt; - output directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This produced four files in &lt;code&gt;target/&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proof&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7,488 bytes&lt;/td&gt;
&lt;td&gt;The cryptographic proof&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,888 bytes&lt;/td&gt;
&lt;td&gt;Verification key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vk_hash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 bytes&lt;/td&gt;
&lt;td&gt;Hash of the vk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;public_inputs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 bytes&lt;/td&gt;
&lt;td&gt;The public inputs (y = 1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What is the verification key?&lt;/strong&gt; It's derived from the circuit structure alone - not from your inputs. It encodes the circuit's "shape": how many gates, what type, how they're wired. Anyone can use it to verify proofs for this circuit. Think of it as the circuit's public fingerprint. The same vk works for any valid proof of this circuit, regardless of what specific values of &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; were used.&lt;/p&gt;

&lt;p&gt;One detail worth noting: &lt;code&gt;bb&lt;/code&gt; prepends the public inputs to the proof blob. The verifier contract knows to extract them from there.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Verifying the proof
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bb verify &lt;span class="nt"&gt;-p&lt;/span&gt; ./target/proof &lt;span class="nt"&gt;-k&lt;/span&gt; ./target/vk &lt;span class="nt"&gt;--verifier_target&lt;/span&gt; evm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scheme is: ultra_honk, num threads: 8 (mem: 8.11 MiB)
Proof verified successfully (mem: 18.36 MiB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what we did NOT pass: the witness. That would defeat the purpose, wouldn't it? The whole point is that the verifier never sees your private inputs. All it needs is the proof and the verification key.&lt;/p&gt;

&lt;p&gt;The proof says: "someone knows a value &lt;code&gt;x&lt;/code&gt; such that &lt;code&gt;x != y&lt;/code&gt;, where &lt;code&gt;y = 1&lt;/code&gt;." It doesn't say what &lt;code&gt;x&lt;/code&gt; is.&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%2Fmxqxe42seafldb5ypoq5.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%2Fmxqxe42seafldb5ypoq5.png" alt="Proving Flow" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Generating a Solidity verifier
&lt;/h2&gt;

&lt;p&gt;Now we want this verification to happen on-chain. &lt;a href="https://barretenberg.aztec.network/docs/getting_started/" rel="noopener noreferrer"&gt;&lt;code&gt;bb&lt;/code&gt;&lt;/a&gt; can generate a Solidity contract that does exactly the same check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bb write_solidity_verifier &lt;span class="nt"&gt;-k&lt;/span&gt; ./target/vk &lt;span class="nt"&gt;--verifier_target&lt;/span&gt; evm &lt;span class="nt"&gt;-o&lt;/span&gt; ./target/Verifier.sol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scheme is: ultra_honk, num threads: 8 (mem: 8.75 MiB)
ZK Honk solidity verifier saved to "./target/Verifier.sol" (mem: 9.87 MiB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is a 2,449-line Solidity file. What's inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;HonkVerifier&lt;/code&gt; contract that inherits from &lt;code&gt;BaseZKHonkVerifier&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The verification key hardcoded as constants (circuit size, elliptic curve points, etc.)&lt;/li&gt;
&lt;li&gt;Pairing check logic using EVM precompiles (&lt;code&gt;ecAdd&lt;/code&gt;, &lt;code&gt;ecMul&lt;/code&gt;, &lt;code&gt;ecPairing&lt;/code&gt;, &lt;code&gt;modexp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A single entry point: &lt;code&gt;function verify(bytes calldata proof, bytes32[] calldata publicInputs) external returns (bool)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works on any EVM chain that supports the required precompiles - Ethereum mainnet, most L2s, and testnets.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Deploying with Foundry
&lt;/h2&gt;

&lt;p&gt;Let's deploy the verifier and verify a proof on-chain. First, set up a Foundry project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge init verifier-deploy
&lt;span class="nb"&gt;cd &lt;/span&gt;verifier-deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the generated verifier:&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="nb"&gt;cp&lt;/span&gt; ../hello_world/target/Verifier.sol src/Verifier.sol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to allow Foundry to read our proof file. Add this to &lt;code&gt;foundry.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;fs_permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="py"&gt;access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"../hello_world/target"&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;h3&gt;
  
  
  Deploy script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity &amp;gt;=0.8.21;

import "forge-std/Script.sol";
import "../src/Verifier.sol";

contract DeployScript is Script {
    function run() external {
        vm.startBroadcast();
        HonkVerifier verifier = new HonkVerifier();
        console.log("HonkVerifier deployed to:", address(verifier));
        vm.stopBroadcast();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// script/Verify.s.sol
// SPDX-License-Identifier: MIT
pragma solidity &amp;gt;=0.8.21;

import "forge-std/Script.sol";
import "../src/Verifier.sol";

contract VerifyScript is Script {
    function run() external {
        bytes memory proofBytes = vm.readFileBinary("../hello_world/target/proof");

        bytes32[] memory publicInputs = new bytes32[](1);
        publicInputs[0] = bytes32(uint256(1)); // y = 1

        vm.startBroadcast();
        HonkVerifier verifier = new HonkVerifier();
        console.log("HonkVerifier deployed to:", address(verifier));

        bool result = verifier.verify(proofBytes, publicInputs);
        console.log("Proof verified:", result);
        vm.stopBroadcast();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running it
&lt;/h3&gt;

&lt;p&gt;Start a local testnet and deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;anvil &lt;span class="nt"&gt;--code-size-limit&lt;/span&gt; 50000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--code-size-limit&lt;/code&gt; flag is needed because &lt;code&gt;HonkVerifier&lt;/code&gt; exceeds the default EIP-170 contract size limit of 24,576 bytes (ours is ~33K). This is fine for local testing. For production, you'd use &lt;code&gt;--optimized&lt;/code&gt; when generating the Solidity verifier or split the contract into libraries.&lt;/p&gt;

&lt;p&gt;In another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge script script/Verify.s.sol &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; http://127.0.0.1:8545 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--private-key&lt;/span&gt; 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--broadcast&lt;/span&gt; &lt;span class="nt"&gt;--code-size-limit&lt;/span&gt; 50000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script ran successfully.

== Logs ==
  HonkVerifier deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  Proof verified: true

ONCHAIN EXECUTION COMPLETE &amp;amp; SUCCESSFUL.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proof verified on-chain. The same proof that &lt;code&gt;bb&lt;/code&gt; verified locally now passes through a Solidity contract running on an EVM.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Trust and threat model
&lt;/h2&gt;

&lt;p&gt;Before you ship anything, consider three attack surfaces.&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%2Fifho4y4a9ny1nbpojpan.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%2Fifho4y4a9ny1nbpojpan.png" alt="Trust Model" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prover honesty.&lt;/strong&gt; The circuit enforces constraints, not truth. Nobody can prove that &lt;code&gt;2 != 2&lt;/code&gt; - the constraint system rejects it. But nothing stops someone from proving &lt;code&gt;2000 != 18&lt;/code&gt; and claiming that proves their age. The circuit guarantees mathematical correctness of the relationship between inputs, not that the inputs themselves are meaningful. External anchoring (signed attestations, on-chain data, oracles) is required to bind proof inputs to real-world facts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;bb&lt;/code&gt; binary.&lt;/strong&gt; &lt;code&gt;bb&lt;/code&gt; is a local executable. If someone swaps it for a modified version, they could generate proofs that pass a compromised verifier. In any system where the prover and verifier are different entities, the verifier must run its own trusted copy of &lt;code&gt;bb&lt;/code&gt; (or verify on-chain where the contract is the trust anchor).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The verifier contract.&lt;/strong&gt; It's source code - editable before deployment. If the same entity generates the proof and deploys the verifier, there's a circular trust problem. For production, the verifier contract should be deployed by a trusted third party, verified on a block explorer, and ideally immutable (no proxy, no upgradeability). Or at the very least, governed by a multisig with a timelock.&lt;/p&gt;

&lt;p&gt;None of these are flaws in the cryptography. These are system design questions that any production deployment needs to answer.&lt;/p&gt;




&lt;p&gt;We went from compiled artifacts to a verified on-chain proof. The circuit is trivial, but the pipeline is the same one you'd use for anything more complex - age verification, credential checks, private voting. The hard part isn't the tooling. It's designing the system around it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recommended reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://noir-lang.org/docs/getting_started/hello_noir" rel="noopener noreferrer"&gt;Noir docs - Proving backend&lt;/a&gt; - official getting started guide&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://barretenberg.aztec.network/docs/getting_started/" rel="noopener noreferrer"&gt;Barretenberg&lt;/a&gt; proving backend documentation&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>blockchain</category>
      <category>security</category>
      <category>tutorial</category>
      <category>web3</category>
    </item>
    <item>
      <title>Was Web-Entwickler von Game-Engine-Architektur lernen können</title>
      <dc:creator>Ziva</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:28:24 +0000</pubDate>
      <link>https://dev.to/ziva/was-web-entwickler-von-game-engine-architektur-lernen-konnen-le3</link>
      <guid>https://dev.to/ziva/was-web-entwickler-von-game-engine-architektur-lernen-konnen-le3</guid>
      <description>&lt;p&gt;Wenn du mit React, Vue oder Angular arbeitest, nutzt du Patterns, die aus der Spieleentwicklung stammen. Nicht inspiriert davon. Direkt übernommen. Der Component Tree, den du jeden Tag baust, wurde in den 1980ern als Scene Graph erfunden, lange bevor JavaScript überhaupt existierte.&lt;/p&gt;

&lt;p&gt;Hier sind vier Architektur-Patterns aus Game Engines, die dein Web-Development besser machen.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Der Scene Graph: Reacts Urgroßvater
&lt;/h2&gt;

&lt;p&gt;Der Scene Graph ist eine hierarchische Baumstruktur, in der jeder Knoten Transformationen und Eigenschaften an seine Kinder vererbt. PHIGS (Programmer's Hierarchical Interactive Graphics System) wurde 1984 entwickelt und 1988 als ANSI-Standard verabschiedet. Es war die erste kommerzielle Scene-Graph-Spezifikation.&lt;/p&gt;

&lt;p&gt;Das Prinzip: Du baust eine Szene nicht als flache Liste, sondern als Baum. Ein Raumschiff-Node enthält einen Triebwerk-Node, der enthält einen Partikel-Node. Wenn sich das Raumschiff bewegt, bewegen sich Triebwerk und Partikel automatisch mit. Kaskadierung von Eigenschaften durch die Hierarchie.&lt;/p&gt;

&lt;p&gt;Kommt dir das bekannt vor? Reacts Component Tree funktioniert identisch. Context propagiert durch den Baum nach unten. CSS-Variablen kaskadieren. State fließt von Eltern zu Kindern. Das ist kein Zufall. Jordan Walke, der Erfinder von React, hat sich explizit von Szenegraphen und funktionaler Programmierung inspirieren lassen.&lt;/p&gt;

&lt;p&gt;Godot macht das noch konsequenter als React. In Godots Scene Tree ist &lt;em&gt;alles&lt;/em&gt; ein Node: UI-Elemente, Spiellogik, Physik, Audio. Kein &lt;code&gt;useEffect&lt;/code&gt;, kein Lifecycle-Hook. Du hast &lt;code&gt;_ready()&lt;/code&gt; (Mount), &lt;code&gt;_process()&lt;/code&gt; (Render-Loop) und &lt;code&gt;_exit_tree()&lt;/code&gt; (Unmount). Drei Funktionen statt 15 Hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;class_name&lt;/span&gt; &lt;span class="n"&gt;Raumschiff&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;CharacterBody2D&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_ready&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Wie componentDidMount / useEffect([], ...)&lt;/span&gt;
    &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;Triebwerk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;starten&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Wie requestAnimationFrame, 60x pro Sekunde&lt;/span&gt;
    &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;geschwindigkeit&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Composition over Inheritance: Die Spielebranche wusste es zuerst
&lt;/h2&gt;

&lt;p&gt;Das Entity Component System (ECS) hat Composition over Inheritance nicht erfunden, aber es hat bewiesen, dass es skaliert. Thief: The Dark Project nutzte 1998 ein ECS, und der gleiche Ansatz lief später in System Shock 2.&lt;/p&gt;

&lt;p&gt;Die Idee: Eine Spielfigur ist keine Klasse in einer Vererbungshierarchie. Sie ist eine leere Entity mit angehängten Components. Braucht sie Physik? &lt;code&gt;PhysicsComponent&lt;/code&gt; anhängen. Braucht sie Gesundheit? &lt;code&gt;HealthComponent&lt;/code&gt; anhängen. Braucht sie beides nicht mehr? Components entfernen.&lt;/p&gt;

&lt;p&gt;React hat dieses Pattern 2018 mit Hooks übernommen. Statt Klassen-Vererbung (&lt;code&gt;class MyComponent extends React.Component&lt;/code&gt;) nutzt du Composition mit &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt; und Custom Hooks. Die Game-Engine-Welt hatte das gleiche Problem 20 Jahre früher gelöst.&lt;/p&gt;

&lt;p&gt;Unity hat die Performance-Vorteile gemessen: Ihr Data-Oriented Technology Stack (DOTS) mit ECS erreicht 5x bis 50x bessere CPU-Performance als die klassische OOP-Architektur, je nach Parallelisierungsgrad. In der Megacity-Demo von 2018 renderte Unity damit eine Stadt mit 4,5 Millionen Mesh-Renderern.&lt;/p&gt;

&lt;p&gt;Für Web-Entwickler heißt das: Wenn du merkst, dass deine Klassen-Hierarchie zu tief wird, denk wie ein Game-Engine-Architekt. Zerlege Verhalten in kleine, kombinierbare Einheiten. In React sind das Hooks. In Godot sind das Nodes. Das Prinzip ist dasselbe.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Signals: Observer Pattern ohne den Boilerplate
&lt;/h2&gt;

&lt;p&gt;Godot hat Signals als erstklassiges Sprachfeature. Du deklarierst ein Signal, und andere Nodes verbinden sich damit. Keine Event-Bus-Bibliothek, kein Redux, kein Pub/Sub-Framework.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;signal&lt;/span&gt; &lt;span class="n"&gt;leben_verloren&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neues_leben&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;schaden_nehmen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;menge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;leben&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;menge&lt;/span&gt;
    &lt;span class="n"&gt;leben_verloren&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leben&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_ready&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;spieler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leben_verloren&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_bei_leben_verloren&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_bei_leben_verloren&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neues_leben&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neues_leben&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Das ist das Observer Pattern in seiner reinsten Form, ohne Middleware-Layer. Die Web-Welt hat Jahre gebraucht, um an einen ähnlichen Punkt zu kommen. Solid.js Signals, Angular Signals (seit v16), Vue 3 Reactivity: Alle lösen das gleiche Problem, das Game Engines seit den 2000ern gelöst haben.&lt;/p&gt;

&lt;p&gt;Der entscheidende Unterschied: In Game Engines sind Signals &lt;em&gt;architektonisch&lt;/em&gt;. Sie definieren, wie Systeme kommunizieren. Im Web werden sie oft nur als State-Management-Optimierung behandelt. Game-Engine-Entwickler denken von Anfang an in entkoppelten Systemen, die über Signals kommunizieren. Web-Entwickler sollten das auch tun.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Object Pooling: Warum Game Engines Garbage Collection hassen
&lt;/h2&gt;

&lt;p&gt;In einem Shooter entstehen pro Sekunde Dutzende Projektile. Jedes Mal &lt;code&gt;new Bullet()&lt;/code&gt; und dann den Garbage Collector aufräumen lassen? Unspielbar. Stattdessen nutzen Game Engines Object Pools: Eine fixe Menge an Objekten wird beim Start erstellt. "Verbrauchte" Objekte werden nicht zerstört, sondern zurückgesetzt und wiederverwendet.&lt;/p&gt;

&lt;p&gt;Web-Entwickler kennen das Prinzip als Connection Pooling bei Datenbanken. Aber es geht weiter: React 18s Concurrent Rendering recycelt Fiber-Nodes intern. Workers in Node.js Thread-Pools werden wiederverwendet. Das Pattern ist überall, nur redet im Web kaum jemand darüber.&lt;/p&gt;

&lt;p&gt;Wenn du eine Anwendung baust, die viele kurzlebige Objekte erzeugt (Chat-Nachrichten, Tabellen-Zeilen, Animationen), frag dich: Könnte ich die recyceln statt neu zu erstellen? Die Spielebranche beantwortet diese Frage seit 30 Jahren mit Ja.&lt;/p&gt;

&lt;h2&gt;
  
  
  Die Zahlen hinter Game-Engine-Architektur
&lt;/h2&gt;

&lt;p&gt;Die deutsche Spielebranche hat 2023 mit knapp 1.200 Unternehmen und fast 13.000 Beschäftigten über 3,9 Milliarden Euro Umsatz erwirtschaftet (Quelle: &lt;a href="https://www.game.de/publikationen/jahresreport-2025/" rel="noopener noreferrer"&gt;game.de Jahresreport 2025&lt;/a&gt;). Das sind keine Hobby-Projekte. Das sind Produktionssysteme, die unter extremen Performance-Anforderungen laufen.&lt;/p&gt;

&lt;p&gt;Godot, die Open-Source-Engine mit über 108.000 GitHub Stars, hat beim GMTK Game Jam 2025 einen Marktanteil von 39% erreicht, mehr als dreimal so viel wie noch 2021 (13%). Tools wie &lt;a href="https://ziva.sh" rel="noopener noreferrer"&gt;Ziva&lt;/a&gt; helfen Godot-Entwicklern dabei, effizienter zu arbeiten, aber die Architektur der Engine selbst ist der eigentliche Lernstoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fazit: Lerne von den Leuten, die 60 FPS halten müssen
&lt;/h2&gt;

&lt;p&gt;Web-Entwickler optimieren für Time-to-Interactive. Game-Engine-Entwickler optimieren für 16,67 Millisekunden pro Frame, bei 60 FPS. Diese Einschränkung hat Architektur-Patterns hervorgebracht, die robuster und performanter sind als das, was die meiste Web-Architektur bietet.&lt;/p&gt;

&lt;p&gt;Das heißt nicht, dass du deine Next.js-App wie eine Game Engine bauen sollst. Es heißt: Wenn du vor einem Architektur-Problem stehst, schau dir an, wie Godot, Unity oder Unreal es lösen. Die Chancen stehen gut, dass sie das Problem vor 10 oder 20 Jahren schon gelöst haben.&lt;/p&gt;

&lt;p&gt;Drei konkrete Schritte:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lies &lt;a href="https://gameprogrammingpatterns.com/" rel="noopener noreferrer"&gt;Game Programming Patterns&lt;/a&gt;&lt;/strong&gt; von Robert Nystrom. Es ist kostenlos online und erklärt jedes Pattern mit konkretem Code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bau ein kleines Godot-Projekt.&lt;/strong&gt; Nicht um Spiele zu entwickeln, sondern um die Architektur zu erleben. Der Scene Tree verändert, wie du über Component-Hierarchien denkst.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hinterfrage deine Abstraktionen.&lt;/strong&gt; Wenn du drei NPM-Pakete brauchst, um ein Problem zu lösen, das eine Game Engine in 10 Zeilen löst, stimmt etwas mit der Abstraktion nicht.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Die beste Architektur entsteht nicht in Frameworks. Sie entsteht dort, wo Performance keine Option, sondern eine harte Grenze ist.&lt;/p&gt;

</description>
      <category>german</category>
      <category>deutsch</category>
      <category>godot</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>StudioMeyer CRM: AI-Native Contact Management with 33 MCP Tools</title>
      <dc:creator>Matthias Meyer</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:27:34 +0000</pubDate>
      <link>https://dev.to/studiomeyer/studiomeyer-crm-ai-native-contact-management-with-33-mcp-tools-ck6</link>
      <guid>https://dev.to/studiomeyer/studiomeyer-crm-ai-native-contact-management-with-33-mcp-tools-ck6</guid>
      <description>&lt;p&gt;Most CRM systems are built for enterprises. Dozens of menus, mandatory fields for things you don't need, and per-seat pricing. For freelancers, small agencies, and solo entrepreneurs, that's overkill.&lt;/p&gt;

&lt;p&gt;StudioMeyer CRM is an MCP server. No dashboard, no app, no interface -- you talk directly to Claude, and Claude manages your contacts, deals, and pipeline. Everything through natural language.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does a CRM without an interface work?
&lt;/h2&gt;

&lt;p&gt;Instead of filling out forms, you simply tell Claude what you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Create company Mueller GmbH, industry IT, website mueller-gmbh.de"&lt;/li&gt;
&lt;li&gt;"New deal: Website Redesign for Mueller, 5,000 EUR, stage proposal"&lt;/li&gt;
&lt;li&gt;"Show me the pipeline"&lt;/li&gt;
&lt;li&gt;"Which follow-ups are overdue?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude runs the CRM tools in the background. You see the results directly in the chat -- formatted, sorted, with all relevant details.&lt;/p&gt;

&lt;h3&gt;
  
  
  30 Tools for the complete CRM workflow
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;What you do with it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Companies&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Create, update, delete (cascading)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contacts&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Manage contact persons, mark decision makers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Create deals, pipeline overview, forecast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Log interactions (email, call, meeting), timeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notes&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Add notes to companies or deals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Dashboard, health scores, revenue report, Stripe sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Full-text search across all 7 entity types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leads&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Capture, qualify, convert leads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow-ups&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Tasks and reminders with priorities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extras&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Import/export, audit log, handoff, guide&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Hosted (get started immediately)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://crm.studiomeyer.io/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the URL into Claude Desktop, OAuth login, done. We handle the database and hosting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Self-hosted
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/studiomeyer-io/mcp-crm
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-crm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env  &lt;span class="c"&gt;# Add DATABASE_URL&lt;/span&gt;
npm run db:push &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Desktop configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"crm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/path/to/mcp-crm/dist/server.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql://..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"CRM_TENANT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-company"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What you can actually do with it
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Morning routine (2 minutes)
&lt;/h3&gt;

&lt;p&gt;"Give me the daily briefing."&lt;/p&gt;

&lt;p&gt;Claude shows you: new leads since yesterday, overdue follow-ups (sorted by priority), pipeline overview, and alerts (inactive companies, at-risk health scores). One command instead of ten clicks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client management
&lt;/h3&gt;

&lt;p&gt;"Log a call with Mueller GmbH -- discussed timeline, next step is proposal by Friday."&lt;/p&gt;

&lt;p&gt;Claude creates the interaction, automatically sets a follow-up, and updates the last contact timestamp. The timeline later shows all interactions and notes chronologically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline at a glance
&lt;/h3&gt;

&lt;p&gt;"How's the pipeline looking?"&lt;/p&gt;

&lt;p&gt;Claude delivers an overview of all deals, grouped by stage (Lead, Qualified, Proposal, Negotiation, Won, Lost). With weighted probability per stage and MRR/ARR calculation from your deal values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stripe integration
&lt;/h3&gt;

&lt;p&gt;If you use Stripe: One command syncs your active subscriptions. For new Stripe customers, a company, contact, and deal are automatically created.&lt;/p&gt;

&lt;p&gt;"Sync Stripe."&lt;/p&gt;

&lt;p&gt;After that, the revenue report shows: MRR, ARR, growth, and breakdown by customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The intelligent search
&lt;/h2&gt;

&lt;p&gt;Search works in three phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;German full-text search&lt;/strong&gt; -- Finds "consulting" even when "consult" is stored (stemming)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy matching&lt;/strong&gt; -- Handles typos: "Meuller" finds "Mueller". Umlauts are automatically resolved&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefix search&lt;/strong&gt; -- Finds partial terms as a final fallback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The search covers all seven entity types simultaneously: companies, contacts, deals, interactions, notes, leads, and tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Health Scores: Which clients need attention?
&lt;/h2&gt;

&lt;p&gt;Every company automatically gets a health score from 0 to 100. The score is based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Last interaction (when was the last contact?)&lt;/li&gt;
&lt;li&gt;Active deals (are there ongoing business relationships?)&lt;/li&gt;
&lt;li&gt;Follow-up discipline (are tasks being completed?)&lt;/li&gt;
&lt;li&gt;Communication frequency (how regular is the contact?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Companies below 40 points get a warning in the dashboard. So you never forget a client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Import and export
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Import
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSV or JSON&lt;/strong&gt; -- Automatic column recognition for German and English headers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HubSpot import&lt;/strong&gt; -- 15 column mappings (First+Last Name, Job Title, Company Domain, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipedrive import&lt;/strong&gt; -- 7 column mappings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dry-run mode&lt;/strong&gt; -- Preview without actually importing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate detection&lt;/strong&gt; -- Prevents double entries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Export
&lt;/h3&gt;

&lt;p&gt;Export contacts, companies, deals, or leads as CSV or JSON. Maximum 10,000 rows per export.&lt;/p&gt;

&lt;h2&gt;
  
  
  The built-in guide
&lt;/h2&gt;

&lt;p&gt;On first start, simply type: "Show me the CRM guide."&lt;/p&gt;

&lt;p&gt;Claude explains 12 topics interactively: from the first five minutes (create company, contact, deal, dashboard, follow-up) to advanced features like Stripe sync, health scores, and pipeline forecast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for productive use
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with five companies.&lt;/strong&gt; Add your most important clients, each with a contact person and an active deal. Takes five minutes and you have a working pipeline immediately.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Log every interaction.&lt;/strong&gt; A brief sentence is enough: "Call with Mueller, feedback on design was positive." Over time, a complete timeline emerges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use follow-ups as your task list.&lt;/strong&gt; Instead of a separate to-do app: "Create follow-up: call Mueller, priority high, by Friday."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automate leads.&lt;/strong&gt; Through the webhook endpoint, leads can flow automatically from your website form or newsletter into the CRM.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Export monthly.&lt;/strong&gt; A CSV export of your contacts as a backup and for analysis in Excel or Google Sheets.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Companies&lt;/th&gt;
&lt;th&gt;Contacts&lt;/th&gt;
&lt;th&gt;Deals&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Free&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pro&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$29/mo&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Team&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$49/mo&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All 30 tools are available in every tier. The differences are in data volume and API rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EU hosting:&lt;/strong&gt; Supabase, Frankfurt (Germany)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tenant isolation:&lt;/strong&gt; Each user has separate, isolated data (Row Level Security)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosting possible:&lt;/strong&gt; Docker + your own PostgreSQL database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit log:&lt;/strong&gt; Complete tracking of who changed what and when&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;StudioMeyer CRM is built for business owners who don't need a complex CRM system -- but a fast one. No onboarding, no training, no interface to learn. You talk to Claude, Claude manages your clients. If you'd like to try it: The free version is enough to get started, and the built-in guide explains everything step by step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://crm.studiomeyer.io" rel="noopener noreferrer"&gt;crm.studiomeyer.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>crm</category>
      <category>typescript</category>
    </item>
    <item>
      <title>HTCPCP IYKYK: I Built a Browser Extension That Lets Dinosaurs Eat the Internet</title>
      <dc:creator>John Munn</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:26:34 +0000</pubDate>
      <link>https://dev.to/tawe/htcpcp-iykyk-i-built-a-browser-extension-that-lets-dinosaurs-eat-the-internet-30a5</link>
      <guid>https://dev.to/tawe/htcpcp-iykyk-i-built-a-browser-extension-that-lets-dinosaurs-eat-the-internet-30a5</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;Dinosaur Eats&lt;/strong&gt;, a Chrome extension that sends a tiny pixel dinosaur onto any webpage and lets it eat the visible text line by line.&lt;/p&gt;

&lt;p&gt;Not paragraphs.&lt;/p&gt;

&lt;p&gt;Not sections.&lt;/p&gt;

&lt;p&gt;Rendered lines.&lt;/p&gt;

&lt;p&gt;It solves nothing. If anything, it introduces a new class of browser instability: &lt;strong&gt;active prehistoric content loss&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Click the toolbar icon and the extension scans the page for readable text. A dinosaur walks in, lines up the shot, and starts chewing through the page one visible line at a time until the whole thing looks like it got caught in a small but highly motivated extinction event.&lt;/p&gt;

&lt;p&gt;Sometimes it’s one dinosaur.&lt;/p&gt;

&lt;p&gt;Sometimes it escalates into a full stampede.&lt;/p&gt;

&lt;p&gt;And because the challenge is &lt;strong&gt;HTCPCP IYKYK&lt;/strong&gt;, I added a hidden protocol joke.&lt;/p&gt;

&lt;p&gt;If the extension is active and you type &lt;strong&gt;418&lt;/strong&gt;, the dinosaurs mutate into &lt;strong&gt;teapotsaurs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Type &lt;strong&gt;814&lt;/strong&gt; and they switch back.&lt;/p&gt;

&lt;p&gt;That was the moment I knew the project had crossed from “browser prank” into “deeply respectful nonsense.”&lt;/p&gt;

&lt;h3&gt;
  
  
  One-line pitch
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;A tiny dinosaur enters your browser and eats the visible text, but typing &lt;code&gt;418&lt;/code&gt; mutates it into a teapotsaur because RFCs deserve whimsy too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Demo video: &lt;a href="https://youtu.be/mSmW5a-bhgo" rel="noopener noreferrer"&gt;https://youtu.be/mSmW5a-bhgo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/Tawe/dinosaur-eats" rel="noopener noreferrer"&gt;https://github.com/Tawe/dinosaur-eats&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built as a &lt;strong&gt;Manifest V3 Chrome extension&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;background service worker&lt;/li&gt;
&lt;li&gt;content scripts&lt;/li&gt;
&lt;li&gt;Chrome &lt;code&gt;activeTab&lt;/code&gt;, &lt;code&gt;storage&lt;/code&gt;, and &lt;code&gt;scripting&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CSS sprite animation&lt;/li&gt;
&lt;li&gt;custom dinosaur + teapotsaur sprite sheets&lt;/li&gt;
&lt;li&gt;looping chomp audio&lt;/li&gt;
&lt;li&gt;optional herd behavior&lt;/li&gt;
&lt;li&gt;hidden &lt;code&gt;418&lt;/code&gt; / &lt;code&gt;814&lt;/code&gt; mutation triggers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; for iteration during development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The part I got most carried away with was making the dinosaur eat &lt;strong&gt;rendered lines instead of DOM blocks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Deleting paragraphs would have been easy.&lt;/p&gt;

&lt;p&gt;Instead I wanted the page to disappear in the exact shape the user sees it, which meant wrapping text, measuring where the browser actually breaks lines, grouping spans into visible rows, randomizing destruction order, and syncing the bite animation so the line disappears on the exact chomp frame.&lt;/p&gt;

&lt;p&gt;A completely unreasonable amount of engineering for a joke.&lt;/p&gt;

&lt;p&gt;Which is probably why it worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;At a high level, the extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;waits for toolbar activation&lt;/li&gt;
&lt;li&gt;scans the current page for visible readable text&lt;/li&gt;
&lt;li&gt;wraps characters to detect true rendered line breaks&lt;/li&gt;
&lt;li&gt;groups them into visible lines&lt;/li&gt;
&lt;li&gt;randomizes the destruction order&lt;/li&gt;
&lt;li&gt;sends in the dinosaur&lt;/li&gt;
&lt;li&gt;removes the line on the bite frame&lt;/li&gt;
&lt;li&gt;mutates into teapotsaur mode on &lt;code&gt;418&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;reverts back on &lt;code&gt;814&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The strangest technical problem was that browsers don’t really expose &lt;strong&gt;“visible lines of text”&lt;/strong&gt; as a concept.&lt;/p&gt;

&lt;p&gt;That layer had to be invented.&lt;/p&gt;

&lt;p&gt;So the joke ended up requiring a lot of layout measurement, span grouping, sprite timing, and DOM mutation choreography just to make the page feel like it was literally being eaten.&lt;/p&gt;

&lt;p&gt;The premise is silly.&lt;/p&gt;

&lt;p&gt;The implementation got weirdly serious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Google AI Usage
&lt;/h2&gt;

&lt;p&gt;I used &lt;strong&gt;Gemini&lt;/strong&gt; throughout development as a fast implementation partner.&lt;/p&gt;

&lt;p&gt;It was especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;working through Manifest V3 structure&lt;/li&gt;
&lt;li&gt;refining content-script injection flow&lt;/li&gt;
&lt;li&gt;thinking through line grouping logic&lt;/li&gt;
&lt;li&gt;improving sprite timing&lt;/li&gt;
&lt;li&gt;helping shape the 418 teapotsaur mutation idea into something that actually reads on screen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final result is proudly useless.&lt;/p&gt;

&lt;p&gt;Gemini helped me make it more useless, faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ode to Larry Masinter
&lt;/h2&gt;

&lt;p&gt;The hidden &lt;code&gt;418&lt;/code&gt; mode is my favorite part.&lt;/p&gt;

&lt;p&gt;Typing &lt;code&gt;418&lt;/code&gt; while the extension is active mutates every dinosaur into a tiny walking &lt;strong&gt;teapotsaur&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They still eat the page.&lt;/p&gt;

&lt;p&gt;They just do it with much stronger protocol energy.&lt;/p&gt;

&lt;p&gt;Typing &lt;code&gt;814&lt;/code&gt; reverses the mutation, which is objectively not how protocols work, but it felt spiritually correct.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>My Journey Contributing to Django Simple Deploy: Improving the PythonAnywhere Plugin for Beginners</title>
      <dc:creator>victoria nyamai</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:26:22 +0000</pubDate>
      <link>https://dev.to/victoria_nyamai_eb584016d/my-journey-contributing-to-django-simple-deploy-improving-the-pythonanywhere-plugin-for-beginners-3e1i</link>
      <guid>https://dev.to/victoria_nyamai_eb584016d/my-journey-contributing-to-django-simple-deploy-improving-the-pythonanywhere-plugin-for-beginners-3e1i</guid>
      <description>&lt;p&gt;I’ve recently started contributing to &lt;em&gt;django-simple-deploy&lt;/em&gt;, a tool that makes it easier to deploy Django projects with a single command. After watching Eric’s &lt;a href="https://www.youtube.com/watch?v=o895qyw-p4I" rel="noopener noreferrer"&gt;DjangoCon talk&lt;/a&gt; and hearing how many beginners struggle with deployment, I decided to focus on improving the PythonAnywhere plugin, especially for people on the free tier. Since PythonAnywhere is where many first-time Django developers go, I want to help make that experience smoother and less confusing. I also want this work to reach &lt;a href="https://djangogirls.org/en/" rel="noopener noreferrer"&gt;Django Girls&lt;/a&gt;, because helping more women get into tech is something I really care about.&lt;/p&gt;

&lt;h1&gt;
  
  
  So what is django-simple-deploy?
&lt;/h1&gt;

&lt;p&gt;Django-simple-deploy is a tool that automates the steps needed to deploy a Django app. Instead of manually setting environment variables, configuring WSGI, or running multiple commands, the tool handles these tasks using platform-specific plugins. You run one command, and it prepares your project for platforms like &lt;a href="https://www.pythonanywhere.com/" rel="noopener noreferrer"&gt;PythonAnywhere&lt;/a&gt;, &lt;a href="https://fly.io/" rel="noopener noreferrer"&gt;Fly.io&lt;/a&gt;, or &lt;a href="https://scalingo.com/" rel="noopener noreferrer"&gt;Scalingo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;There are also community-built plugins, like one that lets you deploy to any &lt;a href="https://github.com/janraasch/dsd-vps-kamal" rel="noopener noreferrer"&gt;VPS with Kamal&lt;/a&gt;, showing that the tool is open for anyone to extend.&lt;/p&gt;

&lt;h1&gt;
  
  
  My very simple todo list
&lt;/h1&gt;

&lt;p&gt;Some of the tasks I’ll be tackling include testing deployments on different project types, checking how the WSGI file is handled, documenting free-tier restrictions, and exploring how an “update” command could work. I’ll also work on smaller fixes, like improving error messages and clarifying instructions, to make the plugin more beginner-friendly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wanna get involved?
&lt;/h1&gt;

&lt;p&gt;If you’re interested in trying &lt;em&gt;django-simple-deploy&lt;/em&gt; or want to help improve the PythonAnywhere plugin, you’re welcome to join in. Testing deployments, giving feedback, or picking up small issues in the repo are great ways to get started. Even just sharing your experience helps us understand where beginners struggle.&lt;/p&gt;

</description>
      <category>django</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Ionify vs Vite: What Actually Happens Inside Your Build Tool</title>
      <dc:creator>KhaledSalem</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:24:35 +0000</pubDate>
      <link>https://dev.to/khaledmsalem/ionify-vs-vite-what-actually-happens-inside-your-build-tool-2end</link>
      <guid>https://dev.to/khaledmsalem/ionify-vs-vite-what-actually-happens-inside-your-build-tool-2end</guid>
      <description>&lt;p&gt;Most developers use Vite and trust it. It's fast, well-designed, and gets out of the way.&lt;/p&gt;

&lt;p&gt;But there's a fundamental assumption baked into Vite's architecture — and every other major build tool — that we decided to challenge when building Ionify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The assumption: every build starts from zero.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post is a deep look at what that means in practice, what we built instead, and the architectural tradeoffs we made along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stateless Build Problem
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;vite build&lt;/code&gt;, here's roughly what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Vite spins up esbuild/Rollup (now powered by Rolldown + Oxc instead of pure Rollup/Babel in many cases)&lt;/li&gt;
&lt;li&gt;Each file goes through its plugin chain independently&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main.tsx&lt;/code&gt; → TS plugin → JSX plugin → output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;utils.ts&lt;/code&gt; → TS plugin → output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.css&lt;/code&gt; → CSS plugin → output&lt;/li&gt;
&lt;li&gt;Everything gets bundled&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next time you run it? Same thing. &lt;strong&gt;All of it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's no memory of what already ran. The TS plugin doesn't know the JSX plugin already processed a file. The bundler doesn't know which files actually changed since last time. The entire pipeline is stateless by design.&lt;/p&gt;

&lt;p&gt;This isn't a bug — it's a deliberate architectural choice that keeps the tool simple and predictable. But it has a real cost at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Ionify Thinks About This Differently
&lt;/h2&gt;

&lt;p&gt;Instead of transforming files, &lt;strong&gt;Ionify addresses them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every module gets a content hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA-256(source content + config version) → CAS key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result lives in a Content-Addressable Store (CAS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.ionify/
  cas/
    &amp;lt;configHash&amp;gt;/
      &amp;lt;moduleHash&amp;gt;/
        transformed.js
        transformed.js.map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same content + same config = same hash = skip the transform entirely.&lt;/p&gt;

&lt;p&gt;Not faster transforms. &lt;strong&gt;No transforms at all&lt;/strong&gt; — for everything that hasn't changed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Persistent Graph
&lt;/h2&gt;

&lt;p&gt;The other half of the equation is the dependency graph.&lt;/p&gt;

&lt;p&gt;Vite reconstructs its module graph on every dev server start. Ionify persists it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.ionify/
  graph.db   ← sled-backed embedded KV store (Rust)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a file changes, Ionify runs a BFS over the reverse dependency index to find exactly which modules are affected:&lt;/p&gt;

&lt;p&gt;This means on a 500-module project, changing one utility file might only invalidate 12 modules — not all 500.&lt;/p&gt;




&lt;h2&gt;
  
  
  Version Isolation: Config Changes Invalidate Everything
&lt;/h2&gt;

&lt;p&gt;One subtle problem with persistent caches: what if your config changes?&lt;/p&gt;

&lt;p&gt;Ionify solves this with a deterministic version hash computed from the config:&lt;/p&gt;




&lt;h2&gt;
  
  
  The Four-Tier CAS Architecture
&lt;/h2&gt;

&lt;p&gt;As the system evolved, we ended up with four distinct caching layers, each solving a different problem:&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1 — Module Transform Cache
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Tier 2 — Deps Artifact Store
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Tier 3 — Compression CAS
&lt;/h3&gt;

&lt;p&gt;Pre-compressed build outputs. Brotli-11 + gzip-9 computed once, served forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 4 — Chunk-Output CAS
&lt;/h3&gt;




&lt;h2&gt;
  
  
  Real Config: Migrating from Vite
&lt;/h2&gt;

&lt;p&gt;Here's an actual &lt;code&gt;ionify.config.ts&lt;/code&gt; from a production project that migrated from Vite:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&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;ionify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/src/main.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;https&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="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Same aliases as tsconfig — Ionify reads these natively&lt;/span&gt;
    &lt;span class="c1"&gt;// Note: if your tsconfig.json is JSONC (comments/trailing commas),&lt;/span&gt;
    &lt;span class="c1"&gt;// auto-alias parsing currently fails — specify manually here&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&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="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@@/Core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/Core/src&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/src&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="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.mts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.jsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.tsx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;mainFields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsnext:main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsnext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&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="na"&gt;optimizeDeps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moment-hijri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;   &lt;span class="c1"&gt;// pre-warm known-problem deps&lt;/span&gt;
    &lt;span class="na"&gt;sharedChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;packSlimming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;vendorPacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auto&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="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;esnext&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  This project has more than +10K module and the results of build time at vite roll-down was 3.7 seconds every time after migrating to Ionify. The time was reduced to 2.2 seconds on warm build, and 3.2 seconds for cold build
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The Fundamental Difference
&lt;/h2&gt;

&lt;p&gt;Vite is built around the assumption that each build is independent.&lt;/p&gt;

&lt;p&gt;Ionify is built around the assumption that &lt;strong&gt;most of the work you did last time is still valid.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both assumptions are reasonable. Vite's leads to a simpler, more predictable system. Ours leads to a system where the second build, the tenth build, and the CI build all benefit from everything that happened before.&lt;/p&gt;

&lt;p&gt;The "With Ionify" side of the transform diagram is mostly empty. Not because we're hiding complexity — but because most of the transforms simply don't run.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ionify is currently in production use. → &lt;a href="https://ionify.cloud" rel="noopener noreferrer"&gt;ionify.cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/khaledM-salem/embed/PwGdqgx?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tooling</category>
      <category>webpack</category>
    </item>
    <item>
      <title>Build a Searchable Creator Database with PostgreSQL and Node.js</title>
      <dc:creator>Olamide Olaniyan</dc:creator>
      <pubDate>Tue, 07 Apr 2026 19:24:00 +0000</pubDate>
      <link>https://dev.to/olams/build-a-searchable-creator-database-with-postgresql-and-nodejs-54mn</link>
      <guid>https://dev.to/olams/build-a-searchable-creator-database-with-postgresql-and-nodejs-54mn</guid>
      <description>&lt;p&gt;Every influencer marketing platform charges $300+/month. The core feature? A searchable database of creators.&lt;/p&gt;

&lt;p&gt;But here's their secret: it's just a PostgreSQL table with a good search index. The data comes from public APIs. The "AI-powered discovery" is a &lt;code&gt;WHERE&lt;/code&gt; clause with some filters.&lt;/p&gt;

&lt;p&gt;Let's build the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A creator database that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ingests creator profiles from Instagram and TikTok&lt;/li&gt;
&lt;li&gt;Stores them in PostgreSQL with full-text search&lt;/li&gt;
&lt;li&gt;Lets you search by niche, follower range, engagement rate, location, and keywords&lt;/li&gt;
&lt;li&gt;Auto-enriches new profiles when they're looked up&lt;/li&gt;
&lt;li&gt;Exposes a simple REST API for querying&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the backend that powers tools like Modash, Heepsy, and Upfluence. You'll have a working version by the end of this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js + Express&lt;/strong&gt; – API server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; – storage + full-text search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prisma&lt;/strong&gt; – database ORM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SociaVault API&lt;/strong&gt; – fetch creator profiles and posts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;node-cron&lt;/strong&gt; – background enrichment jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Database Schema
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Creator {
  id              String    @id @default(cuid())
  platform        String    // instagram, tiktok
  username        String
  displayName     String?
  bio             String?
  followers       Int       @default(0)
  following       Int       @default(0)
  postsCount      Int       @default(0)
  avgLikes        Int       @default(0)
  avgComments     Int       @default(0)
  engagementRate  Float     @default(0)
  profilePicUrl   String?
  isVerified      Boolean   @default(false)
  category        String?   // detected niche
  country         String?
  language        String?
  lastUpdated     DateTime  @default(now())
  createdAt       DateTime  @default(now())

  @@unique([platform, username])
  @@index([platform, followers])
  @@index([category])
  @@index([engagementRate])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma migrate dev &lt;span class="nt"&gt;--name&lt;/span&gt; init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Key Trick: Full-Text Search
&lt;/h2&gt;

&lt;p&gt;PostgreSQL has built-in full-text search. No Elasticsearch needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Add a generated tsvector column for search&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"Creator"&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;search_vector&lt;/span&gt; &lt;span class="n"&gt;tsvector&lt;/span&gt;
  &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;'C'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Index it&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;creator_search_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"Creator"&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_vector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Username and display name get weight A (highest priority). Bio gets B. Category gets C. When you search "fitness coach LA", it'll match creators whose username contains "fitness", bio mentions "coach", and location includes "LA" — ranked by relevance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: The Ingestion Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ingest.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&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;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&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;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.sociavault.com/v1/scrape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SOCIAVAULT_API_KEY&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ingestCreator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Check if we already have fresh data (&amp;lt; 7 days old)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;platform_username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&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;daysSinceUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastUpdated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;daysSinceUpdate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Still fresh&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch profile&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`/instagram/profile?username=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&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="s2"&gt;`/tiktok/profile?username=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileRes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileEndpoint&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;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;profileRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profileRes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch recent posts for engagement calculation&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`/instagram/posts?username=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;limit=12`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/tiktok/profile-videos?username=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;limit=12`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;postsRes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postsEndpoint&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;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postsRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;postsRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate engagement metrics&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalLikes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&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;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;likesCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;diggCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&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;totalComments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&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;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentsCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&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;avgLikes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalLikes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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;avgComments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalComments&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&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;followers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;followersCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;followerCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&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;engagementRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;followers&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;((((&lt;/span&gt;&lt;span class="nx"&gt;avgLikes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;avgComments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Detect category from bio (simple keyword matching)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;detectCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bio&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Upsert into database&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;platform_username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nickname&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bio&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;following&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;followingCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;postsCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postsCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;avgLikes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;avgComments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;engagementRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;profilePicUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profilePicUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;avatarUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isVerified&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="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nickname&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bio&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;following&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;followingCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;postsCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postsCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoCount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;avgLikes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;avgComments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;engagementRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;profilePicUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profilePicUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;avatarUrl&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isVerified&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="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastUpdated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;detectCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bio&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;lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fitness&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fitness&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gym&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personal trainer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;health coach&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beauty&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beauty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;makeup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;skincare&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cosmetics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hair&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;food&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;food&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recipe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cooking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chef&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;restaurant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;travel&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;travel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wanderlust&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adventure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;explore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;backpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tech&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tech&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;developer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;software&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;saas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fashion&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fashion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outfit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clothing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;designer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gaming&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gaming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gamer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;esports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;streamer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;music&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;music&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;musician&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;singer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;producer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dj&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;education&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;teacher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;education&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;learn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tutor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;professor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;business&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entrepreneur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ceo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;founder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;business&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;marketing&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;other&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ingestCreator&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: The Search API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@prisma/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ingestCreator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ingest&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;prisma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Search creators&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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/creators/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;// text search query&lt;/span&gt;
    &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// instagram or tiktok&lt;/span&gt;
    &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// fitness, beauty, tech, etc.&lt;/span&gt;
    &lt;span class="nx"&gt;minFollowers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;maxFollowers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;minEngagement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;followers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&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;where&lt;/span&gt; &lt;span class="o"&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;AND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minFollowers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;gte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minFollowers&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxFollowers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;followers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxFollowers&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minEngagement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;engagementRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;gte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minEngagement&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Full-text search using raw SQL for the tsvector&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;creators&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;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Use PostgreSQL full-text search&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &amp;amp; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// AND between terms&lt;/span&gt;
    &lt;span class="nx"&gt;creators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;`
      SELECT *, ts_rank(search_vector, to_tsquery('english', &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;searchQuery&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)) as rank
      FROM "Creator"
      WHERE search_vector @@ to_tsquery('english', &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;searchQuery&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="nx"&gt;platform&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;`AND platform = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;platform&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="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;``&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="nx"&gt;category&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;`AND category = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;category&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="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;``&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="nx"&gt;minFollowers&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;`AND followers &amp;gt;= &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minFollowers&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="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;``&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="nx"&gt;maxFollowers&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;`AND followers &amp;lt;= &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxFollowers&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="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      ORDER BY rank DESC
      LIMIT &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; OFFSET &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;offset&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;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;creators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;orderBy&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="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&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="nx"&gt;creators&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pagination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&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;span class="c1"&gt;// Lookup + auto-ingest a specific creator&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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/creators/:platform/:username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&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;creator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ingestCreator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&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="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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="na"&gt;error&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Creator DB API running on :3000&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;h2&gt;
  
  
  Step 3: Seed the Database
&lt;/h2&gt;

&lt;p&gt;You need initial data. Here's a script that ingests a list of known creators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// seed.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ingestCreator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ingest&lt;/span&gt;&lt;span class="dl"&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;SEED_CREATORS&lt;/span&gt; &lt;span class="o"&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;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;therock&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="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cristiano&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="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tiktok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charlidamelio&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="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tiktok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;khaby.lame&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Add more from your niche...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &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;creator&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;SEED_CREATORS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Ingesting &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&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="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ingestCreator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&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="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Rate limit&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&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="s2"&gt;`Failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&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="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;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Seeding complete.&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="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Search for fitness creators on Instagram with 10K-100K followers&lt;/span&gt;
curl &lt;span class="s2"&gt;"localhost:3000/api/creators/search?q=fitness&amp;amp;platform=instagram&amp;amp;minFollowers=10000&amp;amp;maxFollowers=100000"&lt;/span&gt;

&lt;span class="c"&gt;# Find beauty creators with 3%+ engagement rate&lt;/span&gt;
curl &lt;span class="s2"&gt;"localhost:3000/api/creators/search?category=beauty&amp;amp;minEngagement=3&amp;amp;sort=engagementRate"&lt;/span&gt;

&lt;span class="c"&gt;# Look up a specific creator (auto-fetches if not in DB)&lt;/span&gt;
curl &lt;span class="s2"&gt;"localhost:3000/api/creators/instagram/some_creator"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Beats Spreadsheets
&lt;/h2&gt;

&lt;p&gt;The moment you have 500+ creators in the database, spreadsheets become useless. You can't full-text search a CSV. You can't filter by engagement rate AND follower count AND niche in Google Sheets without losing your mind.&lt;/p&gt;

&lt;p&gt;With this setup, queries like "find me fitness creators on TikTok with 50K-200K followers and 4%+ engagement" take 3 milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Add Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Background refresh job&lt;/strong&gt; — cron that re-fetches the oldest profiles daily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate detection&lt;/strong&gt; — same creator on Instagram and TikTok, link them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export to CSV&lt;/strong&gt; — for people who still need spreadsheets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple frontend&lt;/strong&gt; — a search bar + filter sidebar + results grid&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Read the Full Guide
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://sociavault.com/blog/build-searchable-creator-database" rel="noopener noreferrer"&gt;Build a Creator Database → SociaVault Blog&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Build influencer databases with &lt;a href="https://sociavault.com" rel="noopener noreferrer"&gt;SociaVault&lt;/a&gt; — one API for profiles, posts, and engagement data across TikTok, Instagram, YouTube, and 10+ platforms.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;p&gt;If you've built a creator/influencer database before, what was your biggest pain point? Schema design? Keeping data fresh? Handling duplicates across platforms?&lt;/p&gt;

&lt;h1&gt;
  
  
  javascript #postgresql #api #webdev #database
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
