<?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>Forem: Brandon</title>
    <description>The latest articles on Forem by Brandon (@brandonkylebailey).</description>
    <link>https://forem.com/brandonkylebailey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F291056%2F07053b8e-8a99-4b07-a9f4-3c0e0117628b.jpg</url>
      <title>Forem: Brandon</title>
      <link>https://forem.com/brandonkylebailey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/brandonkylebailey"/>
    <language>en</language>
    <item>
      <title>The Only Developer Skill That Scales in 2026</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Fri, 28 Nov 2025 01:14:08 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/the-only-developer-skill-that-scales-in-2026-1km8</link>
      <guid>https://forem.com/brandonkylebailey/the-only-developer-skill-that-scales-in-2026-1km8</guid>
      <description>&lt;p&gt;Technical stacks shift. Hiring markets mutate. Tooling obsoletes itself every quarter. The only durable advantage is the ability to convert ambiguity into working systems faster than your peers. That isn’t creativity or talent. It’s process.&lt;/p&gt;

&lt;p&gt;Define the problem in one sentence. Strip every requirement that doesn't change the outcome. Identify the single constraint that governs the system. Build the minimum artifact that validates your assumption. Iterate only on what breaks. Eliminate everything else.&lt;/p&gt;

&lt;p&gt;This workflow outperforms brilliance, years of experience, and encyclopedic framework knowledge. It produces momentum anywhere: new jobs, new stacks, new domains. It reduces burnout by removing self-inflicted complexity. It turns pressure into throughput.&lt;/p&gt;

&lt;p&gt;Developers who internalize this stop chasing hype and stop fearing disruption. They become the stabilizing force teams rely on because they execute predictably in volatile conditions. That’s the multiplier. That’s the leverage.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>The Developer Extinction Line: Why 2026 Will Delete Half the Industry Without Warning</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Thu, 27 Nov 2025 02:49:12 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/the-developer-extinction-line-why-2026-will-delete-half-the-industry-without-warning-1bdn</link>
      <guid>https://forem.com/brandonkylebailey/the-developer-extinction-line-why-2026-will-delete-half-the-industry-without-warning-1bdn</guid>
      <description>&lt;p&gt;The fear isn’t abstract. It’s statistical. The market is oversupplied with engineers whose output is indistinguishable from an autocomplete model. The 2025 economy measures developers by throughput per dollar, and most can’t survive that math.&lt;/p&gt;

&lt;p&gt;The collapse is already visible:&lt;br&gt;
Companies are rewriting job scopes to remove everything slow, manual, or opinion-driven. AI-first workflows aren’t boosting productivity; they’re compressing entire tiers of developer competence into a single button. Entire backlogs are dissolving into promptable tasks. Teams are discovering that six engineers of average velocity can be replaced by one engineer with aggressive automation habits and no emotional friction.&lt;/p&gt;

&lt;p&gt;The fear comes from watching career moats evaporate:&lt;/p&gt;

&lt;p&gt;• Tool familiarity is worthless when interfaces abstract the tool.&lt;br&gt;
• “Experience” collapses when models surface edge cases faster than memory.&lt;br&gt;
• Code quality becomes a non-metric when scaffolding is autogenerated.&lt;br&gt;
• Architecture becomes a commodity when reasoning engines expose patterns instantly.&lt;br&gt;
• Hiring pipelines prefer operators who ship artifacts, not specialists who protect niches.&lt;/p&gt;

&lt;p&gt;The panic spikes when developers realize the new selection pressure:&lt;br&gt;
If your work doesn’t generate compounding leverage, you’re not just underperforming, you’re redundant. The market is rewarding engineers who behave like force multipliers, not function implementers. Anything that can be templated, documented, or recomposed by a model becomes valueless.&lt;/p&gt;

&lt;p&gt;2025 forces a binary outcome:&lt;br&gt;
Either your output creates more output, or your role shrinks until it disappears.&lt;br&gt;
Title:&lt;br&gt;
&lt;strong&gt;The Developer Extinction Line: Why 2025 Will Delete Half the Industry Without Warning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Post:&lt;br&gt;
The fear isn’t abstract. It’s statistical. The market is oversupplied with engineers whose output is indistinguishable from an autocomplete model. The 2025 economy measures developers by throughput per dollar, and most can’t survive that math.&lt;/p&gt;

&lt;p&gt;The collapse is already visible:&lt;br&gt;
Companies are rewriting job scopes to remove everything slow, manual, or opinion-driven. AI-first workflows aren’t boosting productivity; they’re compressing entire tiers of developer competence into a single button. Entire backlogs are dissolving into promptable tasks. Teams are discovering that six engineers of average velocity can be replaced by one engineer with aggressive automation habits and no emotional friction.&lt;/p&gt;

&lt;p&gt;The fear comes from watching career moats evaporate:&lt;/p&gt;

&lt;p&gt;• Tool familiarity is worthless when interfaces abstract the tool.&lt;br&gt;
• “Experience” collapses when models surface edge cases faster than memory.&lt;br&gt;
• Code quality becomes a non-metric when scaffolding is autogenerated.&lt;br&gt;
• Architecture becomes a commodity when reasoning engines expose patterns instantly.&lt;br&gt;
• Hiring pipelines prefer operators who ship artifacts, not specialists who protect niches.&lt;/p&gt;

&lt;p&gt;The panic spikes when developers realize the new selection pressure:&lt;br&gt;
If your work doesn’t generate compounding leverage, you’re not just underperforming, you’re redundant. The market is rewarding engineers who behave like force multipliers, not function implementers. Anything that can be templated, documented, or recomposed by a model becomes valueless.&lt;/p&gt;

&lt;p&gt;Either your output creates more output, or your role shrinks until it disappears.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>The 2025 Tech Layoff Cycle Isn’t the Real Threat, It’s What Comes After</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Wed, 26 Nov 2025 01:31:54 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/the-2025-tech-layoff-cycle-isnt-the-real-threat-its-what-comes-after-4cf7</link>
      <guid>https://forem.com/brandonkylebailey/the-2025-tech-layoff-cycle-isnt-the-real-threat-its-what-comes-after-4cf7</guid>
      <description>&lt;p&gt;Everyone keeps staring at layoff numbers, but that’s not what’s putting devs out of work. The real danger is simpler: hiring managers no longer believe most candidates can produce value without supervision. That single assumption is wiping out entire tiers of applicants overnight.&lt;/p&gt;

&lt;p&gt;This is why rejection feels instantaneous now. Recruiters aren’t hunting for promise. They’re hunting for proof you won’t drain time, context, or senior bandwidth. Anything that hints at uncertainty gets discarded. Side projects with no defined outcome. Resumes padded with tools instead of impact. Vague claims about collaboration. All auto-rejected.&lt;/p&gt;

&lt;p&gt;The fear you’re feeling is earned. The market has no patience left for “I can learn fast.” It wants evidence you’ve already solved something difficult without being told how. That’s the filter.&lt;/p&gt;

&lt;p&gt;If you’re unemployed in 2025, stop trying to impress. Start removing doubt.&lt;br&gt;
Cut your portfolio to one or two projects that demonstrate a clear constraint you identified, the exact intervention you made, and the measurable result. Rebuild your resume around operational clarity, not potential. Strip anything that reads like guesswork or aspiration.&lt;/p&gt;

&lt;p&gt;The market isn’t punishing lack of experience. It’s punishing lack of certainty.&lt;br&gt;
Title: The 2025 Tech Layoff Cycle Isn’t the Real Threat — It’s What Comes After&lt;/p&gt;

</description>
      <category>career</category>
      <category>beginners</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Devs Who Get Hired First Do One Thing Differently</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Mon, 24 Nov 2025 23:03:00 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/devs-who-get-hired-first-do-one-thing-differently-5eok</link>
      <guid>https://forem.com/brandonkylebailey/devs-who-get-hired-first-do-one-thing-differently-5eok</guid>
      <description>&lt;p&gt;Hiring managers operate under load. They scan for the smallest number of signals that predict whether you’ll remove friction instead of adding it. Everything else is noise.&lt;/p&gt;

&lt;p&gt;Rebuild your materials around that reality.&lt;br&gt;
Strip your portfolio to three projects maximum. For each one, document the constraint, the intervention, and the delta. No narratives. No tooling inventories. State the bottleneck, state the modification, state the measurable outcome. This re-frames you from “someone who built features” to “someone who resolves blockers.”&lt;/p&gt;

&lt;p&gt;Expose your reasoning process. Most candidates describe tasks; the ones who get hired describe tradeoffs. Show how you selected one approach while discarding alternatives. Show what you optimized for. Show what you intentionally ignored. This proves decision-making under scarcity, which is the actual job.&lt;/p&gt;

&lt;p&gt;Remove ambiguous claims. Replace “improved performance” with the exact metric. Replace “worked on APIs” with the exact failure point you corrected. Ambiguity signals inexperience. Specificity signals operational maturity.&lt;/p&gt;

&lt;p&gt;Consolidate everything into a single through-line: you compress time-to-output. That’s the trait that stands out in a stack of interchangeable resumes.&lt;/p&gt;

</description>
      <category>career</category>
      <category>programming</category>
      <category>productivity</category>
      <category>employeeexperience</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Mon, 24 Nov 2025 03:23:13 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/-18o0</link>
      <guid>https://forem.com/brandonkylebailey/-18o0</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/brandonkylebailey" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F291056%2F07053b8e-8a99-4b07-a9f4-3c0e0117628b.jpg" alt="brandonkylebailey"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/brandonkylebailey/why-juniors-arent-getting-hired-in-2026-and-the-one-strategy-that-flips-your-odds-overnight-5b1" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Why Juniors Aren’t Getting Hired in 2026 and the One Strategy That Flips Your Odds Overnight&lt;/h2&gt;
      &lt;h3&gt;Brandon ・ Nov 23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>career</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Why Juniors Aren’t Getting Hired in 2026 and the One Strategy That Flips Your Odds Overnight</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Sun, 23 Nov 2025 17:22:07 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/why-juniors-arent-getting-hired-in-2026-and-the-one-strategy-that-flips-your-odds-overnight-5b1</link>
      <guid>https://forem.com/brandonkylebailey/why-juniors-arent-getting-hired-in-2026-and-the-one-strategy-that-flips-your-odds-overnight-5b1</guid>
      <description>&lt;p&gt;Most junior candidates drown in noise because they follow outdated advice. Cut the fluff. Align to what hiring pipelines actually value now.&lt;/p&gt;

&lt;p&gt;Strip your portfolio to three pieces: one production-style service, one front-end build with clean UI discipline, one system-level or infra-touching project. Each must have a README that explains architecture, tradeoffs, and constraints. No tutorials. No clones. Demonstrate judgment, not syntax.&lt;/p&gt;

&lt;p&gt;Rewrite your Git history on each project until it resembles a disciplined engineer. Recruiters skim commit logs for signal. Chaotic diffs get discarded instantly.&lt;/p&gt;

&lt;p&gt;Publish implementation notes weekly. Not “learning logs.” Post breakdowns of decisions, failures, constraints you hit, and what you cut to ship. It frames you as an operator, not a student.&lt;/p&gt;

&lt;p&gt;Optimize for verifiable competence. Run all work through automated checks: formatting, static analysis, containerized local dev, reproducible builds. This mirrors how real teams operate and removes doubt about your baseline.&lt;/p&gt;

&lt;p&gt;Make your presence legible. LinkedIn headline: tech stack + role target + proof in one line. Pin repositories that match job descriptions. Hide everything else.&lt;/p&gt;

&lt;p&gt;Treat every application as an engineering task: gather requirements, ship artifacts that address them, cut non-essentials. Consistency beats volume.&lt;/p&gt;

&lt;p&gt;Stop waiting for permission. Produce evidence. Publish it. Reduce ambiguity. That’s the 2025–2026 hiring filter.&lt;br&gt;
Title: &lt;strong&gt;Stop Getting Ignored: The 2025–2026 Junior Dev Playbook That Actually Gets You Hired&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most junior candidates drown in noise because they follow outdated advice. Cut the fluff. Align to what hiring pipelines actually value now.&lt;/p&gt;

&lt;p&gt;Strip your portfolio to three pieces: one production-style service, one front-end build with clean UI discipline, one system-level or infra-touching project. Each must have a README that explains architecture, tradeoffs, and constraints. No tutorials. No clones. Demonstrate judgment, not syntax.&lt;/p&gt;

&lt;p&gt;Rewrite your Git history on each project until it resembles a disciplined engineer. Recruiters skim commit logs for signal. Chaotic diffs get discarded instantly.&lt;/p&gt;

&lt;p&gt;Publish implementation notes weekly. Not “learning logs.” Post breakdowns of decisions, failures, constraints you hit, and what you cut to ship. It frames you as an operator, not a student.&lt;/p&gt;

&lt;p&gt;Optimize for verifiable competence. Run all work through automated checks: formatting, static analysis, containerized local dev, reproducible builds. This mirrors how real teams operate and removes doubt about your baseline.&lt;/p&gt;

&lt;p&gt;Make your presence legible. LinkedIn headline: tech stack + role target + proof in one line. Pin repositories that match job descriptions. Hide everything else.&lt;/p&gt;

&lt;p&gt;Treat every application as an engineering task: gather requirements, ship artifacts that address them, cut non-essentials. Consistency beats volume.&lt;/p&gt;

&lt;p&gt;Stop waiting for permission. Produce evidence. Publish it. Reduce ambiguity. That’s the 2026 hiring filter.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>career</category>
      <category>beginners</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Sun, 23 Nov 2025 16:35:50 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/-5dgp</link>
      <guid>https://forem.com/brandonkylebailey/-5dgp</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/brandonkylebailey" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F291056%2F07053b8e-8a99-4b07-a9f4-3c0e0117628b.jpg" alt="brandonkylebailey"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/brandonkylebailey/stop-building-indie-saas-the-hard-way-26o2" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Stop Building Indie SaaS the Hard Way&lt;/h2&gt;
      &lt;h3&gt;Brandon ・ Nov 23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#saas&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Stop Building Indie SaaS the Hard Way</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Sun, 23 Nov 2025 01:32:10 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/stop-building-indie-saas-the-hard-way-26o2</link>
      <guid>https://forem.com/brandonkylebailey/stop-building-indie-saas-the-hard-way-26o2</guid>
      <description>&lt;p&gt;In my experience, developers burn months writing boilerplate, chasing config drift, and IaC before a single user arrives. The market rewards velocity, not heroics. Ship slower than your competitors and your idea dies, even if it's better.&lt;/p&gt;

&lt;p&gt;Here’s what I'm doing: Use a production-grade monorepo starter that forces discipline, standardization, and speed. TurboRepo plus a unified stack. Next.js for web, NestJS for backend and Expo for native. This delivers predictable builds, consistent types, shared UI libraries, and zero-friction DX. The result: one mental model, one dependency graph, one build pipeline.&lt;/p&gt;

&lt;p&gt;This structure eliminates 80% of early-stage chaos: mismatched TypeScript configs, duplicated components, fragmented API contracts, and slow local iterations. Shared packages consolidate types, UI primitives, and business logic. You stop gluing pieces together and start building business solutions.&lt;/p&gt;

&lt;p&gt;Most developers still opt for scattered repos, mismatched stacks, and brittle hand-rolled infra. That choice kills momentum. The fastest teams centralize everything, standardize aggressively, and automate the boring work out of existence.&lt;/p&gt;

&lt;p&gt;If you want leverage, the formula is: one repo, one toolchain, one source of truth. The devs who adopt this pattern dominate velocity metrics and out-ship teams twice their size.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>saas</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Zero to SaaS - Bookmarksy.io Working around Twitter API Limitations</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Tue, 29 Nov 2022 00:37:21 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/zero-to-saas-bookmarksyio-working-around-twitter-api-limitations-55p8</link>
      <guid>https://forem.com/brandonkylebailey/zero-to-saas-bookmarksyio-working-around-twitter-api-limitations-55p8</guid>
      <description>&lt;p&gt;One of the most important features of &lt;a href="http://bookmarksy.io" rel="noopener noreferrer"&gt;bookmarksy.io&lt;/a&gt; is it’s ability to reach out to Twitters API and retrieve a given users bookmarks.&lt;/p&gt;

&lt;p&gt;Getting a list of users bookmarks is the easy bit. You can make a call to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//api.twitter.com/2/users/:id/bookmarks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and what is returned is a list that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&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="s2"&gt;2022-11-11T18:18:36.000Z&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="s2"&gt;edit_controls&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="s2"&gt;edits_remaining&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is_edit_eligible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editable_until&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="s2"&gt;2022-11-11T18:48:36.000Z&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="s2"&gt;text&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="s2"&gt;Build a $50 info-product in one day using this process:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;(Probably shouldn't give this away for free bc later I'm going to sell a guide to this same process for ~$50.)&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="s2"&gt;public_metrics&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="s2"&gt;retweet_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;242&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reply_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;like_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1171&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quote_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&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="s2"&gt;Tweet Hunter Pro&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="s2"&gt;conversation_id&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="s2"&gt;1591133306069843979&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="s2"&gt;lang&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="s2"&gt;en&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="s2"&gt;id&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="s2"&gt;1591133306069843979&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="s2"&gt;reply_settings&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="s2"&gt;everyone&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="s2"&gt;possibly_sensitive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edit_history_tweet_ids&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="s2"&gt;1591133306069843979&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;includes&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="s2"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&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="s2"&gt;Rob Lennon 🗯 | Audience Growth&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="s2"&gt;username&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="s2"&gt;thatroblennon&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="s2"&gt;tweets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&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="s2"&gt;2022-11-11T18:18:36.000Z&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="s2"&gt;edit_controls&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="s2"&gt;edits_remaining&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is_edit_eligible&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editable_until&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="s2"&gt;2022-11-11T18:48:36.000Z&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="s2"&gt;text&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="s2"&gt;Build a $50 info-product in one day using this process:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;(Probably shouldn't give this away for free bc later I'm going to sell a guide to this same process for ~$50.)&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="s2"&gt;public_metrics&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="s2"&gt;retweet_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;242&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reply_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;like_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1171&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quote_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source&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="s2"&gt;Tweet Hunter Pro&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="s2"&gt;conversation_id&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="s2"&gt;1591133306069843979&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="s2"&gt;lang&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="s2"&gt;en&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="s2"&gt;id&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="s2"&gt;1591133306069843979&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="s2"&gt;reply_settings&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="s2"&gt;everyone&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="s2"&gt;possibly_sensitive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edit_history_tweet_ids&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="s2"&gt;1591133306069843979&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meta&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="s2"&gt;result_count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next_token&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="s2"&gt;zldjwdz3w6sba13nbs0z2lzml73coscn3mc18c0avxs&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s all fine and well. But if I’m dealing with a thread, which most bookmarks tend to be, how do I retrieve all the related tweets?&lt;/p&gt;

&lt;p&gt;The twitter documentation speaks of use of recent search or archive search for tweets. This isn’t viable for two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;recent search only works on tweets less than seven days old&lt;/li&gt;
&lt;li&gt;archive search is enrollment based and EXPENSIVE!.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So what’s the solution?  Here’s what I’ve implemented to work around this:&lt;/p&gt;

&lt;p&gt;assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bookmark is either a single tweet, or a collection (conversation: twitter language) of tweets.&lt;/li&gt;
&lt;li&gt;If a bookmark is a collection of tweets, it can be assumed that this bookmark is a thread.&lt;/li&gt;
&lt;li&gt;Thread tweets are typically created around the same time (roughly &amp;lt; 10 seconds between tweets).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;p&gt;Leverage the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//api.twitter.com/2/users/:id/tweets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;endpoint with a request body as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;since_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ID&lt;/span&gt; &lt;span class="na"&gt;OF&lt;/span&gt; &lt;span class="na"&gt;BOOKMARKED&lt;/span&gt; &lt;span class="na"&gt;TWEET&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;,
    end_time: &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CREATED_AT&lt;/span&gt; &lt;span class="na"&gt;OF&lt;/span&gt; &lt;span class="na"&gt;BOOKMARKED&lt;/span&gt; &lt;span class="na"&gt;TWEET&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="na"&gt;5&lt;/span&gt; &lt;span class="na"&gt;MINS&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;,
    limit: 100
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this will retrieve the first 100 tweets published within the first five minutes after the initial tweet.&lt;/p&gt;

&lt;p&gt;Then we can filter on those tweets by &lt;code&gt;conversation_id&lt;/code&gt; to retrieve the ones associated with our bookmark! &lt;/p&gt;

&lt;p&gt;With this solution, we work around the 7 day and archive restrictions. Is it ideal? of course not, but it’s been reliable so far! And unless your bookmark is a thread of over one hundred tweets, there doesn’t appear to be any data loss.&lt;/p&gt;

&lt;p&gt;The only drawback is that I will likely end up querying redundant tweets which would eat into my monthly query limit, but we will cross that bridge when we get to it! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://bookmarksy.io" rel="noopener noreferrer"&gt;https://bookmarksy.io&lt;/a&gt; is coming along nicely so far. Hit a couple of snag’s but I will be looking for beta testers soon! &lt;/p&gt;

&lt;p&gt;Follow the journey on twitter &lt;a href="https://twitter.com/bookmarksy_io" rel="noopener noreferrer"&gt;bookmarksy_io&lt;/a&gt; or &lt;a href="https://twitter.com/brandonkpbailey" rel="noopener noreferrer"&gt;brandonkpbailey&lt;/a&gt; and join the waitlist! &lt;a href="https://brandonkpbailey.substack.com/" rel="noopener noreferrer"&gt;https://brandonkpbailey.substack.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Zero to SaaS - Bookmarksy.io Initial Data Model approach</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Tue, 22 Nov 2022 02:52:22 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/zero-to-saas-bookmarksyioinitial-data-model-approach-e8e</link>
      <guid>https://forem.com/brandonkylebailey/zero-to-saas-bookmarksyioinitial-data-model-approach-e8e</guid>
      <description>&lt;p&gt;I haven't talked about the data model for &lt;a href="https://twitter.com/bookmarksy_io" rel="noopener noreferrer"&gt;bookmarksy.io&lt;/a&gt; yet but I spent yesterday evening building out the core data model for bookmarks/categories.&lt;/p&gt;

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

&lt;p&gt;First, the definitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bookmark - a twitter bookmark.&lt;/li&gt;
&lt;li&gt;Category - a group of bookmarks that share a domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User registers&lt;/li&gt;
&lt;li&gt;Categories service seeds their account with a default category "Uncategorized" (all bookmarks will initially exist as part of this category)&lt;/li&gt;
&lt;li&gt;Then, when a user reads their dashboard (these things happen asynchronously):

&lt;ul&gt;
&lt;li&gt;categories are loaded&lt;/li&gt;
&lt;li&gt;bookmarks are loaded&lt;/li&gt;
&lt;li&gt;bookmarks are hydrated (reach out to twitter to capture any new bookmarks)&lt;/li&gt;
&lt;li&gt;hydrated bookmarks are loaded into "uncategorized"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So far, this flow exists and is stable, with the exception of bookmark hydration. I have yet to come up with a non blocking, performant solution but am working on it!&lt;/p&gt;

&lt;p&gt;I think, given the potential variety of categories, having them account scoped makes the most sense. This will likely lead to some de-normalization but it's an acceptable trade off&lt;/p&gt;

&lt;p&gt;If you're like me and have a tonne of bookmarks you never read, check &lt;br&gt;
&lt;a href="https://bookmarksy.io" rel="noopener noreferrer"&gt;https://bookmarksy.io&lt;/a&gt;&lt;br&gt;
 out and join the &lt;a href="https://brandonkpbailey.substack.com/" rel="noopener noreferrer"&gt;waitlist!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Zero to SaaS - Bookmarksy.io Sign in with Twitter</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Tue, 15 Nov 2022 22:54:16 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/zero-to-saas-bookmarksyio-sign-in-with-twitter-4jg4</link>
      <guid>https://forem.com/brandonkylebailey/zero-to-saas-bookmarksyio-sign-in-with-twitter-4jg4</guid>
      <description>&lt;p&gt;So at the end of week 2 of &lt;a href="https://bookmarksy.io" rel="noopener noreferrer"&gt;https://bookmarksy.io&lt;/a&gt; ‘s development, I now have a (semi) functioning frontend and backend!&lt;/p&gt;

&lt;p&gt;I’ll do a technical breakdown of each component in a future post but would like to spend some time talking about the challenges I faced integration twitter authentication.&lt;/p&gt;

&lt;p&gt;Initially, I spent some time familiarizing myself with the authorization and authentication flows twitter exposes for app clients. &lt;/p&gt;

&lt;p&gt;The type of app client I’m working with means my best option for authorization is OAuth2.0 with PKCE.&lt;/p&gt;

&lt;p&gt;Q. What is OAuth 2 with PKCE?&lt;/p&gt;

&lt;p&gt;A. OAuth 2.0 is the industry-standard protocol for authorization. Paired with PKCE (Proof key for code exchange), it helps prevent CSRF (Cross Site Request Forgery) and authorization code injection attacks.&lt;/p&gt;

&lt;p&gt;Here’s a diagram of the flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjireqzp0ibbk5vuq8djj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjireqzp0ibbk5vuq8djj.png" alt="Image description" width="800" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A typical user story would look something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User clicks sign in with twitter&lt;/li&gt;
&lt;li&gt;User is redirected to &lt;a href="https://www.notion.so/1ae3645697cf4cc4a4221c60b96b0591" rel="noopener noreferrer"&gt;https://twitter.com/i/oauth2/authorize?response_type=code&amp;amp;client_id={SOME CLIENT ID}&amp;amp;redirect_uri={SOME CALLBACK URL}&amp;amp;scope={SOME LIST OF SCOPES TO AUTHORIZE}&amp;amp;state={SOME STATE CODE}&amp;amp;code_challenge={SOME CODE CHALLENGE}&amp;amp;code_challenge_method={SOME CODE CHALLENGE METHOD}&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;user accepts authorization request&lt;/li&gt;
&lt;li&gt;user is redirected to {SOME CALLBACK URL} containing code and state in query string. e.g:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://example.com?code={SOME" rel="noopener noreferrer"&gt;https://example.com?code={SOME AUTHORIZATION CODE}&amp;amp;state={SOME AUTHORIZATION STATE}&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Web app then requests an access token on behalf of the user using the code returned. E.g:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.twitter.com/2/oauth2/token'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Basic {SOME AUTH TOKEN FOR YOUR APP}'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'code={YOUR CODE RETURNED FROM CALLBACK}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'grant_type=authorization_code'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'redirect_uri=https://www.example.com'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'code_verifier=challenge'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Access token is stored and used to make API requests.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The web app can revoke an access token like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="s1"&gt;'https://api.twitter.com/2/oauth2/revoke'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Basic {SOME AUTH TOKEN FOR YOUR APP}'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'token={SOME ACCESS TOKEN TO REVOKE}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, I tried to implement this by hand with the physical endpoints, which worked fine after some time, but after stumbling upon the twitter API SDK, I found a much more elegant solution &lt;a href="https://github.com/twitterdev/twitter-api-typescript-sdk" rel="noopener noreferrer"&gt;https://github.com/twitterdev/twitter-api-typescript-sdk&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So now the code looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// create an oauth2 client&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuth2User&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;client_id&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;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client_secret&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;CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000/callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;scopes&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="s2"&gt;tweet.read&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="s2"&gt;users.read&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="s2"&gt;offline.access&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// redirect client to this url&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateAuthURL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;code_challenge_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s256&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;// after authorization, retrieve code from query param&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// revoke token&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;authClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeAccessToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which works wonderfully! &lt;/p&gt;

&lt;p&gt;My only concern at the moment is that the &lt;code&gt;revokeAccessToken&lt;/code&gt; doesn’t use a parsed access token, so I’m unsure as to how exactly it knows which token to revoke access to and whether this will impact multiple users.&lt;/p&gt;

&lt;p&gt;But otherwise, &lt;a href="https://twitter.com/bookmarksy_io" rel="noopener noreferrer"&gt;https://twitter.com/bookmarksy_io&lt;/a&gt; (&lt;a href="https://bookmarksy.io" rel="noopener noreferrer"&gt;https://bookmarksy.io&lt;/a&gt;) is coming along well! &lt;/p&gt;

&lt;p&gt;Follow the journey on twitter &lt;a href="https://twitter.com/bookmarksy_io" rel="noopener noreferrer"&gt;bookmarksy_io&lt;/a&gt; or &lt;a href="https://twitter.com/brandonkpbailey" rel="noopener noreferrer"&gt;brandonkpbailey&lt;/a&gt; and join the waitlist! &lt;a href="https://brandonkpbailey.substack.com/" rel="noopener noreferrer"&gt;https://brandonkpbailey.substack.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Zero to SaaS - Product #1 Bookmarksy.io the solution</title>
      <dc:creator>Brandon</dc:creator>
      <pubDate>Sun, 06 Nov 2022 13:00:00 +0000</pubDate>
      <link>https://forem.com/brandonkylebailey/zero-to-saas-product-1-bookmarksyio-the-solution-1h7f</link>
      <guid>https://forem.com/brandonkylebailey/zero-to-saas-product-1-bookmarksyio-the-solution-1h7f</guid>
      <description>&lt;p&gt;The idea for &lt;a href="http://Bookmarksy.io" rel="noopener noreferrer"&gt;Bookmarksy.io&lt;/a&gt; started with a simple statement:&lt;/p&gt;

&lt;p&gt;“I wish I could get an email of my twitter bookmarks every week so I didn’t stop forgetting them. Something like The Morning Brew” &lt;/p&gt;

&lt;p&gt;So I went on the internet searching for a solution that might already exist that solves my problem. It didn’t. So I went to work.&lt;/p&gt;

&lt;p&gt;Setting up a domain name, email list, landing page, social media, The whole nine yards. But having spoken to some people about this problem, I realize there’s something more here.&lt;/p&gt;

&lt;p&gt;It isn’t just difficult to remember to re-visit your twitter bookmarks, the entire concept of bookmarks in twitter is broken! A single stream of un-contextualized tweets or threads, sorted in order of most recently added to least recently added. &lt;/p&gt;

&lt;p&gt;A feed that would allow you to categorize, tag and sort these bookmarks more effectively would be much more useful.&lt;/p&gt;

&lt;p&gt;After some brainstorming, here’s the feature list I’d like to go to launch with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bookmark aggregation/consolidation

&lt;ul&gt;
&lt;li&gt;Threads condensed to a single, blog post like display with likes, retweets and replies information&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Contextualization

&lt;ul&gt;
&lt;li&gt;Adding categorization and tagging to bookmarks for storage and retrieval&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Bookmark habits/ Analytics

&lt;ul&gt;
&lt;li&gt;Am I actually using my bookmarks in a meaningful way?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Daily/ Weekly roundup emails

&lt;ul&gt;
&lt;li&gt;The Morning Brew style emails of my bookmarks for low-effort consumption.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Bookmark cleanup

&lt;ul&gt;
&lt;li&gt;Once I’ve processed my bookmarks in Bookmarksy.io, I can clean them up from my twitter feed.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;What do you think? Is this a good idea? Is this something you would use?&lt;/p&gt;

&lt;p&gt;As always, if you’d like to be the first to receive updates, sign up to our waitlist &lt;a href="https://bookmarksy.io" rel="noopener noreferrer"&gt;https://bookmarksy.io&lt;/a&gt; !&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
