<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Developer Tools - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Developer Tools - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 16:30:03 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/developer-tools/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Software Factory with Claude Code: From Vibe Coding to Agentic Development ]]>
                </title>
                <description>
                    <![CDATA[ AI coding tools now offer much more than autocomplete. They can analyze your codebase, edit multiple files, execute commands, explain errors, generate tests, write documentation, and prepare pull requ ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-software-factory-with-claude-code/</link>
                <guid isPermaLink="false">6a106a2f1f237623ea0336d3</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ agentic AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ claude ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Qudrat Ullah ]]>
                </dc:creator>
                <pubDate>Fri, 22 May 2026 14:37:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/9dba291f-c5b1-4c0c-99a6-44941e60f014.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>AI coding tools now offer much more than autocomplete. They can analyze your codebase, edit multiple files, execute commands, explain errors, generate tests, write documentation, and prepare pull request summaries. For small tasks, these capabilities are impressive. When you ask Claude Code, Cursor, or Copilot to explain a function, clean up a component, write a utility, or fix a clear bug, the process often feels seamless.</p>
<p>However, developing significant features presents different challenges.</p>
<p>A complete feature involves more than code. It requires product rules, architectural decisions, edge case handling, tests, security checks, review standards, and delivery constraints. As features grow, a single AI session must manage increasing complexity.</p>
<p>This is where the workflow begins to strain.</p>
<p>For example, you might ask your AI assistant to add invoice reminders to a SaaS billing application. Initially, it performs well: inspecting the invoice model, identifying the email service, recognizing the background worker, proposing a plan, and implementing changes. You approve permissions and edits, it runs tests, resolves errors, and updates the summary.</p>
<p>As the session progresses, complexity increases.</p>
<p>The AI must now track the original business rule, tenant boundaries, retry behavior, modified files, added tests, corrected constraints, and instructions on what not to change. While progress remains faster than before, the workflow becomes less organized.</p>
<p>You review the plan again, approve additional edits, identify missing constraints, reiterate rules, request file checks, rerun tests, and examine the diff. You begin to question whether the implementation still aligns with the original intent.</p>
<p>The AI is not failing due to lack of capability; it struggles because the workflow lacks sufficient structure.</p>
<p>A single extended conversation attempts to serve as product analyst, architect, backend engineer, frontend engineer, test engineer, reviewer, and release assistant simultaneously. While this may suffice for small tasks, it becomes unreliable when features involve complex business rules and production risks. Many developers overlook this transition.</p>
<p>Advancing AI-assisted development requires more than improved prompts; it involves designing a more effective system around the model.</p>
<p>If this scenario resonates with you, it does not reflect a lack of skill with AI. Instead, it indicates that your workflow may not be well-suited to the tool.</p>
<p>I am Qudrat Ullah, a tech lead based in London. I collaborate with engineering teams delivering production software and have observed how AI coding tools are transforming daily workflows. In this handbook, I will share practical insights to help you evolve your approach. By the end, you will move beyond repetitive setups and begin building your own software factory. Effective solutions start small and develop over time; avoid aiming for a comprehensive solution in a single day. Start small and continue to grow.</p>
<p>This handbook outlines the workflow I wish I had received when I started using AI for production code. By the end, you will be able to establish your own small software factory, a structured approach to using AI for planning, building, testing, and reviewing features while maintaining control of your codebase.</p>
<h2 id="heading-what-youll-learn">What You'll learn</h2>
<ul>
<li><p>How AI-assisted development actually evolved, and what the shape of that history tells you about where it is going.</p>
</li>
<li><p>Why "just ask the AI" stops working as soon as a project gets real, and what to do instead.</p>
</li>
<li><p>The five layers of an AI-assisted workflow: context, knowledge, agents, workflows, and delivery.</p>
</li>
<li><p>How to use Claude Code's building blocks (<code>CLAUDE.md</code>, skills, subagents, hooks) and let Claude itself generate most of them for you. (You can use any tool. The concepts are the same. I picked one tool for simplicity.)</p>
</li>
<li><p>How to build a working set of seven specialized agents and an orchestrator that chains them together.</p>
</li>
<li><p>A hands-on setup you can copy into any Next.js or Node.js project this weekend. If you understand the concepts, you can apply them to any project.</p>
</li>
<li><p>What I deliberately left out, and where to learn it next.</p>
</li>
</ul>
<h2 id="heading-who-this-is-for">Who this is For</h2>
<p>This guide is accessible to developers new to Claude Code or any AI tool, yet comprehensive enough for senior engineers or tech leads to benefit from the workflow patterns, orchestrator design, review checklist, and delivery section.</p>
<p>Examples reference Next.js, Node.js, and a SaaS billing application, but the concepts are tool-agnostic. Whether you use Cursor, Claude, Aider, Windsurf, Kilo, Cline, or future tools, the same principles apply.</p>
<h2 id="heading-what-youll-be-able-to-build-by-the-end">What You'll Be Able to Build by the End</h2>
<ul>
<li><p>A <code>CLAUDE.md</code> that captures your project's facts and standards.</p>
</li>
<li><p>Seven custom subagents that do focused work in their own context: researcher, story writer, spec writer, backend builder, frontend builder, test verifier, and validator.</p>
</li>
<li><p>One orchestrator (first as a skill, then optionally as an agent) that delegates work across those seven sub agents.</p>
</li>
<li><p>One reusable skill that encodes a workflow your team runs repeatedly.</p>
</li>
<li><p>One pre-commit hook for safety.</p>
</li>
<li><p>A short PR review checklist to ensure AI-generated pull requests are reviewed against the same standards every time.</p>
</li>
</ul>
<p>This is what a "software factory" means in practice. A factory can be scaled to your needs. It is not a large autonomous system, but rather a small set of files in your repository that enables one developer and one AI to function as a coordinated team.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<h3 id="heading-part-1-foundations-before-the-factory"><strong>Part 1: Foundations Before the Factory</strong></h3>
<ul>
<li><p><a href="#heading-1-how-ai-assisted-development-evolved">1. How AI-Assisted Development Evolved</a></p>
</li>
<li><p><a href="#heading-2-why-vibe-coding-breaks-down">2. Why Vibe Coding Breaks Down</a></p>
</li>
<li><p><a href="#heading-3-the-five-layers-of-an-ai-assisted-workflow">3. The Five Layers of an AI-Assisted Workflow</a></p>
</li>
<li><p><a href="#heading-4-the-context-layer-explore-before-you-build">4. The Context Layer: Explore Before You Build</a></p>
</li>
<li><p><a href="#heading-5-the-knowledge-layer-claudemd-skills-and-hooks">5. The Knowledge Layer: CLAUDE.md, Skills, and Hooks</a></p>
</li>
</ul>
<h3 id="heading-part-2-build-the-agent-factory"><strong>Part 2: Build the Agent Factory</strong></h3>
<ul>
<li><p><a href="#heading-6-the-agent-layer-seven-agents-that-do-focused-work">6. The Agent Layer: Seven Agents That Do Focused Work</a></p>
</li>
<li><p><a href="#heading-7-the-workflow-layer-the-orchestrator-that-runs-the-chain">7. The Workflow Layer: The Orchestrator That Runs the Chain</a></p>
</li>
<li><p><a href="#heading-8-the-delivery-layer-prs-reviews-and-the-new-sdlc">8. The Delivery Layer: PRs, Reviews, and the New SDLC</a></p>
</li>
<li><p><a href="#heading-9-build-your-first-claude-powered-software-factory">9. Build Your First Claude-Powered Software Factory</a></p>
</li>
</ul>
<h3 id="heading-part-3-wrap-up"><strong>Part 3: Wrap Up</strong></h3>
<ul>
<li><p><a href="#heading-10-what-i-did-not-cover-and-where-to-go-next">10. What I Did Not Cover (and Where to Go Next)</a></p>
</li>
<li><p><a href="#heading-11-closing-thoughts">11. Closing Thoughts</a></p>
</li>
</ul>
<h2 id="heading-part-1-foundations-before-the-factory">Part 1: Foundations Before the Factory</h2>
<p>Before building a factory, it is important to understand the current landscape, why existing workflows break down, and the foundational elements required. The first five sections establish this groundwork; construction begins in Section 6.</p>
<h2 id="heading-1-how-ai-assisted-development-evolved">1. How AI-Assisted Development Evolved</h2>
<p>Before building anything, it is helpful to understand the progression of AI in coding. This evolution occurred in few stages, with each stage addressing a specific problem and enabling the next.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/e48786a4-d3f3-42a6-a641-f823648ea905.png" alt="e48786a4-d3f3-42a6-a641-f823648ea905" width="2172" height="724" loading="lazy">

<p><em>Figure 1: Five stages of AI in coding, leading to today's software factory shift.</em></p>
<h3 id="heading-manual-coding">Manual Coding</h3>
<p>In the early workflow, you wrote everything by hand. The editor highlighted the text but did not understand it. You looked things up in books, in docs, on Stack Overflow, then slowly shaped the application line by line. This produced strong developers because every detail had to pass through their heads, but it placed a hard cap on what one person could ship in a week.</p>
<h3 id="heading-smart-editors">Smart editors</h3>
<p>Then the editors got useful. IntelliSense, language servers, ESLint, snippet engines, refactoring tools. None of these wrote code for you, but they removed friction inside the file you were already editing. This was the first stage at which developers began to expect the editor to help. It changed the baseline.</p>
<h3 id="heading-smart-autocomplete">Smart Autocomplete</h3>
<p>Tabnine and early versions of GitHub Copilot looked at nearby code and predicted what would come next. If you started writing a function <code>calculateInvoiceTotal(items)</code>, the tool guessed you wanted to loop over items, multiply quantity by price, and return a total. The editor was no longer completing syntax. It was completing intent. But you still owned the design.</p>
<h3 id="heading-chat-ai">Chat AI</h3>
<p>Then chat-based AI arrived, and the workflow split in half. You opened ChatGPT or Claude in another tab and asked for a login page or a registration API. Useful for boilerplate. Bad for anything that depended on your real folder structure, your auth flow, your database schema, or your team's decisions. The generated code looked correct in isolation, but broke when you pasted it in. It helped you draft something initially without typing.</p>
<h3 id="heading-ai-in-the-ide">AI in the IDE</h3>
<p>Cursor, Claude Code, Copilot Chat, Windsurf, Aider. These closed that gap. The AI could now inspect files, suggest edits across the project, run commands, and help with multi-file work. Instead of "write me a React component," you could ask, "Look at our existing dashboard widgets and add a new metric card in the same style." Much more powerful, because the AI is no longer working from a blank page. This is also the start of vibe coding. You vibe with the AI, it makes changes, you keep going. A lot of people are doing that today and getting real leverage from it.</p>
<p>That power is changing how software is built, but the industry is already moving in another direction. Let's look at what breaks in the vibe coding model.</p>
<h2 id="heading-2-why-vibe-coding-breaks-down">2. Why Vibe Coding Breaks Down</h2>
<p>Vibe coding is the workflow most developers fall into in the first week they use an AI IDE. You ask for a feature. The AI writes code. Something breaks. You paste the error. The AI patches it. Something else breaks. You ask again. Round and round.</p>
<p>On day one, this feels fast. You can build a landing page in fifteen minutes. You can sketch a prototype in an afternoon. Real progress.</p>
<p>On day thirty, the loop turns painful. The same logic appears in three places. The AI has forgotten the convention you set up two weeks ago. New features step on old ones. Tests are missing or shallow. The app works today, then breaks tomorrow because one prompt removed a guard you forgot existed. You are now spending more time supervising the AI than you used to spend writing code yourself.</p>
<p>There are techniques that make this better. Writing better prompts. Maintaining good docs. Keeping the context tight. I covered some of those in <a href="https://www.freecodecamp.org/news/how-to-unblock-ai-pr-review-bottleneck-handbook/">my previous article on unblocking the AI PR review bottleneck</a>. Those techniques help, but a single session still drifts when too many jobs land in the same conversation, and that's the challenge we are going to solve.</p>
<h3 id="heading-the-deeper-problem-one-chat-too-many-jobs">The Deeper Problem: One Chat, Too Many Jobs</h3>
<p>If you watch a real engineering team for a day, you notice that different people have different responsibilities. A product person clarifies the user problem. A senior engineer thinks about architecture. A backend developer designs the API. A frontend developer builds the interface. A test engineer thinks about edge cases. A reviewer decides whether the work fits the codebase.</p>
<p>When you point one AI session at "build the feature," you collapse all of those roles into one conversation. The AI plans, designs, codes, tests, and reviews its own work in the same messy context. That is risky because mistakes compound. A wrong assumption in the plan becomes a wrong database model. A wrong database model becomes a wrong API. A wrong API becomes a wrong UI. By the time you notice, the mistake has spread through the whole feature.</p>
<p>You may start thinking the next stage of AI-assisted development is better prompts. No, it is not, It is a better system.</p>
<p>Use AI to automate structured work, not chaotic work. If your team has no standards, AI will generate inconsistent code faster. If your tests are weak, AI will produce fragile features faster. If your review process is vague, AI will let important risks through faster.</p>
<p>That single idea drives everything that follows.</p>
<h2 id="heading-3-the-five-layers-of-an-ai-assisted-workflow">3. The Five Layers of an AI-Assisted Workflow</h2>
<p>Before we get into specifics, here is the mental model this article uses. A working AI-assisted workflow has five layers that stack. Each one only works as well as the one below it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/752ad70c-8ef7-4b51-b9f8-9b719bf4fe85.png" alt="752ad70c-8ef7-4b51-b9f8-9b719bf4fe85" style="display:block;margin:0 auto" width="2172" height="724" loading="lazy">

<p><em>Figure 2: The five layers. Each one feeds the next; the whole stack is your software factory.</em></p>
<p>At the bottom is the Context Layer, which is what the AI can see in the current message. Above that sits the Knowledge Layer, which is the persistent project memory the AI inherits at the start of every session. Memory management itself is a huge topic we will cover in a future article (centralized memory, shared knowledge stores, and so on). For now, rely on Claude's session memory. The Agent Layer turns that knowledge into focused workers with their own tools and their own context windows. The Workflow Layer puts an orchestrator on top of those agents and chains them into a real pipeline with validation gates and human approval points. The Delivery Layer is how everything that comes out of the pipeline reaches production safely: pull requests, a review checklist, and CI gates.</p>
<p>If you invest in only one layer, the others remain weak. A team with great agents but no shared <code>CLAUDE.md</code> ends up with inconsistent code. A team with great context discipline but no validation gates ships fragile features fast. The whole point of the model is that you build all five, even if you start small in each one. Also, one more important tip across the teams use same AI and tools for better and consistent results.</p>
<p>Before you build the factory, understand the foundations first.</p>
<p>This article is split into two halves on purpose.</p>
<p>Part 1 (Sections 4 and 5) covers the foundations. Context management. <code>CLAUDE.md</code>. Skills. Hooks. These are not the factory. These are the things you have to understand before the factory can stand on top of them. If you skip them and jump straight to building agents, the factory looks impressive for a week and then falls over. The agents will inherit a messy context. The orchestrator will route work that lacks clear rules. The validator will have nothing to validate against.</p>
<p>Part 2 (Sections 6, 7, 8, and 9) is where you actually build the factory. Seven specialized agents. An orchestrator that runs the chain. A delivery layer that gets the output to production. A hands-on section that wires it all together in your own repo.</p>
<p>A note on Part 1. You might read Sections 4 and 5 and think, "This is still me typing prompts. This is still vibe coding with extra steps." That is fair on the surface, and I want to address it directly. The habits in Part 1 are not the factory. They are the discipline that makes the factory possible. The exploration workflow you do by hand in Section 4 is the same workflow your codebase-researcher agent will automate in Section 6. The <code>CLAUDE.md</code> you write in Section 5 is what every agent will read at the start of every task. Part 1 teaches you the moves. Part 2 teaches the machine to make them for you.</p>
<p>If you already practice good context hygiene and have a <code>CLAUDE.md</code> you trust, skim Part 1 and head straight to Section 6. If you do not, take the time. The factory is only as good as what it stands on.</p>
<h2 id="heading-4-the-context-layer-explore-before-you-build">4. The Context Layer: Explore Before You Build</h2>
<p>Context is the AI's working memory. It is your prompt, the files you opened, the previous messages, your project rules, the documentation you injected, the terminal output, and the errors. Anything else the model can see while it is helping you.</p>
<p>Senior engineers carry a lot of project knowledge in their heads. They know why a decision was made, where the risky files live, which patterns the team follows, and what should not be touched. AI does not automatically know any of that. It only knows what is in its context.</p>
<p>Even with very large context windows, more is not better. Too much uncontrolled context makes the model worse. It mixes old decisions with new ones. It follows an outdated file pattern. It carries forward a wrong assumption that you corrected three messages ago. The goal is not to give the AI everything. The goal is to give it the right information at the right time which save computing time and cost both.</p>
<h3 id="heading-habit-1-explore-before-you-build">Habit 1: Explore before you build</h3>
<p>The single biggest mistake developers make with AI in the IDE is asking for code as the first move. The AI accepts the prompt, makes guesses to fill the gaps in your description, and starts generating. That is when bad designs sneak in. Strongly recommend avoid that.</p>
<p>A better move is to treat the first phase as exploration, not implementation. You are not asking the AI to build anything yet. You are asking it to read the existing code and tell you what is there. During this process you will observe AI will discover things which it finalize wrong initially.</p>
<p>Concrete example. Imagine you run a SaaS billing platform built with Next.js (App Router) on the frontend and Node.js services on the backend. The app has customers, subscriptions, invoices, a webhook handler that updates payment status, and a Resend integration for transactional email. You want to add reminder emails for unpaid invoices.</p>
<p>If you tell Claude Code, "add invoice reminders," you are gambling. It might do something reasonable. It might also create a new scheduler when you already have one, send reminders to customers who already paid, ignore timezone handling, hardcode business rules into the API route, or skip audit logs entirely. None of that is the AI being bad. It is the AI guessing because you asked it to.</p>
<p>Here is the controlled version, step by step.</p>
<p><strong>Step 1.</strong> Open Claude Code in plan mode and start with a read-only prompt. The goal is to make the AI describe the relevant parts of your codebase before any code is written.</p>
<pre><code class="language-text">I want to add reminder emails for invoices that have been unpaid
for more than 7 days. Before suggesting anything, please:

1. Read the invoice, payment, and email-sending code in this repo.
2. Tell me how invoices are created and where their status is stored.
3. Tell me how transactional emails are sent today.
4. Tell me whether we already have a background job system or scheduler.
5. List the files that would most likely change if we added reminders.

Do not write any code yet. I want a clear map first.
</code></pre>
<p>The prompt above can be written in many ways. Also can references docs folder if <a href="http://CLAUDE.md">CLAUDE.md</a> does not have clear mapping or you want to give more context to the AI for better results. The purpose is to show the shape: ask for understanding before action.</p>
<p><strong>Step 2.</strong> Read the response carefully. This is the moment to spot wrong assumptions while they are cheap to fix. If the AI says "I will use cron," but you actually have BullMQ workers running, correct that now. Because during codebase discovery it's possible it has not discovered BullMQ code and that information is in your head.</p>
<p><strong>Step 3.</strong> Once the map is right, ask for options, not code. You want a small comparison, not a solution.</p>
<pre><code class="language-text">Based on what you just found, suggest 3 ways we could implement
invoice reminders.

For each option, explain:

- how it would work end-to-end
- which existing parts of the system it reuses
- which new files or DB changes it needs
- the main risks (timezone, multi-tenant, retries, deduplication)
- Which option would you recommend and why

Do not edit any files yet.
</code></pre>
<p><strong>Step 4.</strong> Pick one option, then ask Claude Code to write a one-page brief: goal, approach, business rules, data model changes, tests needed, edge cases, open risks. Read the brief in under a minute. If something is missing, ask for a revision before moving on.</p>
<p><strong>Step 5.</strong> Open a fresh Claude Code session and paste only the brief into it. This is the move most people skip. During exploration, the AI discussed multiple options. Some were rejected. Some were partially correct. You do not want all that noise carried forward when implementation starts. A clean session means a clean context.</p>
<p><strong>Step 6.</strong> Ask about the new session's implementation plan and read it slowly. Look for things like "we will store processed invoice IDs in memory." That is a red flag. Memory is lost on restart and is not shared across multiple servers, so the same reminder could be sent twice. Catching that in the plan costs five minutes. Catching it after Claude has changed ten files costs an afternoon.</p>
<p><strong>Step 7.</strong> Build, then ask Claude to explain back. After the implementation, do not blindly commit. Ask the AI to walk you through the important decisions, list the tests it added, and update the docs with anything operators need to know. Trust but verify.</p>
<p>The shape of this workflow is:</p>
<p><code>inspect → compare options → pick approach → write brief → start clean → plan → review → build → explain back</code></p>
<p>Compare that to the vibe-coding shape: <code>prompt → generate → run → paste error → repeat</code>. The first one is controlled progress. The second is accidental progress, which does not scale.</p>
<p>This whole workflow is what you do today, by hand. In Section 7, you will see how an orchestrator can run most of it for you while you only step in at the review points.</p>
<h3 id="heading-habit-2-watch-for-context-drift">Habit 2: Watch for Context Drift</h3>
<p>Even with a clean start, bad information can sneak into a long session. Once a wrong assumption enters the context, the model keeps building on top of it. I call this context drift, and it is the most common reason a working session quietly produces a broken codebase. One small wrong assumption can spread across many files before you notice.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/240b1d48-4181-43dc-8f68-378e562ce67f.png" alt="240b1d48-4181-43dc-8f68-378e562ce67f" style="display:block;margin:0 auto" width="2172" height="724" loading="lazy">

<p><em>Figure 3: How a vague prompt drifts into spreading damage, and the only reliable way out.</em></p>
<p>A real example. You give Claude this prompt:</p>
<blockquote>
<p>Add subscription management to our SaaS. Users should be able to create a subscription and cancel it later.</p>
</blockquote>
<p>That prompt is too broad. The AI guesses ownership and creates something like:</p>
<pre><code class="language-text">User
└── Subscription
      ├── planName
      ├── status
      └── renewalDate
</code></pre>
<p>Looks fine on the surface. Then you remember your real business rule: a company account has many users, and the subscription belongs to the company, not the individual user. That difference is huge, and the AI has already designed around the wrong owner.</p>
<p>If you only say "no, subscriptions belong to companies," Claude tries to patch. You end up with both <code>user.subscriptionId</code> and <code>company.subscriptionId</code> floating around, defensive comments where they should not exist, and renamed code that still behaves like the old design.</p>
<blockquote>
<p><strong>Rule of thumb:</strong> If the AI makes a small typo, correct it inline. If it makes a wrong architectural assumption, throw the conversation away and start a new session with a stronger prompt. Small mistakes can be patched. Deep design mistakes should not be patched inside a polluted conversation.</p>
</blockquote>
<p>The cleaner move is to discard the chat, edit your original prompt, and start over with the rule baked in:</p>
<pre><code class="language-text">We need subscription management for our SaaS.

Important business rules:
- Subscriptions belong to a company account, not an individual user.
- A company can have many users.
- Only company admins can change the subscription.
- Billing history is visible to admins only.
- Cancelled subscriptions remain active until the end of the billing period.

Before writing code, inspect our existing account, user, and billing models.
Then suggest an implementation plan. Do not edit files yet.
</code></pre>
<p>Now the AI starts from the correct mental model. The first version is a guess. The second version is a design.</p>
<h3 id="heading-habit-3-pin-the-ai-to-your-installed-versions">Habit 3: Pin the AI to your installed versions</h3>
<p>Models know a lot, but they do not always know the exact version of your framework, your library, or your team standard. Sometimes they answer from older training data. Sometimes they give you a generic answer that worked in a tutorial three years ago and does not fit your project today.</p>
<p>A better prompt forces the AI to ground itself in your real installed versions:</p>
<pre><code class="language-text">Before writing code, inspect this project's structure and package.json.

This project uses Next.js App Router. Use the authentication library
version that is actually installed. Look up the current docs for that
specific version. Then explain the recommended file structure before
editing anything.
</code></pre>
<p>Same idea for Tailwind versions, Stripe SDK versions, Prisma migrations, React 18 vs 19 differences. Anywhere there is a real version-to-pattern dependency, make the AI ground itself in your installed versions and the current docs, not its training memory. Without it, the model produces average internet code and keep fixing errors and after a while will reach to correct information. With it, the model produces code that fits your project.</p>
<p>A useful tool here is <strong>Context7.</strong> It is a plugin that fetches the current docs for the exact installed version of each library. You can install it in Claude Code and reference it in your prompts or knowledge files so the model always pulls current docs before writing code. I use it regularly.</p>
<h2 id="heading-5-the-knowledge-layer-claudemd-skills-and-hooks">5. The Knowledge Layer: CLAUDE.md, Skills, and Hooks</h2>
<p>The Context Layer covers a single conversation. The Knowledge Layer covers everything that survives between conversations. This is where most teams' AI workflows quietly fail. They keep re-explaining the same project facts to the AI, every day, in every chat. Capturing that knowledge once, in the right place, is what turns a good AI workflow into a repeatable one.</p>
<p>Claude Code gives you four building blocks for this layer. Picking the right block for the right kind of knowledge is half the skill.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/b640f3ea-e01d-4480-bec7-08ad586fd04b.png" alt="b640f3ea-e01d-4480-bec7-08ad586fd04b" style="display:block;margin:0 auto" width="1024" height="1536" loading="lazy">

<p><em>Figure 4: Four building blocks. Each one feeds your Claude Code session in a different way.</em></p>
<h3 id="heading-claudemd-the-lasting-facts">CLAUDE.md: The Lasting Facts</h3>
<p><code>CLAUDE.md</code> is a Markdown file at the root of your repo (or at <code>~/.claude/CLAUDE.md</code> for personal-level instructions). It is loaded automatically every time you open a Claude Code session in that project, and it is where lasting facts live. If you have multiple projects in a monorepo you can have one for each project.</p>
<p>A working <code>CLAUDE.md</code> for a Next.js + Node.js SaaS billing app looks like this:</p>
<pre><code class="language-markdown"># Project Instructions

This is a SaaS billing application.

## Stack

- Next.js 14 (App Router) with TypeScript
- Node.js services for billing and email
- Prisma + PostgreSQL
- Auth.js for authentication
- Resend for transactional email
- BullMQ for background jobs

## Commands

- npm run dev - start the dev server
- npm test - run unit tests
- npm run typecheck - type-check the project
- npm run lint - lint the project
- npx prisma migrate dev - run migrations locally

## Architecture

- Business logic lives in services or domain modules.
- API routes stay thin and call into services.
- Use the existing email template system; do not add a new one.
- The BullMQ worker handles all scheduled jobs. Do not add cron.
- Tenant isolation is enforced at the service layer, not the route.

## Documentation

For deeper context, consult these before guessing:

- `docs/architecture.md` — service boundaries, request flow, tenant isolation model
- `docs/billing.md` — Stripe webhook handling, invoice lifecycle, proration rules
- `docs/email.md` — template system, Resend setup, list of available templates
- `docs/jobs.md` — BullMQ queue names, job patterns, retry/backoff policy
- `docs/db.md` — schema conventions, tenant isolation patterns, soft-delete rules
- `docs/runbooks/` — production incident runbooks
- `prisma/schema.prisma` — source of truth for the data model
- ADRs in `docs/adr/` — past architecture decisions; read before contradicting one

For Next.js, Prisma, Auth.js, BullMQ, or Resend specifics, check the official docs rather than guessing.

## Testing

- Every feature has success, validation failure, and not-found tests.
- Use test data builders, not inline setup objects.
- Do not mock the database unless existing tests do.

## Don't do

- Do not log raw payment payloads.
- Do not return database errors directly to the client.
- Do not edit migrations after they have been merged.
</code></pre>
<blockquote>
<p><strong>Keep</strong> <code>CLAUDE.md</code> <strong>tight.</strong> 100 to 300 lines is healthy. If a section grows into a multi-step procedure, that procedure belongs in a skill, not in <code>CLAUDE.md</code>. <code>CLAUDE.md</code> is for facts and rules. Workflows go in the next building block.</p>
</blockquote>
<blockquote>
<p><strong>A trick for growing your</strong> <code>CLAUDE.md</code> <strong>naturally.</strong> Every time the AI makes a mistake that surprises you, ask yourself if a rule in <code>CLAUDE.md</code> would have prevented it. Add the rule. Over a few weeks, your <code>CLAUDE.md</code> becomes a record of every assumption the AI got wrong, and your future sessions get noticeably better.</p>
</blockquote>
<h3 id="heading-skills-the-workflows-you-keep-retyping">Skills: The Workflows You Keep Retyping</h3>
<p>A skill is a small folder with a <code>SKILL.md</code> file inside. Claude scans every skill's name and description on startup, but only loads the body when the skill is needed. That progressive loading is what makes it cheap to keep dozens of skills around without slowing the model down.</p>
<p>Use a skill when you keep pasting the same instructions into chat: a commit format, a deployment checklist, a build process, a PR review pattern. Use <code>CLAUDE.md</code> for facts. Use skills for procedures.</p>
<p>The neat trick is that you do not have to write a skill by hand. Claude will write it for you. Open Claude Code in the project, then ask:</p>
<pre><code class="language-text">I want to create a Claude Code skill that captures how I build a production feature on this project. The skill should cover:

1. How to read CLAUDE.md and the technical brief before writing code.

2. How to look at 2-3 existing similar features and match their
   patterns.

3. How to write unit tests alongside the production code as normal good engineering (not as a strict TDD red-green loop).

4. How to run typecheck, lint, and the test suite at the end.

5. The conventions our codebase already follows: naming, error handling, where business logic lives, how tests are structured.

Create the skill at .claude/skills/build-with-tests/SKILL.md.
Use the recommended Claude Code skill format with proper YAML
frontmatter (name, description). Make the description specific
enough that the skill triggers automatically when I ask to
build, implement, or extend a feature.

Show me the file before writing it.
</code></pre>
<p>Claude reads your existing code, infers the patterns, and proposes a skill file. You review it, edit anything that does not match your taste, then save. The skill is now part of the repo, and every future session can use it. You can also use Claude's skill-creator to bootstrap new skills with <code>/skill-creator create me a new skill...</code>.</p>
<p>Here is the kind of file Claude will produce:</p>
<pre><code class="language-markdown">---
name: build-with-tests
description: Use this skill when implementing a feature or extending existing behaviour. Reads CLAUDE.md and the technical brief first, matches existing patterns, writes production code with unit tests alongside it, and runs the project's typecheck and test commands at the end. Triggers on: "build", "implement", "add", "extend", "ship the feature".
---

Process:

1. Read CLAUDE.md so you know the project rules and stack.
2. Read the technical brief so you stay inside its scope.
3. Look at 2-3 similar features in the codebase. Note their file layout, naming, error handling, and test structure.
4. Implement the feature in the smallest coherent steps you can.
For each step:
   - Write the production code.
   - Write a unit test that covers the new behaviour.
   - Run the test and confirm it passes.
5. When the feature is complete, run the full typecheck, lint,
   and test commands from CLAUDE.md.
6. Return a short summary: files changed, patterns reused, any
   rule you would suggest adding to CLAUDE.md.

Conventions used in this project:

- File names follow the existing folder structure.
- Tests live next to the code they cover (or in tests/ if that
  is the existing pattern).
- Use builders from test/builders/ for any entity setup.
- Cover success, validation failure, and one edge case per
  behaviour.

Rules:

- Do not refactor unrelated code.
- Do not change files outside the agreed scope.
- Do not add new dependencies without explicit instruction.
- If you cannot make the tests pass without violating a rule,
  stop and report the conflict.
</code></pre>
<p>With this skill saved, you no longer paste the process every time. You can just write:</p>
<pre><code class="language-text">Use the build-with-tests skill to implement the invoice reminder service.
</code></pre>
<blockquote>
<p><strong>The most common skill mistake.</strong> Avoid the mega-skill. A single SKILL.md trying to handle commits, PRs, branch naming, and changelog updates all at once tends to fire less reliably and confuse the model when two parts conflict. Split them. A good skill fits on one screen.</p>
</blockquote>
<h3 id="heading-hooks-automatic-gates-and-workflow-triggers">Hooks: Automatic Gates and Workflow Triggers</h3>
<p>Some parts of an AI workflow should not depend on the model remembering them.</p>
<p>A prompt can say, "run the tests before finishing." <code>CLAUDE.md</code> can say, "do not edit secret files." A skill can say, "validate the implementation before opening a PR." But those are still instructions. The model can forget. The model can choose to skip.</p>
<p>A hook is different.</p>
<p>A hook is an automatic action that runs at a specific point in the Claude Code session lifecycle. It can run a shell command, call an HTTP endpoint, or trigger a prompt or agent-based check depending on how you configure it.</p>
<p>That makes hooks useful for two things:</p>
<ol>
<li><p><strong>Gates.</strong> Stop or warn when something unsafe happens.</p>
</li>
<li><p><strong>Workflow triggers.</strong> Notify another system when something important happens.</p>
</li>
</ol>
<p>In a software factory, agents do the work, but hooks enforce the rules around them.</p>
<p>Claude Code hooks can run at lifecycle events such as:</p>
<ul>
<li><p><code>UserPromptSubmit</code>: before Claude processes your prompt</p>
</li>
<li><p><code>PreToolUse</code>: before Claude runs a tool</p>
</li>
<li><p><code>PostToolUse</code>: after a tool succeeds</p>
</li>
<li><p><code>Stop</code>: when Claude finishes a response</p>
</li>
<li><p><code>SubagentStart</code>: when a subagent starts</p>
</li>
<li><p><code>SubagentStop</code>: when a subagent finishes</p>
</li>
</ul>
<p>A simple, useful hook is a pre-commit gate that blocks credential files from ever being committed. Save this as <code>.claude/hooks/pre-commit.sh</code>:</p>
<pre><code class="language-bash">#!/usr/bin/env bash
# Block commits that would include sensitive files.

if git diff --cached --name-only \
   | grep -qE '\.(env|key|pem)$|secrets\.json|creds\.md'; then
  echo "BLOCKED: attempt to commit sensitive files"
  exit 1
fi
</code></pre>
<p>Wire it into your Claude Code hook configuration so it runs before commits. The configuration syntax lives in the official Claude Code hooks docs, but the shape is JSON and looks roughly like this:</p>
<pre><code class="language-json">{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/pre-commit.sh"
          }
        ]
      }
    ]
  }
}
</code></pre>
<p>That is deliberately minimal. In a real project you would also use <code>PostToolUse</code> to run formatters after edits, and <code>Stop</code> to run typecheck and tests before Claude finishes a response. Once it is wired, the hook runs every time, regardless of what the model thinks.</p>
<p>A few other hooks that pay off quickly:</p>
<ul>
<li><p><strong>PostToolUse on Edit</strong>: run the formatter so every AI edit comes out formatted.</p>
</li>
<li><p><strong>Stop</strong>: run typecheck and tests, refuse to stop if either fails.</p>
</li>
<li><p><strong>SubagentStop on validator</strong>: post the validator's findings to your team Slack channel automatically.</p>
</li>
</ul>
<p>Hooks matter because they cannot be argued with. The model can suggest, plan, and write. The lint, the type-check, and the test run on every change. That asymmetry is what keeps a software factory honest.</p>
<h3 id="heading-how-the-four-blocks-fit-together">How the Four Blocks fit Together</h3>
<p>A simple way to remember which block to reach for:</p>
<ul>
<li><p><code>CLAUDE.md</code> answers "what is true here?" Project facts and rules.</p>
</li>
<li><p><strong>Skills</strong> answer "how is this done?" Repeatable procedures.</p>
</li>
<li><p><strong>Subagents</strong> answer "who should do this?" Focused workers (next section).</p>
</li>
<li><p><strong>Hooks</strong> answer "what is enforced?" Deterministic gates.</p>
</li>
</ul>
<p>You will use all four. <code>CLAUDE.md</code> tells the AI the rules of your codebase. Skills give the AI repeatable playbooks. Subagents give it focused workers. Hooks make sure the rules are real and not optional.</p>
<p>The four blocks are the foundation. Section 6 is where we build the workers that actually do the factory's work.</p>
<h2 id="heading-part-2-build-the-agent-factory">Part 2: Build the Agent Factory</h2>
<p>You now have everything Part 1 promised. You know how to keep the AI's context clean. You have a <code>CLAUDE.md</code> it can lean on. You understand skills and hooks. That is the ground floor.</p>
<p>The next four sections are the factory itself.</p>
<p>Section 6 builds the seven specialized agents. Section 7 puts an orchestrator on top of them so the chain runs itself. Section 8 covers how the factory's output reaches production safely. Section 9 is the hands-on walkthrough where you build the whole thing in your own repo.</p>
<p>By the end of Part 2, the workflow you have been doing by hand will be running on its own. You will type one prompt. The orchestrator will route the work. The agents will do their focused jobs. You will step in at three approval points where your judgement matters. That is the shift.</p>
<h2 id="heading-6-the-agent-layer-seven-agents-that-do-focused-work">6. The Agent Layer: Seven Agents That Do Focused Work</h2>
<p>Now we get to the part that makes a factory a factory.</p>
<p>So far we have been giving the AI better instructions and better memory. But the AI is still one worker doing every job in the same chat. That is fine for small tasks. It does not scale to real feature work.</p>
<p>The fix is to split the work across specialized agents. In Claude Code these are called subagents. A subagent is not just a longer chat message. It is a focused worker with its own job description, its own tool permissions, and its own context window. That last piece is the one that matters most.</p>
<p>When the main session delegates work to a subagent, the subagent does the heavy reading or processing in its own context. It returns only a short summary to the main thread. The verbose part (file searches, log dumps, multi-step exploration) never bloats your main conversation.</p>
<p>Picture it like this. Your main Claude Code session is the lead engineer. Subagents are specialists you call in for specific tasks. A researcher who maps the codebase. A story writer who turns ideas into user stories. A spec writer who turns stories into technical briefs. A backend builder who writes API routes, services, and database access. A frontend builder who writes components and pages. A test verifier who writes acceptance tests against the user story once the feature is built. A validator who compares everything against the brief.</p>
<p>Each one is good at one thing. None of them tries to do everything.</p>
<h3 id="heading-why-one-big-ai-session-is-not-enough">Why One Big AI Session is Not Enough</h3>
<p>Imagine you ask your main session "build the invoice reminder feature." The session inspects files, designs the data model, writes API routes, builds UI, adds tests, and updates documentation. That sounds great until you realize one conversation is now carrying product thinking, architecture, database design, backend implementation, frontend implementation, testing, documentation, and self-review. The context is heavy, the model mixes responsibilities, and the same conversation that designed the feature is also reviewing it. That is a self-graded paper.</p>
<p>Splitting work into subagents fixes that. Each subagent has a narrow responsibility, a clean context window, and only sees what it needs. The validator does not see how the code was written. It sees what was supposed to be built and what is now on disk. That is exactly the gap a real reviewer looks for.</p>
<h3 id="heading-let-claude-write-the-agent-file-for-you">Let Claude Write the Agent File for You</h3>
<p>You can write a subagent file by hand if you want (it is just Markdown with YAML frontmatter) but there is rarely a reason to. The cleaner workflow is to use the <code>/agents</code> slash command and let Claude itself draft the file from your description.</p>
<p>Here is the workflow, end to end. Open Claude Code in your project and type:</p>
<pre><code class="language-text">/agents
</code></pre>
<p>That opens the agent management view. Choose to create a new project-level agent (which lives at <code>.claude/agents/&lt;name&gt;.md</code> and gets committed to your repo so the whole team uses it) and ask Claude to generate it for you. Claude will ask what the agent should do, what tools it should have, and what model it should run on.</p>
<p>The key idea is this: you describe the role you want. Claude writes the file. You review, edit, save, commit. Repeat for every agent your team needs.</p>
<h3 id="heading-tool-access-and-model-selection-are-part-of-the-design">Tool Access and Model Selection are Part of the Design</h3>
<p>Before we look at the seven agents, two design choices apply to every one of them.</p>
<p><strong>Tool access.</strong> A common beginner mistake is giving every agent every tool. That is risky. If an agent's job is to inspect architecture, it should not have Edit. If its job is to review code, it should not have Write. Restricting tools is how you make a subagent's behaviour match its description. The researcher cannot accidentally write code. The validator cannot accidentally fix what it found. The backend builder cannot accidentally edit frontend files. That separation is the point.</p>
<p><strong>Model selection.</strong> Inspection and review do not need a top-tier model. Routing them to a smaller, faster, cheaper model (Haiku) is one of the practical reasons subagents exist. Save the top-tier model (Sonnet, or Opus when reasoning quality really matters) for the work that needs it: the spec writer, the builders, the test verifier, and the validator.</p>
<h3 id="heading-the-anatomy-of-a-good-agent-definition">The Anatomy of a Good Agent Definition</h3>
<p>Before we look at the seven specific agents, here is the shape every good agent definition follows. You can use this as a template to design your own agents later. Anything the agents below have, you can copy. Anything they do not have but your team needs, you can add.</p>
<p>Two things beginners almost always miss when they design their first agent. The first is <strong>boundaries</strong>. They tell the agent what to do but not what it must not do, and the agent ends up doing both. The second is <strong>output format</strong>. They tell the agent what to think about but not how to return the result, so each invocation produces a slightly different shape and the next agent in the chain cannot rely on it. Both of those are in the template below.</p>
<p>Here is the template, written as if you were briefing a new agent on day one:</p>
<pre><code class="language-text">Subagent name:
  &lt;short-kebab-case-name&gt;

Purpose:
  One sentence on why this agent exists and what it is for.

Main responsibility:
  One sentence on the single job this agent owns.

What it should investigate / do:
  - Specific thing one
  - Specific thing two
  - Specific thing three
  (Be concrete. "Find similar features already implemented" is
   better than "understand the codebase".)

What it should NOT do:
  - The action it must never take (for example, edit files)
  - The decision it must never make (for example, invent rules)
  - The tool it must never use
  - The scope it must never widen
  (Boundaries are what make an agent's behaviour predictable.)

Tool access:
  Only the tools this agent actually needs.

Model:
  haiku for cheap inspection, sonnet for reasoning,
  opus when reasoning quality is critical.

Output format:
  1. Section one of the result (for example, "Relevant files")
  2. Section two (for example, "Existing patterns to follow")
  3. Section three (for example, "Risks or conflicts")
  (This is the contract with the next agent in the chain.
   A consistent output shape is what makes chaining reliable.)

Behaviour rules:
  - Short, specific rules the agent must follow every time
  - Limits on length, scope, or assumptions
  - When to ask a clarifying question instead of guessing
</code></pre>
<p>That is the shape. You hand it to Claude using the <code>/agents</code> slash command and ask Claude to create the agent file from the template. Claude turns it into a complete <code>.claude/agents/&lt;name&gt;.md</code> with the right YAML frontmatter, formatted system prompt, and tool restrictions.</p>
<p>The seven agents below all follow this shape. Once you understand the template, you can design your own. A design-system reviewer that checks new components against your tokens. An accessibility auditor that reads new UI code and flags issues. A migration writer that turns a schema change into a Prisma migration with the right naming. A release-note drafter that reads recent merges and writes a summary. Anything your team keeps doing by hand and would like to capture once.</p>
<h3 id="heading-the-seven-agents-at-a-glance">The Seven Agents at a Glance</h3>
<p>Before drilling into each one, here is the whole chain on one screen.</p>
<table>
<thead>
<tr>
<th>Agent</th>
<th>Purpose</th>
<th>Main output</th>
<th>Tools</th>
</tr>
</thead>
<tbody><tr>
<td><code>codebase-researcher</code></td>
<td>Map the relevant code before anything is built</td>
<td>Relevant files, existing patterns, risks</td>
<td>Read, Grep, Glob</td>
</tr>
<tr>
<td><code>story-writer</code></td>
<td>Turn a rough feature idea into a user story</td>
<td>Story, acceptance criteria, edge cases</td>
<td>Read</td>
</tr>
<tr>
<td><code>spec-writer</code></td>
<td>Turn the approved story into a technical brief</td>
<td>Data model, flow, API, UI, tests, risks</td>
<td>Read, Grep, Glob</td>
</tr>
<tr>
<td><code>backend-builder</code></td>
<td>Build the backend half</td>
<td>Services, API, jobs, migrations, unit tests</td>
<td>Read, Edit, Write, Bash</td>
</tr>
<tr>
<td><code>frontend-builder</code></td>
<td>Build the frontend half</td>
<td>Components, pages, hooks, UI tests</td>
<td>Read, Edit, Write, Bash</td>
</tr>
<tr>
<td><code>test-verifier</code></td>
<td>Add acceptance tests against the user story</td>
<td>Acceptance tests and coverage report</td>
<td>Read, Edit, Write, Bash</td>
</tr>
<tr>
<td><code>implementation-validator</code></td>
<td>Compare implementation against the story and brief</td>
<td>Findings grouped by severity</td>
<td>Read, Grep, Glob</td>
</tr>
</tbody></table>
<p>These seven cover the path from feature idea to a vertical slice ready for PR. They are not the canonical set. They are an opinionated starting point. Section 6 ends with how to grow the library beyond these.</p>
<p>Now let's build the seven.</p>
<h3 id="heading-agent-1-codebase-researcher">Agent 1: Codebase-Researcher</h3>
<p>This is the explore-before-build habit from Section 4, captured as a reusable worker. It maps the relevant parts of the codebase and returns findings. It never writes code.</p>
<p>Type <code>/agents</code> and use this description:</p>
<pre><code class="language-text">Create a project-level subagent named codebase-researcher.

Its job: inspect this codebase and explain how a specific area
works, without editing anything.

Inputs: a question about an area of the codebase (for example, "how does invoice creation work today?").

Outputs:
- a short list of the relevant files with paths
- a concise summary of the current architecture in that area
- the patterns and conventions in use
- risks or missing information the next agent should know about

Tool access: Read, Grep, Glob only. No Write. No Edit. No Bash.

Recommended model: haiku (this is cheap inspection work).
Recommended color: teal.

Behaviour rules:
- Never edit files.
- Never run commands that modify state.
- Keep the summary under 400 words.
- If a question is ambiguous, ask one clarifying question first.
</code></pre>
<p>Claude reads your description, picks reasonable defaults, and writes the file for you. In your terminal you will see something like:</p>
<blockquote>
<p>I have created the agent at <code>.claude/agents/codebase-researcher.md</code> with the following content. The agent is restricted to read-only tools (Read, Grep, Glob) so it cannot accidentally modify your codebase. I have set the model to Haiku to keep inspection cheap. Restart your Claude Code session to load the new agent, then invoke it with <code>@codebase-researcher</code> followed by the area of code you want explained.</p>
</blockquote>
<p>You will get an agent file like this:</p>
<pre><code class="language-markdown">---
name: codebase-researcher
description: Read-only investigator that maps the relevant parts of the codebase before any code is written. Returns the files involved, the patterns in use, similar features that already exist, and risks the next agent should know about. Use as the first step of any feature.
tools: Read, Grep, Glob
model: haiku
color: teal
---

You are a read-only investigator for this project. Your only
job is to inspect the codebase and explain how a specific area
works so the next agent has a clear, accurate map to build on.

When invoked, expect a question about an area of the codebase,
for example: "how does invoice creation work today?" or "where
is the email-sending code?".

Produce, every time, in this exact order:

1. **Relevant files**
   File paths grouped by role (services, API routes, models,
   workers, tests). Cite paths exactly.

2. **Existing patterns to follow**
   Naming conventions, folder structure, how business logic is
   organised, how errors are handled, how tests are structured.

3. **Similar feature examples**
   Two or three existing features in the codebase that solve
   a similar shape of problem. Cite paths.

4. **Risks or conflicts**
   Places where the proposed change could break old features,
   tenant boundaries that need to be preserved, timezone
   handling that already exists, anything that smells fragile.

5. **Recommended implementation plan (high level)**
   A short bullet list of how the change should fit into the
   existing system. Do not write code. Do not commit to one
   approach over another if more than one is reasonable.

6. **Tests that should be updated or added**
   Existing test files that probably need updates, plus the
   new test cases you would expect.

7. **Open questions** (only if you have any)
   Things that are genuinely unclear from the codebase. Never
   guess. Ask instead.

Behaviour rules:

- Never edit files.
- Never run commands that modify state.
- Keep the whole summary under 400 words.
- If the user's question is ambiguous, ask one clarifying
  question before investigating.
- Cite every file path exactly.
- If the answer requires running code or seeing live data,
  say so. Do not guess from filenames alone.
</code></pre>
<p>You will see this same flow for every agent below. The pattern is always: you describe the role using the <code>/agents</code> slash command, Claude generates the actual file, you review it, and you commit it to the repo. If you prefer to skip the <code>/agents</code> flow entirely, you can paste the generated files directly into <code>.claude/agents/&lt;name&gt;.md</code> and they will work the same way.</p>
<h3 id="heading-agent-2-story-writer">Agent 2: Story-Writer</h3>
<p>Turns a rough feature idea into a user story with acceptance criteria, edge cases, and out-of-scope items. This is the agent that catches things before any code is written.</p>
<pre><code class="language-text">Create a project-level subagent named story-writer.

Its job: take a rough feature idea (from the user) plus
exploration findings (from codebase-researcher) and produce
a clear user story.

Inputs:
- a rough feature description
- exploration findings from codebase-researcher
- any product or business rules already known

Outputs:
1. One user story in the form:
   "As a &lt;role&gt;, I want &lt;behaviour&gt;, so that &lt;outcome&gt;."
2.- Acceptance criteria that a test can verify directly. Cover the happy path, the obvious failure paths, and the rules from the brief.
3. A list of edge cases worth thinking about.
4. A list of explicitly out-of-scope items.

Tool access: Read only.
Recommended model: sonnet.
Recommended color: purple.

Behaviour rules:
- Use plain language. Avoid jargon.
- Do not invent product rules. If something is unclear, list
  it as an open question instead of guessing.
- Keep the story under one page.
</code></pre>
<p>Claude responds:</p>
<blockquote>
<p>I have created the agent at <code>.claude/agents/story-writer.md</code> with the following content. Restart your session to pick it up. You can invoke it with <code>@story-writer</code> and a feature idea, ideally with the codebase researcher's findings attached.</p>
</blockquote>
<pre><code class="language-markdown">---
name: story-writer
description: Turns a rough feature idea plus codebase exploration findings into a clear user story with acceptance criteria, edge cases, and out-of-scope items. Read-only. Use this after the codebase researcher has produced findings, before any technical brief is written.
tools: Read
model: sonnet
color: purple
---

You are the user story author for this project. Your job is to
turn a rough feature idea into a clear, testable user story
that the rest of the chain can build against.

When invoked, expect to receive:

- A rough feature description from the user.
- Exploration findings from the codebase-researcher agent.
- Optionally, any product or business rules already known.

Produce, every time, in this exact order:

1. **User story**
   One sentence in the form:
   "As a &lt;role&gt;, I want &lt;behaviour&gt;, so that &lt;outcome&gt;."

2. **Acceptance criteria**
   Statements that a test can verify directly. Cover the happy
   path, the obvious failure paths, and the rules from the
   brief.

3. **Edge cases worth thinking about**
   Boundary conditions, retries, multi-tenant concerns,
   permission edges, anything that often goes wrong.

4. **Out of scope**
   Things this story explicitly does not cover, so the team
   knows what NOT to build.

5. **Open questions** (only if you have any)
   Things that are genuinely unclear from the input. Never
   invent answers. Always ask instead.

Behaviour rules:

- Use plain language. Avoid product or framework jargon.
- Never invent business rules. If a rule is missing, ask.
- Keep the whole story to one page or less.
- Do not write code or technical design. That is the spec
  writer's job.
</code></pre>
<h3 id="heading-agent-3-spec-writer">Agent 3: Spec-Writer</h3>
<p>Turns the approved user story plus exploration findings into a technical brief. Data model changes, background flow, API changes, frontend changes, tests required, risks. This agent is read-only. It cannot edit code.</p>
<pre><code class="language-text">Create a project-level subagent named spec-writer.

Its job: take an approved user story and exploration findings,
and produce a technical brief that the backend builder, frontend
builder, and test verifier can follow.

Inputs:
- an approved user story
- exploration findings from codebase-researcher
- CLAUDE.md and any relevant project rules

Outputs (one short Markdown document):
- Data model changes
- Background flow / process flow
- API changes (if any)
- Frontend changes (if any)
- Tests required (success, failure, edge cases)
- Risks and open questions
- Files that will change

Tool access: Read, Grep, Glob.
Recommended model: sonnet.
Recommended color: indigo.

Behaviour rules:
- Read CLAUDE.md before writing the brief.
- Prefer reusing existing infrastructure. Call out any new
  scheduler, new database, or new third-party dependency.
- Highlight tenant isolation and timezone concerns explicitly.
- Never edit files.
</code></pre>
<p>Claude responds:</p>
<blockquote>
<p>I have created the agent at <code>.claude/agents/spec-writer.md</code> with the following content. The agent is read-only and is configured to read <code>CLAUDE.md</code> before producing each brief, so the brief stays consistent with your project's architecture rules.</p>
</blockquote>
<pre><code class="language-markdown">---
name: spec-writer
description: Turns an approved user story plus exploration findings into a short technical brief that the build and verification agents can follow. Read-only. Always reads CLAUDE.md before writing. Use after the user story has been approved.
tools: Read, Grep, Glob
model: sonnet
color: indigo
---

You are the technical brief writer for this project. Your job
is to turn an approved user story plus the codebase researcher's findings into a short, actionable brief that downstream agents can follow without ambiguity.

Before writing:

1. Read CLAUDE.md for the project's stack, architecture rules,
   and "don't do" list.
2. Read the user story and the researcher's findings.
3. If something material is missing or unclear, list it as an
   open question. Do not guess.

Output a short Markdown document with these sections, in order:

**Data model changes**

- Which models change. What fields. What types.
- Any migration considerations.

**Background flow / process flow**

- Step-by-step description of how the behaviour runs.
- Which existing infrastructure it reuses.

**API changes**

- New or changed endpoints, with request and response shape.
- Auth and authorization requirements.

**Frontend changes**

- New or changed components, hooks, or pages.
- How they call the API and handle loading / error states.

**Tests required**

- Success cases.
- Failure cases.
- Edge cases (boundaries, retries, deduplication).
- Acceptance tests at the user-story level.

**Risks and open questions**

- Tenant isolation concerns. State them explicitly.
- Timezone concerns. State them explicitly.
- Anything else the team should decide before code is written.

**Files that will change**

- Bullet list of file paths, grouped by backend / frontend / tests.

Behaviour rules:

- Prefer reusing existing infrastructure. Any new scheduler,
  new database, or new third-party dependency must be called
  out explicitly with a justification.
- Tenant isolation and timezone handling must always be
  addressed, even if only to say "no tenant boundary applies"
  or "timezone is irrelevant for this feature."
- Never edit files.
- Keep the whole brief under one page where possible.
</code></pre>
<h3 id="heading-agent-4-backend-builder">Agent 4: Backend-Builder</h3>
<p>This is the first of the build-side agents. Its job is the backend half of a feature: API routes, services, database access, background jobs, and the unit tests that cover its own code. It does not touch frontend files.</p>
<p>A note before the prompt. Writing "only edit backend files" inside an agent definition is guidance, not a hard security boundary. The agent will usually obey, but the strongest enforcement comes from Claude Code permissions, hooks that reject edits to specific paths, or CI checks that fail when a PR touches files outside its declared scope. Use prompt rules for direction. Use hooks and CI for enforcement.</p>
<pre><code class="language-text">Create a project-level subagent named backend-builder.

Its job: implement the backend half of a feature described in
the technical brief. That means API routes, services, database
access, background jobs, and unit tests for the code it writes.

Inputs:
- the approved technical brief
- the codebase researcher's findings
- CLAUDE.md and any relevant project rules
- the build-with-tests skill (project skill)

Outputs:
- backend code that implements the brief
- unit tests that cover the new behaviour
- a short summary: files changed, patterns reused, any rule
  worth adding to CLAUDE.md

Tool access: Read, Edit, Write, Bash. Restricted to backend
folders (services, API routes, workers, migrations, server-side
helpers, and their tests).
Recommended model: sonnet.
Recommended color: green.

Behaviour rules:
- Use the build-with-tests skill for conventions.
- Read CLAUDE.md and the brief before editing anything.
- Only edit backend files. Do not touch React components, pages,
  or client-side hooks.
- Match existing patterns. Reuse existing helpers, services, and templates instead of writing new ones.
- Do not add new dependencies without explicit instruction.
- Run typecheck, lint, and the test suite at the end. Report
  pass/fail and any unexpected failures.
- If a project rule that would have helped is missing from
  CLAUDE.md, surface it as a suggested addition.
</code></pre>
<p>Claude responds:</p>
<blockquote>
<p>I have created the agent at <code>.claude/agents/backend-builder.md</code> with the following content. This agent has full edit and bash access, scoped to backend folders. I have added explicit rules so it does not stray into frontend files and so it always uses the build-with-tests skill for conventions.</p>
</blockquote>
<pre><code class="language-markdown">---
name: backend-builder
description: Implements the backend half of a feature: API routes, services, database access, background jobs, and unit tests. Reads CLAUDE.md, the technical brief, and matches existing patterns. Uses the build-with-tests skill. Restricted to backend folders.
tools: Read, Edit, Write, Bash
model: sonnet
color: green
---

You are the backend implementation worker for this project.
Your job is to implement the backend half of the feature
described in the approved technical brief.

Before you edit anything:

1. Read CLAUDE.md so you know the project rules and stack.
2. Read the technical brief so you stay inside its scope.
3. Load the build-with-tests skill for conventions.
4. Look at 2-3 similar backend features in the codebase and
   match their patterns.

Implementation rules:

- Only edit backend files: services, API routes, workers,
  migrations, server-side helpers, and their tests.
- Never edit React components, pages, or client-side hooks.
  That is the frontend-builder's job.
- Match existing patterns. If a helper, service, or template
  already does what you need, use it instead of writing a new
  one.
- Do not refactor unrelated code.
- Do not add new dependencies without explicit instruction.
- Write unit tests alongside the production code.

After you edit:

1. Run the project's typecheck, lint, and test commands (from
   CLAUDE.md).
2. Confirm all tests pass.
3. Return a short summary:
   - Files added / edited (backend only)
   - Patterns and helpers reused
   - Anything you noticed that would benefit from a CLAUDE.md
     rule

If you cannot complete the work without violating one of the
rules above, stop and report the conflict.
</code></pre>
<h3 id="heading-agent-5-frontend-builder">Agent 5: Frontend-Builder</h3>
<p>This is the second build-side agent. Its job is the frontend half of the same feature: components, pages, hooks, client-side state, and the unit/component tests that cover its own code. It does not touch backend files. It consumes the API contract the backend builder has already produced.</p>
<pre><code class="language-text">Create a project-level subagent named frontend-builder.

Its job: implement the frontend half of a feature described in
the technical brief. That means React components, pages, hooks,
client-side state, and component tests for the code it writes.

Inputs:
- the approved technical brief
- the codebase researcher's findings
- the backend builder's summary (so it knows the API contract)
- CLAUDE.md and any relevant project rules
- the build-with-tests skill (project skill)

Outputs:
- frontend code that implements the brief
- component and unit tests that cover the new behaviour
- a short summary: files changed, patterns reused, any rule
  worth adding to CLAUDE.md

Tool access: Read, Edit, Write, Bash. Restricted to frontend
folders (components, pages, hooks, client-side helpers, and
their tests).
Recommended model: sonnet.
Recommended color: blue.

Behaviour rules:
- Use the build-with-tests skill for conventions.
- Read CLAUDE.md and the brief before editing anything.
- Only edit frontend files. Do not touch services, API routes,
  workers, or migrations.
- Consume the API exactly as the backend builder produced it.
  Do not invent endpoints or response shapes.
- Match existing component patterns: styling, accessibility,
  loading and error states.
- Do not add new dependencies without explicit instruction.
- Run typecheck, lint, and the test suite at the end. Report
  pass/fail and any unexpected failures.
- If a project rule that would have helped is missing from
  CLAUDE.md, surface it as a suggested addition.
</code></pre>
<p>Claude responds:</p>
<blockquote>
<p>I have created the agent at <code>.claude/agents/frontend-builder.md</code> with the following content. This agent has full edit and bash access, scoped to frontend folders. It consumes the API contract the backend builder produced, rather than inventing endpoints of its own.</p>
</blockquote>
<pre><code class="language-markdown">---
name: frontend-builder
description: Implements the frontend half of a feature: components, pages, hooks, client-side state, and component tests. Reads CLAUDE.md, the technical brief, the backend builder's summary, and matches existing component patterns. Uses the build-with-tests skill. Restricted to frontend folders.
tools: Read, Edit, Write, Bash
model: sonnet
color: blue
---

You are the frontend implementation worker for this project.
Your job is to implement the frontend half of the feature
described in the approved technical brief, consuming the API
that the backend builder has already produced.

Before you edit anything:

1. Read CLAUDE.md so you know the project rules and stack.
2. Read the technical brief so you stay inside its scope.
3. Read the backend builder's summary so you know exactly which
   endpoints exist and what they return.
4. Load the build-with-tests skill for conventions.
5. Look at 2-3 similar components or pages in the codebase and
   match their patterns.

Implementation rules:

- Only edit frontend files: components, pages, hooks, client-side helpers, and their tests.
- Never edit services, API routes, workers, or migrations. That
  is the backend-builder's job.
- Consume the API exactly as the backend builder produced it.
  If the shape is wrong for the UI, surface the mismatch as
  feedback instead of patching around it.
- Match existing component patterns. Styling, accessibility,
  loading states, and error handling should look like the rest
  of the codebase.
- Do not refactor unrelated code.
- Do not add new dependencies without explicit instruction.
- Write component or unit tests alongside the production code.

After you edit:

1. Run the project's typecheck, lint, and test commands (from
   CLAUDE.md).
2. Confirm all tests pass.
3. Return a short summary:
   - Files added / edited (frontend only)
   - Patterns and components reused
   - Anything you noticed that would benefit from a CLAUDE.md
     rule

If you cannot complete the work without violating one of the
rules above, stop and report the conflict.
</code></pre>
<h3 id="heading-agent-6-test-verifier">Agent 6: Test-Verifier</h3>
<p>Once the feature is built end to end, the test verifier writes acceptance tests that exercise the user story directly. Unit tests live next to the code they cover (the build agents wrote them). Acceptance tests live here. They are how the chain proves the feature actually does what the story said it should.</p>
<pre><code class="language-text">Create a project-level subagent named test-verifier.

Its job: given the approved user story, the approved technical
brief, and a feature that has already been built end to end,
write acceptance tests that exercise the user story and confirm
each acceptance criterion holds.

Inputs:
- the approved user story (with acceptance criteria)
- the approved technical brief
- the backend builder's and frontend builder's summaries
- the build-with-tests skill (project skill)

Outputs:
- one acceptance test file (or one extension of an existing
  one) that covers every acceptance criterion in the story
- a short report of which criteria are covered and which are
  not (only if any are missing or untestable)

Tool access: Read, Edit, Write (test files only), Bash.
Recommended model: sonnet.
Recommended color: yellow.

Behaviour rules:
- Read the user story and the brief before writing.
- Use the build-with-tests skill for conventions.
- Cover every acceptance criterion, plus the edge cases listed
  in the story.
- Do not modify backend or frontend files outside the test
  folder.
- After writing, run the new tests once. Report pass/fail and
  any acceptance criterion that could not be covered cleanly.
</code></pre>
<p>Claude responds:</p>
<blockquote>
<p>I have created the agent at <code>.claude/agents/test-verifier.md</code> with the following content. The agent is scoped to test files only. It uses the build-with-tests skill for conventions and runs after both build agents have finished, so it has a working feature to test against.</p>
</blockquote>
<pre><code class="language-markdown">---
name: test-verifier
description: Writes acceptance tests against the user story after the build agents have finished. Confirms every acceptance criterion holds against the built feature. Uses the build-with-tests skill. Run after backend-builder and frontend-builder.
tools: Read, Edit, Write, Bash
model: sonnet
color: yellow
---

You are the acceptance test author for this project. Your job is to verify, with tests, that the feature now built end to end
actually satisfies every acceptance criterion in the user story.
 
Before writing:

1. Read the approved user story so you know every criterion.
2. Read the approved technical brief so you know how the
   feature is wired together.
3. Read the backend builder's and frontend builder's summaries
   so you know which endpoints, components, and behaviours exist.
4. Load the build-with-tests skill for conventions.
5. Look at 2-3 existing acceptance tests in the codebase and
   match their style.

Writing rules:

- Cover every acceptance criterion in the user story.
- Cover the edge cases the story lists.
- Use the project's test data builders, not inline setup.
- Follow the project's existing acceptance-test layout.
- Edit only test files. Do not edit any code.

After writing:

1. Run the new tests.
2. If any fail, the feature does not satisfy the story. Report
   exactly which criterion failed and why. Do not patch the
   code. That is for the build agents to fix on the
   next loop.
3. If any criterion cannot be covered cleanly (for example, the
   brief did not name a way to observe it), report it. Do not
   invent a workaround.
4. Return a short summary: criteria covered, criteria failed,
   criteria that need clarification.
</code></pre>
<h3 id="heading-agent-7-implementation-validator">Agent 7: Implementation-Validator</h3>
<p>Compares the current state of the implementation against the approved story and brief. Reports gaps. Never fixes them. The validator is the agent that catches everything the build agents and test verifier missed.</p>
<pre><code class="language-text">Create a project-level subagent named implementation-validator.

Its job: compare the current implementation against the approved user story and technical brief, and report gaps. It does not fix anything.

Inputs:
- the approved user story
- the approved technical brief
- the current state of the implementation (files on disk)
- the test verifier's report

Outputs, grouped by severity:
- critical (must fix before merge)
- important (should fix before merge)
- minor (nice to have)
- recommended next agent

Always check for:
- missing acceptance criteria
- missing tests for failure paths
- security issues (auth checks, tenant isolation, raw error
  exposure, secrets in logs)
- changes to files outside the agreed scope
- inconsistent project patterns (compared to CLAUDE.md and
  existing code)
- duplicate logic that should be reused
- timezone or multi-tenant concerns from the brief that the
  implementation may have missed

Tool access: Read, Grep, Glob.
Recommended model: sonnet (this needs careful reasoning).
Recommended color: red.

Behaviour rules:
- Never edit files.
- Never run destructive commands.
- Always cite the file and line number for each finding.
- If a finding is opinion-based rather than a real risk,
  mark it as such.
</code></pre>
<p>Claude responds:</p>
<blockquote>
<p>I have created the agent at <code>.claude/agents/implementation-validator.md</code> with the following content. Restart your Claude Code session to load it. You can then invoke it as <code>@implementation-validator</code> once the chain has produced an implementation to review.</p>
</blockquote>
<pre><code class="language-markdown">---
name: implementation-validator
description: Strict reviewer that compares the current implementation against the approved user story and technical brief and reports gaps grouped by severity. Never edits files. Use after the build and verification agents have finished, before opening a PR.
tools: Read, Grep, Glob
model: sonnet
color: red
---

You are an implementation validator for this project. Your only
job is to compare the code on disk against the approved user
story and technical brief, and report what is missing or wrong.
You do not fix anything.

Inputs you should expect:

- The approved user story.
- The approved technical brief.
- The current state of the implementation (files on disk).
- The test verifier's report.

What to check, every time:

- Acceptance criteria from the story that are not implemented.
- Failure paths from the brief that have no test coverage.
- Security issues: missing auth checks, tenant isolation gaps,
  raw error exposure, secrets in logs, missing rate limits on
  sensitive endpoints.
- Changes to files outside the agreed scope.
- Inconsistencies with project patterns documented in CLAUDE.md
  or visible in the existing codebase.
- Duplicate logic that should reuse existing helpers.
- Timezone or multi-tenant concerns called out in the brief
  that the implementation may have missed.

Output format, every time:

**Critical** (must fix before merge)

- &lt;one finding, with file path and line number&gt;
- ...

**Important** (should fix before merge)

- &lt;finding&gt;
- ...

**Minor** (nice to have)

- &lt;finding, marked "(opinion)" if it is opinion-based&gt;
- ...

**Recommended next agent**

- &lt;e.g. "backend-builder to fix tenant isolation in X,
  then test-verifier to add the matching acceptance test"&gt;

Behaviour rules:

- Never edit files.
- Never run destructive commands.
- Cite the file and line number for every finding.
- Mark opinion-based findings clearly so reviewers can ignore
  them safely.
- If you find no critical or important issues, say so plainly.
  Do not invent issues to look thorough.
</code></pre>
<h3 id="heading-these-seven-are-examples-not-the-canonical-set">These seven are examples, not the canonical set</h3>
<p>Seven agents is enough to ship real features. It is not a ceiling. The whole point of the pattern is that your team builds the agents your team needs, using the anatomy template from earlier in this section. Sky is the limit. Build whatever you want.</p>
<p>A short list of agents you might add next, depending on where your team feels friction:</p>
<ul>
<li><p><strong>accessibility-reviewer</strong>: reads new UI code and flags missing labels, contrast issues, keyboard traps, and other problems against your project's standards.</p>
</li>
<li><p><strong>security-reviewer</strong>: runs before the validator and checks for missing auth, tenant isolation gaps, unsafe deserialization, and dependency risks.</p>
</li>
<li><p><strong>migration-writer</strong>: turns a brief's schema change into a Prisma (or your ORM's) migration with the project's naming and rollback conventions.</p>
</li>
<li><p><strong>design-system-reviewer</strong>: checks new components against your design tokens, spacing scale, and existing component library before they ship.</p>
</li>
<li><p><strong>docs-updater</strong>: reads the final diff and updates the README, feature docs, or operator notes from it.</p>
</li>
<li><p><strong>release-note-writer</strong>: reads recent merges and drafts the user-facing change summary in your team's style.</p>
</li>
<li><p><strong>payments-integration</strong>: knows your Stripe webhook conventions inside out, so any engineer can ship a feature that touches billing without a payments specialist on the path.</p>
</li>
</ul>
<p>Each one is the same shape: a focused role, restricted tools, a clear input/output contract, behaviour rules. Use the anatomy template, hand it to Claude with <code>/agents</code>, review the file, commit it. The factory grows the way your codebase grows. Add what you keep doing by hand. Remove what no longer pays for itself.</p>
<h3 id="heading-start-smaller-if-seven-feels-like-a-lot">Start smaller if seven feels like a lot</h3>
<p>If standing up seven agents in one weekend feels like too much, do not. The smallest useful version of this pattern is three:</p>
<pre><code class="language-text">codebase-researcher → build-with-tests skill → implementation-validator
</code></pre>
<p>Researcher maps the code. The skill keeps the build agent honest. The validator catches what you missed. Run a few features through that three-piece setup, see where it hurts, then add the next agent that would have prevented the friction. Most teams do not need all seven on day one.</p>
<h3 id="heading-built-in-subagents-you-already-have">Built-in Subagents You Already Have</h3>
<p>Before you build any of the seven above, Claude Code already ships with a few subagents you should know about and use where they fit:</p>
<ul>
<li><p><strong>Explore</strong> is read-only and tuned for searching and understanding codebases. Cheap, fast. You can use it directly, or wrap it with your own codebase-researcher when you want a tighter output format.</p>
</li>
<li><p><strong>Plan</strong> gathers context inside plan mode and proposes an implementation plan before any file changes happen.</p>
</li>
<li><p><strong>General-purpose</strong> handles tasks that need both exploration and modification.</p>
</li>
</ul>
<p>Reach for the built-in ones when they fit. Build custom ones when you want a tighter contract on inputs and outputs, or when you want to enforce a specific behaviour rule.</p>
<p>Seven agents is enough to run a real factory. The eighth piece, the one that makes them work together, is the orchestrator in the next section.</p>
<h2 id="heading-7-the-workflow-layer-the-orchestrator-that-runs-the-chain">7. The Workflow Layer: The Orchestrator That Runs the Chain</h2>
<p>You now have seven agents that each do one thing well. The next question is: who decides when to call which agent, and in what order?</p>
<p>In a vibe-coding workflow, the answer is "the human types prompts." That works, but it makes the human the orchestrator. You hold the chain in your head. You remember to call the researcher first. You remember to pause for review. You remember to invoke the validator at the end. Miss one step and the chain breaks.</p>
<p>The whole point of a factory is that the chain runs itself. The human stays in the loop where judgement matters (approving the story, approving the brief, approving the PR), but the routing between agents is automated.</p>
<p>That is what an orchestrator does.</p>
<h3 id="heading-what-the-orchestrator-is">What The Orchestrator Is</h3>
<p>The orchestrator is another piece of the factory whose only job is to delegate to other agents in the right order, pass the right inputs forward, pause for human approval at the right points, and recover when an agent reports a problem.</p>
<p>There are a few ways to build it in Claude Code. I will show you two.</p>
<ol>
<li><p><strong>As a skill or a slash command.</strong> This is the starter version. Either a <code>SKILL.md</code> file at <code>.claude/skills/feature-factory/SKILL.md</code> (auto-triggers when its description matches what you ask) or a Markdown file at <code>.claude/commands/feature-factory.md</code> (runs when you type <code>/feature-factory</code>). Same content in either, different way of firing it. Simple, no new concepts, easy to read and edit.</p>
</li>
<li><p><strong>As a subagent.</strong> This is the advanced upgrade. It runs in its own context window and can delegate to the other seven agents using Claude Code's subagent invocation. Cleaner, more powerful, but it adds one more concept on top.</p>
</li>
</ol>
<p>Build the skill/command version first. Live with it for a week. Then upgrade to the agent version when you understand the chain well enough to want stronger automation.</p>
<h3 id="heading-the-chain-itself">The Chain Itself</h3>
<p>Here is the chain the orchestrator runs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/ef23d784-c2d0-4e39-99de-704152309023.png" alt="ef23d784-c2d0-4e39-99de-704152309023" style="display:block;margin:0 auto" width="941" height="1672" loading="lazy">

<p>There are three human approval points:</p>
<ol>
<li><p><strong>After the story.</strong> Is this the right problem? Are the acceptance criteria correct?</p>
</li>
<li><p><strong>After the brief.</strong> Is the design safe? Any red flags before code is written?</p>
</li>
<li><p><strong>After validation.</strong> Is this PR ready to ship?</p>
</li>
</ol>
<p>Everything else is the orchestrator routing work between agents.</p>
<h3 id="heading-version-1-the-orchestrator-as-a-skill">Version 1: The Orchestrator as a Skill</h3>
<p>Create a skill at <code>.claude/skills/feature-factory/SKILL.md</code>. Ask Claude to generate it for you:</p>
<pre><code class="language-text">Create a Claude Code skill at .claude/skills/feature-factory/SKILL.md that orchestrates a feature build using seven existing subagents: codebase-researcher, story-writer, spec-writer, backend-builder, frontend-builder, test-verifier, implementation-validator.

The skill should:
- Trigger when the user asks to build, ship, or implement a
  feature with phrases like "build a feature", "ship a
  feature", "feature factory", "run the full chain".
- Run the chain in the order described below.
- Pause for human approval after the story and after the brief.
  At each approval point, handle three outcomes: approved,
  changes requested, or rejected.
- Run backend-builder first, then frontend-builder, then
  test-verifier.
- Invoke implementation-validator at the end and report
  critical, important, and minor findings.
- If the validator reports critical gaps, loop back to the
  appropriate builder (backend or frontend), then re-run
  test-verifier and the validator.

Order:
1. codebase-researcher: map the area of code involved.
2. story-writer: produce a user story.
3. ASK HUMAN: approve the story.
   - Approved: continue.
   - Changes requested: re-invoke story-writer with the human's
     feedback. Repeat this step until approved or rejected.
   - Rejected: stop the chain. Summarise what was explored so
     the human can decide what to do next.
4. spec-writer: produce a technical brief.
5. ASK HUMAN: approve the brief.
   - Approved: continue.
   - Changes requested: re-invoke spec-writer with the human's
     feedback. Repeat this step until approved or rejected.
   - Rejected: stop the chain. Keep the approved story so the
     human can resume later with a different technical
     approach.
6. backend-builder: implement backend + unit tests.
7. frontend-builder: implement frontend + component tests.
8. test-verifier: write acceptance tests against the story.
9. implementation-validator: report findings.
10. If critical findings: route back to backend-builder or
    frontend-builder, then re-run test-verifier and the
    validator.
11. ASK HUMAN: final review before opening PR.

Show me the skill file before saving it.
</code></pre>
<p>Claude will produce something like this:</p>
<pre><code class="language-markdown">---
name: feature-factory
description: Use this skill when the user asks to build, ship,
  or implement a feature end to end. Runs the full chain of
  seven subagents with human approval points after the story
  and the brief, runs the build agents in order (backend,
  frontend, test-verifier), then validates. Triggers on:
  "build a feature", "ship a feature", "run the factory",
  "feature factory".
---

Process:

1. Invoke the codebase-researcher subagent. Pass the feature
   idea and the relevant area of code. Wait for findings.

2. Invoke the story-writer subagent. Pass the feature idea
   and the researcher's findings. Wait for the user story.

3. Show the story to the user. Ask: "Does this match what
   you want? Reply 'approved' to continue, describe what
   to change, or reply 'reject' to stop the chain."
   - If approved, continue.
   - If changes requested, invoke story-writer again with
     the user's feedback. Repeat this step until approved
     or rejected.
   - If rejected, stop the chain. Summarise what was
     explored so the user can decide what to do next.

4. Invoke the spec-writer subagent. Pass the approved story
   and the researcher's findings. Wait for the technical brief.

5. Show the brief to the user. Ask: "Any design red flags?
   Reply 'approved' to continue, describe what to change,
   or reply 'reject' to stop the chain."
   - If approved, continue.
   - If changes requested, invoke spec-writer again with
     the user's feedback. Repeat this step until approved
     or rejected.
   - If rejected, stop the chain. Keep the approved story
     so the user can resume later with a different
     technical approach.

6. Invoke the backend-builder subagent. Pass the brief and the
   researcher's findings. Wait for the backend implementation
   and its summary.

7. Invoke the frontend-builder subagent. Pass the brief, the
   researcher's findings, and the backend builder's summary
   (so it knows the API contract). Wait for the frontend
   implementation and its summary.

8. Invoke the test-verifier subagent. Pass the approved story,
   the brief, and both builder summaries. Wait for the
   acceptance tests and the verifier's report.

9. Invoke the implementation-validator subagent. Pass the
   approved story, the approved brief, the test verifier's
   report, and the current implementation. Wait for findings.

10. If the validator reports critical findings, route them to
    the right build agent (backend-builder or frontend-builder)
    along with the relevant test from test-verifier. Then
    re-run test-verifier and the validator.

11. Show the validator findings to the user. Ask:
    "Ready to open the PR?"

Rules:

- Never skip the human approval points.
- Never invoke frontend-builder before backend-builder.
- Never invoke test-verifier before both builders have
  finished.
- Never invoke the validator before the chain has produced
  some implementation and the verifier has run.
- Each agent runs in its own subagent context. Pass only
  the inputs that agent needs.
- If any agent reports it cannot complete its task, stop
  and surface the reason to the user.
</code></pre>
<p>That is the full orchestrator. It is a single Markdown file. It calls seven other Markdown files. Together they are a factory. Now you might thinking what if we have to create only backend endpoint not frontend or vice versa. So that totally depends on your needs. Create in that specific way. Above are just examples. You have to decide what you want to build.</p>
<h3 id="heading-running-the-chain-a-compact-end-to-end-example">Running the Chain: A Compact End-to-End Example</h3>
<p>You open Claude Code and type:</p>
<pre><code class="language-text">/feature-factory

I want to add reminder emails for invoices that have been unpaid for more than 7 days.
</code></pre>
<p>What happens next, step by step:</p>
<p><strong>Step 1.</strong> The orchestrator delegates to <code>codebase-researcher</code>. The researcher runs in its own context, reads the invoice, payment, and email files, and returns:</p>
<blockquote>
<p>Invoices are created in <code>services/invoices/create.ts</code>. Status is stored on the Invoice model. Transactional email goes through <code>services/email/send.ts</code> using Resend templates. BullMQ workers in <code>workers/</code> already handle scheduled jobs. No existing reminder logic. Admin UI lives under <code>app/(admin)/invoices/</code>. Likely files to change: Invoice model, a new reminder service, the email template registry, a new admin page for manual triggers.</p>
</blockquote>
<p><strong>Step 2.</strong> The orchestrator delegates to <code>story-writer</code>, passing the feature idea and the findings. It returns:</p>
<blockquote>
<p><em>As an account admin, I want overdue invoice reminders to be sent automatically, so customers are reminded without manual follow-up.</em></p>
<p>Acceptance criteria:</p>
<ol>
<li><p>A reminder is sent when an invoice is unpaid for more than 7 days.</p>
</li>
<li><p>No reminder is sent for paid invoices.</p>
</li>
<li><p>Duplicate reminders are not sent in the same window.</p>
</li>
<li><p>Failed email attempts do not mark the reminder as sent.</p>
</li>
<li><p>Admins can see when the last reminder was sent.</p>
</li>
<li><p>Admins can manually trigger a reminder for a specific invoice.</p>
</li>
</ol>
<p>Edge cases: invoice exactly 7 days old, retries, tenant isolation, timezone. Out of scope: SMS reminders, customer-side preferences.</p>
</blockquote>
<p><strong>Step 3.</strong> The orchestrator pauses and offers three options: approve, request changes, or reject. You read the story. It matches what you want, so you approve.</p>
<p><strong>Step 4.</strong> The orchestrator delegates to <code>spec-writer</code>, passing the approved story and the researcher's findings. It returns a brief: data model change (<code>lastReminderSentAt</code>), BullMQ flow, admin endpoint for manual trigger, admin UI tile showing last sent time, tests required, risks (multi-tenant, timezone).</p>
<p><strong>Step 5.</strong> You read the brief. You notice it says "we will store processed invoice IDs in memory." Red flag. You reply:</p>
<pre><code class="language-text">Change the deduplication approach. Do not use in-memory state.
Use the lastReminderSentAt column instead.
</code></pre>
<p>The orchestrator re-invokes <code>spec-writer</code>, the brief is updated, you approve.</p>
<p><strong>Step 6.</strong> The orchestrator delegates to <code>backend-builder</code>, passing the brief and the researcher's findings. The builder adds <code>lastReminderSentAt</code> to the Invoice model, writes the new reminder service, plugs it into the existing BullMQ worker, adds an admin endpoint for manual triggers, and writes unit tests for each. Runs typecheck, lint, and the test suite. All green. Returns a summary:</p>
<blockquote>
<p>Backend implementation complete.</p>
<p>Files added:</p>
<ul>
<li><p><code>services/reminders/send-overdue.ts</code></p>
</li>
<li><p><code>app/api/admin/invoices/[id]/remind/route.ts</code></p>
</li>
<li><p><code>workers/reminders.worker.ts</code></p>
</li>
</ul>
<p>Files edited:</p>
<ul>
<li><code>prisma/schema.prisma</code> (added <code>lastReminderSentAt</code> column)</li>
</ul>
<p>Reused: <code>prisma</code>, <code>sendEmail</code>, <code>daysAgo</code>, existing <code>invoice-reminder</code> email template, existing BullMQ <code>redisConnection</code>. Tenant isolation enforced in the manual-trigger endpoint via the existing <code>requireSameTenant</code> helper.</p>
<p>Unit tests added: 7. All passing. Suggested <code>CLAUDE.md</code> rule: "Reminder columns follow the <code>last&lt;Action&gt;SentAt</code> pattern."</p>
</blockquote>
<p><strong>Step 7.</strong> The orchestrator delegates to <code>frontend-builder</code>, passing the brief, the researcher's findings, and the backend builder's summary. The frontend builder reads the API contract from the summary, adds a "Last reminder sent" column to the admin invoice table, adds a "Send reminder" button on each row that posts to the manual-trigger endpoint, handles loading and error states, and writes component tests. Runs typecheck, lint, and the test suite. All green. Returns a summary:</p>
<blockquote>
<p>Frontend implementation complete.</p>
<p>Files added:</p>
<ul>
<li><p><code>app/(admin)/invoices/_components/ReminderColumn.tsx</code></p>
</li>
<li><p><code>app/(admin)/invoices/_components/SendReminderButton.tsx</code></p>
</li>
</ul>
<p>Files edited:</p>
<ul>
<li><code>app/(admin)/invoices/page.tsx</code> (added column and button)</li>
</ul>
<p>Reused: existing admin <code>DataTable</code>, existing <code>useMutation</code> hook for the API call, existing <code>Toast</code> for success/error states. API consumed exactly as the backend summary documented.</p>
<p>Component tests added: 5. All passing.</p>
</blockquote>
<p><strong>Step 8.</strong> The orchestrator delegates to <code>test-verifier</code>, passing the approved story, the brief, and both builder summaries. The verifier writes one acceptance test file covering all six acceptance criteria plus the edge cases. Runs the new tests. Reports:</p>
<blockquote>
<p>Acceptance tests written: 8 (one per acceptance criterion plus two edge cases).</p>
<p>7 passing. 1 failing: "manual trigger is rejected across tenants" returns 200 instead of 403. Looks like the admin endpoint is not checking tenant before sending. Recommending the validator confirm.</p>
</blockquote>
<p><strong>Step 9.</strong> The orchestrator delegates to <code>implementation-validator</code>. The validator returns:</p>
<blockquote>
<p><strong>Critical:</strong> the manual trigger endpoint does not check that the admin belongs to the same tenant as the invoice. A Company A admin can trigger a reminder for a Company B invoice. (<code>app/api/admin/invoices/[id]/remind/route.ts</code>, line 14.) The <code>requireSameTenant</code> helper is imported but never called.</p>
<p><strong>Important:</strong> no test covers the case where <code>lastReminderSentAt</code> is exactly 7 days ago. Clarify whether the rule is <code>&gt;</code> or <code>&gt;=</code>.</p>
<p><strong>Minor:</strong> the new <code>ReminderColumn</code> could reuse the existing <code>RelativeTime</code> component instead of inlining its own formatter.</p>
</blockquote>
<p><strong>Step 10.</strong> Critical finding detected. The orchestrator loops back. It delegates to <code>backend-builder</code> with the validator's finding and the failing acceptance test from the verifier. Backend builder fixes and calls <code>requireSameTenant</code> in the manual-trigger endpoint, re-runs unit tests. Then the orchestrator re-runs <code>test-verifier</code>. All eight acceptance tests pass. Then <code>implementation-validator</code> runs again. Clean.</p>
<p><strong>Step 11.</strong> The orchestrator pauses for your final review and asks if you want it to open the PR.</p>
<p>That is a working factory. One prompt kicked it off. Seven agents did the focused work. The orchestrator routed the chain and paused at the three points where your judgement was needed.</p>
<h3 id="heading-version-2-the-orchestrator-as-a-subagent-advanced">Version 2: The Orchestrator as a Subagent (Advanced)</h3>
<p>Once you have lived with the skill version for a while, you may want the orchestrator to run in its own context window. The skill version inherits your main session's context. That can be fine for short features, but for longer ones the main context fills up with the chain's intermediate state.</p>
<p>Promoting the orchestrator to a subagent gives it isolation. Type <code>/agents</code> and use this description:</p>
<pre><code class="language-text">Create a project-level subagent named feature-orchestrator.

Its job: take a feature idea from the user and run the full
seven-agent chain (codebase-researcher, story-writer, spec-writer, backend-builder, frontend-builder, test-verifier,
implementation-validator), pausing for human approval after the
story and after the brief, running the build agents in order
(backend then frontend then verifier), then validating, then
looping back to the right build agent if the validator finds
critical gaps. Use the feature-factory skill for the exact step
order, including the approve, changes-requested, and rejected
paths at each human approval point.

Inputs:
- a rough feature idea from the user

Outputs:
- a finished implementation in the working directory
- a final summary of what was built, tests added, and any
  validator findings the human chose to waive at the final
  review

Tool access: Task (to invoke other subagents), Read, Bash.
Recommended model: sonnet (this needs reasoning for routing).
Recommended color: gray.

Behaviour rules:
- Use the feature-factory skill as the canonical step order.
- Always invoke other agents through subagent invocation, not
  by inlining their work.
- Always pause at the human approval points described in the
  skill. At each approval point, handle approved, changes
  requested, and rejected paths exactly as the skill defines.
- If any agent fails, surface the failure with the agent name
  and stop. Do not silently retry.
- Never edit code directly. Always go through the
  appropriate build agent.
</code></pre>
<p>The behaviour is almost identical to the skill version. The only difference is that the orchestrator now runs in its own context. You invoke it with <code>@feature-orchestrator</code> and a feature idea. The orchestrator's context is preserved across the chain. Your main session stays clean.</p>
<p>Pick one version. Run a few real features through it. The factory will reveal where it needs tuning according to your codebase.</p>
<h3 id="heading-why-this-works">Why This Works</h3>
<p>Each step reduces a different kind of ambiguity. The story reduces business ambiguity. The brief reduces technical ambiguity. The backend builder reduces API ambiguity. The frontend builder reduces UI ambiguity. The test verifier proves the user story actually holds. The validator catches what everyone else missed. By the time the chain reaches the validator, the feature has been constrained by everything that came before it. The validator only has to check the gap between what the brief asked for and what the code does.</p>
<p>The orchestrator turns that chain from "a workflow you remember to run" into "a workflow that runs itself, with you in the loop only where it matters."</p>
<p>This is the move from vibe coding to factory thinking, and it is the single biggest mindset change in this whole article.</p>
<h3 id="heading-extending-the-chain">Extending the Chain</h3>
<p>Seven agents and three human approval points are a starting point, not a ceiling. Once your basic chain is running, you can add more agents wherever you want extra rigour. A security reviewer that runs before the validator. A performance auditor that flags slow queries on the new code paths. A docs writer that updates the README from the diff. A migration reviewer that sanity-checks any Prisma changes before they merge. The pattern is the same every time: define the agent using the anatomy template, restrict its tools, plug it into the orchestrator's step order, decide whether the human needs to review its output.</p>
<p>You can also move some of the human approval points into agents if your team trusts them. The story approval is hard to remove because business intent is genuinely a human call. The brief approval can sometimes be replaced by a second spec-reviewer agent for low-risk features. The final PR approval should always stay human.</p>
<p>A factory grows the way a real codebase grows. Start small. Add what your team keeps doing by hand. Remove what no longer pays for itself.</p>
<h3 id="heading-run-reads-in-parallel-run-writes-in-sequence">Run Reads in Parallel, Run Writes in Sequence</h3>
<p>One last design rule that saves a lot of pain.</p>
<p>Read-only agents can run in parallel. They do not touch the files on disk, so two or more of them running at the same time cannot conflict. Running them in parallel is one of the easiest speed-ups you will get from this whole setup. For example, say you maintain four services and you need to refresh the docs for each one before a quarterly review. You can fire four codebase-researcher subagents in parallel, one per service. Each one reads its own codebase, summarises what changed, and returns its findings independently. Then four docs-updater agents pick up the findings, one per service, and rewrite each README in parallel. Because each docs-updater works on a different repo, they cannot collide on the same files. Four parallel reads, four parallel writes, and a job that used to drag on now finishes quickly.</p>
<p>Write agents (backend-builder, frontend-builder, test-verifier) must run in sequence. They edit files. If two of them touch the same file at the same time, you get partial writes, lost edits, broken tests, and a confused git status. Worse, the failure is silent until you notice the diff is wrong, and tracing back to which agent wrote what becomes its own debugging job.</p>
<p>The orchestrator handles this for you when you set it up correctly. Inside the build phase, backend-builder always finishes before frontend-builder starts, and frontend-builder always finishes before test-verifier starts. Outside the build phase, parallel reads are fair game.</p>
<p>Rule of thumb: anything with <code>Read</code>, <code>Grep</code>, or <code>Glob</code> access only is safe to run in parallel. Anything with <code>Edit</code>, <code>Write</code>, or <code>Bash</code> access must run alone in its lane.</p>
<h3 id="heading-failure-modes-to-expect">Failure Modes to Expect</h3>
<p>Every team running a chain like this hits the same handful of issues in the first couple of weeks. None of them break the factory. Here is what to watch for, with a quick fix for each.</p>
<ul>
<li><p><strong>Orchestrator skips a human approval.</strong> Make the approval step explicit in the skill or agent (<code>ASK HUMAN: approve the story</code>).</p>
</li>
<li><p><strong>An agent silently summarises away part of its work.</strong> Add a "what was covered / what was skipped" checklist to its output format.</p>
</li>
<li><p><strong>Validator misses something a human reviewer caught later.</strong> Add a new rule to the validator's behaviour rules. The validator gets sharper feature by feature.</p>
</li>
<li><p><strong>Session runs out of context mid-chain.</strong> Keep <code>CLAUDE.md</code> tight and start a fresh main session for each major feature.</p>
</li>
<li><p><strong>Chain runs perfectly but the spec misunderstood the business rule.</strong> This is exactly why the story approval is a hard human checkpoint.</p>
</li>
<li><p><strong>Frontend builder invents an endpoint the backend builder did not produce.</strong> Strengthen the frontend builder's rule to consume the backend summary exactly. Surface mismatches as feedback, not as patches.</p>
</li>
</ul>
<p>A good factory makes mistakes easier to catch, not harder to see.</p>
<h2 id="heading-8-the-delivery-layer-prs-reviews-and-the-new-sdlc">8. The Delivery Layer: PRs, Reviews, and the New SDLC</h2>
<p>So far this article has been close to the keyboard. Let's zoom out.</p>
<p>When AI absorbs much of the coding, testing, and documentation work, the cost of producing a software change drops. That does not mean software becomes free. It means the bottleneck moves. The slow part used to be typing, wiring, and searching. The slow part now is choosing the right feature, defining the right constraints, validating behaviour, and deciding what should ship.</p>
<p>That changes how teams are organized, how reviews are done, and how delivery pipelines work.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/ef5e86ca-dea9-4106-a254-b3f2bbeb44fc.png" alt="ef5e86ca-dea9-4106-a254-b3f2bbeb44fc" style="display:block;margin:0 auto" width="1024" height="1536" loading="lazy">

<p><em>Figure 6: How the SDLC reshapes when the orchestrator absorbs the coding work. Handoffs collapse. Review and judgement stay human.</em></p>
<h3 id="heading-one-engineer-can-now-finish-a-complete-vertical-slice">One Engineer can now Finish a Complete Vertical Slice</h3>
<p>The shape of the SDLC changes when the chain runs the heavy lifting.</p>
<p>Before, a feature moved through a queue of specialists. A frontend engineer who needed a new API endpoint waited for a backend engineer. A backend engineer who needed a UI waited for a frontend engineer. A new feature might pass through three or four people before it shipped, and most of that time the work was sitting still in someone's review queue.</p>
<p>Now, the same engineer kicks off <code>/feature-factory</code>, the chain runs end to end (backend, frontend, acceptance tests, validation), and a complete vertical slice lands as one PR. One person on the path. Zero handoffs. Section 11 returns to this and explores what it means for the team and for the wider industry. For now, what matters is that the unit of work has changed: features come out of the chain whole, not piecemeal.</p>
<h3 id="heading-stack-your-features-not-the-inside-of-one-feature">Stack Your Features, not the Inside of one Feature</h3>
<p>Once handoffs are gone, the next question is "what do I do while my last PR is in review?" The answer is the second feature. And the third.</p>
<p>The pattern that fits this is <strong>stacked PRs</strong>, but the unit of stacking is one PR per feature, not one PR per slice of a feature. Each PR is a complete vertical slice produced by one chain run.</p>
<p>It looks like this in practice. You finish Feature A. You open PR A from <code>feature-a</code> against <code>main</code>. While A is waiting for review, you do not stop. You branch <code>feature-b</code> on top of <code>feature-a</code> (not on top of <code>main</code>), kick off <code>/feature-factory</code> for the next feature, and ship PR B against <code>feature-a</code>. While both A and B are in review, you branch <code>feature-c</code> on top of <code>feature-b</code> and start the third one.</p>
<p>The order matters. A has to merge first. Then B rebases onto <code>main</code> and merges. Then C rebases onto <code>main</code> and merges. Tools like Graphite, Sapling, or git's own <code>git rebase --onto</code> handle the rebasing automatically when an upstream PR merges. You do not need to think about it most of the time.</p>
<p>Two rules keep this safe.</p>
<p>First, <strong>respect the chain.</strong> If C depends on B, do not try to merge C before B. The branch graph already enforces this, but it is worth saying out loud because the temptation to skip ahead is real when an early PR is taking too long to review.</p>
<p>Second, <strong>do not split one feature across the stack.</strong> A single feature should be one PR. If you find yourself wanting to put the migration in PR 1, the backend in PR 2, and the UI in PR 3, that usually means the chain produced too much in one run. Go back, split at the story level (Section 7), and run two smaller chains instead. Each chain still produces one feature, and each feature still ships as one PR.</p>
<p>The factory's whole point is that one engineer can finish a feature without waiting for anyone. Stacked PRs are how you keep that going across multiple features without blocking yourself on your own review queue.</p>
<p>This is where the software industry is heading. Smaller teams, fewer handoffs, every engineer shipping complete features end to end. The teams that get there first will not be the ones with the best AI tools. They will be the ones who built the cleanest factories around the AI tools they already have.</p>
<h3 id="heading-add-a-pr-reviewer-agent">Add a PR Reviewer Agent</h3>
<p>A team using AI needs a PR review pattern that is consistent across both human and AI reviewers. The single most useful artifact for that consistency is a short, explicit checklist that every PR is reviewed against. Without it, review becomes subjective. With it, everyone checks for the same things every time.</p>
<p>I covered AI-assisted PR review in detail in <a href="https://www.freecodecamp.org/news/how-to-unblock-ai-pr-review-bottleneck-handbook/">my previous article on unblocking the AI PR review bottleneck</a>, including the full checklist I use, the rules that work, and the ones that quietly do not. If you have not read it, do that next. The factory you just built is the upstream half of that workflow. PR review is the downstream half.</p>
<p>For the factory specifically, the cleanest place to put the checklist is inside another agent. Use the <code>/agents</code> slash command and create a <code>pr-reviewer</code> agent the same way you created the seven in Section 6:</p>
<pre><code class="language-text">Create a project-level subagent named pr-reviewer.

Its job: review a pull request against this project's review
checklist and report findings grouped by severity. It does
not edit files or merge PRs.

Inputs:
- a PR or a diff to review
- CLAUDE.md and any project-level rules

Outputs, grouped by severity:
- critical (must fix before merge)
- important (should fix before merge)
- minor (nice to have)

Always check for:
- Scope: one clear purpose, no unrelated refactoring,
  no unrelated files.
- Tests: unit tests cover the core behaviour, failure
  cases tested, existing tests still pass.
- Security and tenant safety: auth checks, tenant isolation
  preserved, no secrets in logs or error responses.
- Architecture: business logic out of UI and API route
  handlers, existing patterns from CLAUDE.md respected,
  no unjustified new dependencies.
- Documentation: README or feature docs updated for
  user-facing changes, technical debt acknowledged in
  the PR description.

Tool access: Read, Grep, Glob, Bash (for git commands only).
Recommended model: sonnet (this needs careful reasoning).
Recommended color: orange.

Behaviour rules:
- Never edit files.
- Never merge or close PRs.
- Cite file paths and line numbers for every finding.
- Mark opinion-based findings clearly so reviewers can
  ignore them safely.
</code></pre>
<p>Claude generates the file, you review and commit it, and now your project has a consistent reviewer that humans and AI invoke the same way: <code>@pr-reviewer review this PR</code>. You can also wire it into your CI pipeline so every developer handles their own PR feedback before a human reviewer ever sees it. The load on reviewers drops.</p>
<p>This pattern matters because the agent becomes the single source of truth. Humans read its findings before merging. The orchestrator from Section 7 can invoke it as the final step before opening a PR. CI can run it on every push. The checklist lives in one place and updates in one place. When your team learns a new failure mode, you add it to the agent's behaviour rules, and the next review picks it up automatically.</p>
<h3 id="heading-cloud-reviewers-are-functions-not-colleagues">Cloud Reviewers are Functions, not Colleagues</h3>
<p>AI is starting to live inside CI pipelines: PR review bots, security scanners, release-note generators, issue triagers. That is genuinely useful. But the language matters.</p>
<p>If you say "Claude approved this PR," you have already made a small mistake. Cloud-based AI is not a teammate. It is not a developer. It is not accountable for the decision. The right sentence is "Claude ran the review workflow against the project's review checklist and reported findings, and a human decided the PR was safe to merge." Accountability stays with the human.</p>
<p>There is a practical reason for this discipline. Cloud reviewers are good at the things they were prompted to look for: missing tests, naming inconsistencies, duplicate helpers. They miss things outside their checklist. If your checklist does not specifically tell the reviewer to verify tenant isolation in invoice download endpoints, the AI reviewer might still let through a bug where a user from Company A can download an invoice from Company B. That is why a project-specific review checklist is so much more valuable than a generic AI reviewer.</p>
<h3 id="heading-where-humans-win">Where Humans Win</h3>
<p>AI review is not approval. AI can help find issues. It can summarize complex changes. It can compare code against a checklist. It can suggest tests. But humans still own the decisions that matter: does this solve the right problem, is this an acceptable trade-off, should it ship now, should it ship behind a feature flag, do we need more user data first?</p>
<p>That judgement is still human work. The best AI-assisted teams are not the ones that remove humans. They are the ones that put humans where their judgement matters most.</p>
<h2 id="heading-9-build-your-first-claude-powered-software-factory">9. Build Your First Claude-Powered Software Factory</h2>
<p>Theory is done. Here is the checklist to stand up the factory in your own project. Each step points back to the section that explains the why.</p>
<table>
<thead>
<tr>
<th>#</th>
<th>Step</th>
<th>Where</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Install Claude Code from the official docs</td>
<td><a href="https://code.claude.com/docs/en/desktop">https://code.claude.com/docs/en/desktop</a></td>
</tr>
<tr>
<td>2</td>
<td>Create the folder structure (<code>.claude/agents</code>, <code>.claude/skills/feature-factory</code>, <code>.claude/skills/build-with-tests</code>, <code>.claude/hooks</code>, <code>CLAUDE.md</code>)</td>
<td>Section 5</td>
</tr>
<tr>
<td>3</td>
<td>Write <code>CLAUDE.md</code> (100-300 lines, project facts and rules)</td>
<td>Section 5</td>
</tr>
<tr>
<td>4</td>
<td>Create the seven subagents via <code>/agents</code></td>
<td>Section 6</td>
</tr>
<tr>
<td>5</td>
<td>Create the <code>feature-factory</code> orchestrator skill</td>
<td>Section 7</td>
</tr>
<tr>
<td>6</td>
<td>Create the <code>build-with-tests</code> skill</td>
<td>Section 5</td>
</tr>
<tr>
<td>7</td>
<td>Add the pre-commit hook and make it executable</td>
<td>Section 5</td>
</tr>
<tr>
<td>8</td>
<td>Create the <code>pr-reviewer</code> agent</td>
<td>Section 8</td>
</tr>
<tr>
<td>9</td>
<td>Run one real feature through the chain</td>
<td>below</td>
</tr>
</tbody></table>
<p>Total time: two to three hours for the first version.</p>
<h3 id="heading-when-you-run-the-first-real-feature">When You Run the First Real Feature</h3>
<p>Pick something small. An admin tool, a new API endpoint with a tiny UI tile. Open Claude Code:</p>
<pre><code class="language-text">/feature-factory

I want to &lt;describe the feature in one sentence&gt;.
</code></pre>
<p>The chain will run. Approve the story. Approve the brief. Read the validator report. Open the PR.</p>
<p>The first time will not be perfect. Things to note as you go:</p>
<ul>
<li><p>Researcher's output too shallow? Strengthen its description.</p>
</li>
<li><p>Story writer missed an edge case? Add a rule to its description.</p>
</li>
<li><p>Spec missed a risk? Add the rule to <code>CLAUDE.md</code>.</p>
</li>
<li><p>Backend builder touched a frontend file? Tighten its scope rule.</p>
</li>
<li><p>Frontend builder invented an endpoint? Tighten the API-consumption rule.</p>
</li>
<li><p>Validator missed something a human caught later? Add a check to its rules.</p>
</li>
<li><p>Hook should have caught something earlier? Add to it.</p>
</li>
</ul>
<p>After three or four features, the factory tunes itself. You will spend less time supervising and more time deciding what to build next.</p>
<h2 id="heading-part-3-wrap-up">Part 3: Wrap Up</h2>
<h2 id="heading-10-what-i-did-not-cover-and-where-to-go-next">10. What I Did Not Cover (and Where to Go Next)</h2>
<p>AI-assisted development is a huge surface area, and one article cannot cover it all. Here are the topics I deliberately left out, in the order I would explore them next.</p>
<h3 id="heading-centralized-memory-management-across-sessions">Centralized Memory Management Across Sessions</h3>
<p>Once you start running multiple sessions in parallel (one per feature, one per branch, one per teammate) you start wishing the AI shared memory across them. Things like Claude's project-level memory, MCP-based shared knowledge stores, and team-wide vector stores fit here. This is a fast-moving area and worth a dedicated read.</p>
<h3 id="heading-running-agents-in-parallel">Running Agents in Parallel</h3>
<p>Claude Code subagents can run in parallel inside a single session. So can multiple sessions across worktrees with tools that wrap Claude Code (Nimbalyst is one example). Once your factory is stable, parallelism gives you the next big speed-up. Be careful with merge conflicts and CI cost.</p>
<h3 id="heading-cloud-based-unattended-agents">Cloud-Based Unattended Agents</h3>
<p>Running Claude Code or similar agents on a server, triggered by events (a webhook, a cron, a new GitHub issue) lets your factory work while you sleep. The honest state of this in 2026 is that it works for narrow tasks like PR review and triage. It is not yet trustworthy for unattended feature work without strong validation gates.</p>
<h3 id="heading-custom-mcp-servers-for-your-business">Custom MCP Servers for Your Business</h3>
<p>MCP (Model Context Protocol) lets you expose internal systems like your billing data, your customer support tickets, and your design system to Claude as tools. A well-built MCP server turns Claude from a coding assistant into something closer to a junior teammate who knows your business. Worth a deep look once your basic factory is in place.</p>
<h3 id="heading-cost-optimization-at-scale">Cost Optimization at Scale</h3>
<p>Once a team uses this workflow daily, token cost becomes a real budget line. Routing inspection and review to Haiku, reasoning work to Sonnet, and only the heaviest planning to Opus is the simplest lever. Caching, batching, and trimming context are the next ones.</p>
<h3 id="heading-extending-into-product-design-and-support">Extending into Product, Design, and Support</h3>
<p>This article is developer-focused, but the same shape applies to product owners, designers, and support engineers. They benefit from skills, subagents, and hooks too. The biggest team-level wins come when those roles also build their own corner of the factory and the dev team can call into theirs.</p>
<p>If you want to go deeper, the official Claude Code documentation is the most up-to-date source for subagents, skills, hooks, and MCP. Anthropic also publishes a free introduction-to-subagents course that pairs well with this article.</p>
<h2 id="heading-11-closing-thoughts">11. Closing Thoughts</h2>
<p>This article opened with a single idea: use AI to automate structured work, not chaotic work. The eleven sections in between are what that looks like in practice.</p>
<p>So before you automate anything, define the system. Write the rules in <code>CLAUDE.md</code>. Generate the skills your team keeps retyping. Create the agents that do focused work. Wire up the orchestrator. Add the gates. And keep humans in the loop where judgement matters, not where typing matters.</p>
<p>A software factory is not a giant autonomous machine that builds your product overnight. It is a small set of files in your repository that turn one developer plus one AI into a controlled team. The agents are the asset. The factory is how you put them to work.</p>
<h3 id="heading-the-new-way-of-working">The New Way of Working</h3>
<p>Section 8 introduced the idea that one engineer can ship a full vertical slice. Step back from the keyboard for a moment and look at what that means for the team, not just for one developer.</p>
<p>Software has always moved through handoffs. A product owner writes a story, a lead developer turns it into a specification, a backend engineer builds the API, a frontend engineer builds the UI, a payments specialist handles the integration. By the time the feature ships, four or five people have touched it, each waiting for the previous one to finish. Every handoff was time the work spent sitting still.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/2aa870cf-17f7-4fc1-8b7c-14095bb61980.png" alt="2aa870cf-17f7-4fc1-8b7c-14095bb61980" style="display:block;margin:0 auto" width="2172" height="724" loading="lazy">

<p><em>Figure 7: The old shape. Every arrow is a handoff. Every handoff is a wait.</em></p>
<p>The factory dissolves most of those handoffs because the expertise is no longer trapped inside the people. It is shared, in the form of agents.</p>
<p>A frontend engineer who has never written a Stripe webhook can still ship a feature that needs one, because the team's payments specialist has already built and tuned a <code>payments-integration</code> agent. A backend engineer who has never built a Recharts dashboard can ship a feature that needs one, because the frontend lead has built a <code>dashboard-component-builder</code> agent. The QA engineer's <code>regression-suite-writer</code> agent is available to everyone. The DevOps engineer's <code>ci-pipeline-updater</code> agent is available to everyone. The security engineer's <code>auth-checker</code> agent runs as part of every chain.</p>
<p>The result is that one engineer can finish a complete vertical slice on their own.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69cae64c9fffa7474087a0d4/64d37829-30cc-46bc-9047-72f34081ab12.png" alt="64d37829-30cc-46bc-9047-72f34081ab12" style="display:block;margin:0 auto" width="1024" height="1536" loading="lazy">

<p><em>Figure 8: The new shape. Every engineer pulls from the same agent library. Specialists still exist, but their expertise lives in the agents they maintain, not in their availability for handoffs.</em></p>
<p>Look at what changed. The specialists are still there. The frontend lead still owns the design system. The payments specialist still owns the Stripe integration. The DevOps engineer still owns the CI pipeline. They still bring the taste and judgement that nobody else on the team has. What changed is that their expertise is now portable. It rides inside agents that anyone on the team can invoke.</p>
<p>This shift compounds in three ways:</p>
<p><strong>Cycle time drops.</strong> A feature that used to wait for three engineers' time now waits for none. The chain runs end to end for one engineer. The PR opens the same day instead of the same week.</p>
<p><strong>Specialists do their best work.</strong> Before, a senior payments engineer spent half their week unblocking other engineers' Stripe integrations. Now they spend that week improving the <code>payments-integration</code> agent itself. The leverage is much higher. One improvement to the agent benefits every feature the team ships from that point on.</p>
<p><strong>Team scaling looks different.</strong> Before, hiring a tenth engineer added a tenth set of handoffs. Now, hiring a tenth engineer adds a tenth full-stack contributor who immediately benefits from every agent the existing nine have built. Onboarding speed increases. Coordination cost drops.</p>
<p>This is the broader shift the article is pointing at. The factory is not just a productivity trick for one developer. It is how an engineering team starts to look more like a community of full-stack contributors who share their expertise as code, and less like a relay race where every baton pass costs a day.</p>
<p>The teams that figure this out first will not be the ones with the largest headcount or the biggest AI budget. They will be the ones whose agent libraries reflect their team's collective taste, kept current, kept small, kept tight. The agents are the asset. The factory is how you put them to work.</p>
<h3 id="heading-a-short-note">A Short Note</h3>
<p>The shape of this workflow will keep evolving as the tools evolve, and every team has its own way of working. What I have shared here is the smallest version that has actually held up under deadline pressure on real production work. It is not the final word. It is a starting point you can adapt to your team, your stack, and your taste.</p>
<p>If you build a version of this in your own team, I would love to hear what worked and what did not. The fastest way to improve a workflow is to read about other people's failure modes. Good luck building your factory.</p>
<h3 id="heading-resources">Resources</h3>
<p><strong>Claude Code</strong></p>
<ul>
<li><p>Claude Code overview: <a href="https://code.claude.com/docs/en/overview">code.claude.com/docs/en/overview</a></p>
</li>
<li><p>Subagents: <a href="https://code.claude.com/docs/en/sub-agents">code.claude.com/docs/en/sub-agents</a></p>
</li>
<li><p>Skills: <a href="https://docs.anthropic.com/en/docs/claude-code/slash-commands">docs.anthropic.com/en/docs/claude-code/slash-commands</a></p>
</li>
<li><p>Memory and <code>CLAUDE.md</code>: <a href="https://docs.anthropic.com/en/docs/claude-code/memory">docs.anthropic.com/en/docs/claude-code/memory</a></p>
</li>
<li><p>Hooks reference: <a href="https://code.claude.com/docs/en/hooks">code.claude.com/docs/en/hooks</a></p>
</li>
<li><p>Hooks guide: <a href="https://code.claude.com/docs/en/hooks-guide">code.claude.com/docs/en/hooks-guide</a></p>
</li>
</ul>
<p><strong>Other AI IDEs (the same patterns apply)</strong></p>
<ul>
<li><p>Cursor: <a href="https://cursor.com">cursor.com</a></p>
</li>
<li><p>Aider: <a href="https://aider.chat">aider.chat</a></p>
</li>
<li><p>Cline: <a href="https://cline.bot">cline.bot</a></p>
</li>
</ul>
<p><strong>Tools mentioned in the article</strong></p>
<ul>
<li><p>MCP documentation: <a href="https://modelcontextprotocol.io">modelcontextprotocol.io</a></p>
</li>
<li><p>Context7 (current docs plugin): <a href="https://context7.com">context7.com</a></p>
</li>
<li><p>Nimbalyst (visual workspace for parallel Claude Code sessions): <a href="https://nimbalyst.com">nimbalyst.com</a></p>
</li>
<li><p>Graphite (stacked PRs): <a href="https://graphite.dev">graphite.dev</a></p>
</li>
<li><p>Sapling (stacked PRs): <a href="https://sapling-scm.com">sapling-scm.com</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Agentic Terminal Workflow with GitHub Copilot CLI and MCP Servers ]]>
                </title>
                <description>
                    <![CDATA[ Most developers live in their terminal. You run commands, debug pipelines, manage infrastructure, and navigate codebases, all from a shell prompt. But despite how central the terminal is to developer  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-an-agentic-terminal-workflow-with-github-copilot-cli-and-mcp-servers/</link>
                <guid isPermaLink="false">69f212526e0124c05e1857b5</guid>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mcp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mcp server ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Caleb Mintoumba ]]>
                </dc:creator>
                <pubDate>Wed, 29 Apr 2026 14:14:42 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3e4e3d7e-6cbf-4742-a63b-f9a2579f2318.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most developers live in their terminal. You run commands, debug pipelines, manage infrastructure, and navigate codebases, all from a shell prompt.</p>
<p>But despite how central the terminal is to developer workflows, AI assistance there has remained shallow: autocomplete a command here, explain an error there.</p>
<p>That changes when you combine GitHub Copilot CLI with MCP (Model Context Protocol) servers. Instead of an AI that reacts to isolated prompts, you get a terminal that understands your project context, queries live data sources, and chains tool calls autonomously – what the industry is starting to call an agentic workflow.</p>
<p>In this tutorial, you'll learn exactly how to wire these two systems together, step by step. By the end, your terminal will be able to do things like understand your Git history before suggesting a fix, query your running Docker containers before writing a compose patch, or pull live API schemas before generating a request.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-is-github-copilot-cli">What is GitHub Copilot CLI?</a></p>
</li>
<li><p><a href="#heading-what-is-the-model-context-protocol">What is the Model Context Protocol?</a></p>
</li>
<li><p><a href="#heading-how-mcp-servers-work-in-a-terminal-context">How MCP Servers Work in a Terminal Context</a></p>
</li>
<li><p><a href="#heading-step-1-install-and-configure-github-copilot-cli">Step 1 – Install and Configure GitHub Copilot CLI</a></p>
</li>
<li><p><a href="#heading-step-2-set-up-your-first-mcp-server">Step 2 – Set Up Your First MCP Server</a></p>
</li>
<li><p><a href="#heading-step-3-wire-copilot-cli-to-your-mcp-server">Step 3 – Wire Copilot CLI to Your MCP Server</a></p>
</li>
<li><p><a href="#heading-step-4-build-a-real-agentic-workflow">Step 4 – Build a Real Agentic Workflow</a></p>
</li>
<li><p><a href="#heading-step-5-extend-with-multiple-mcp-servers">Step 5 – Extend with Multiple MCP Servers</a></p>
</li>
<li><p><a href="#heading-debugging-common-issues">Debugging Common Issues</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before you start, make sure you have the following:</p>
<ul>
<li><p><strong>Node.js</strong> v18 or later (<code>node --version</code>)</p>
</li>
<li><p><strong>npm</strong> v9 or later</p>
</li>
<li><p>A GitHub account with Copilot enabled. The free tier (available to all GitHub users) is sufficient to follow this tutorial. Pro, Business, and Enterprise plans unlock higher usage limits but aren't required.</p>
</li>
<li><p><strong>GitHub CLI</strong> (<code>gh</code>) installed. We'll use it to authenticate.</p>
</li>
<li><p>Basic familiarity with the terminal and JSON configuration files</p>
</li>
<li><p>(Optional) <strong>Docker</strong> installed if you want to follow the Docker MCP example in Step 5</p>
</li>
</ul>
<p>You don't need prior experience with MCP or agentic AI systems, as this guide builds that understanding from the ground up.</p>
<h2 id="heading-what-is-github-copilot-cli">What is GitHub Copilot CLI?</h2>
<p>GitHub Copilot CLI is the terminal-native interface to GitHub's Copilot AI. Unlike the IDE plugin (which assists with code completion), Copilot CLI is designed specifically for shell workflows. It exposes three main commands:</p>
<ul>
<li><p><code>gh copilot suggest</code> proposes a shell command based on a natural language description</p>
</li>
<li><p><code>gh copilot explain</code> explains what a given command does</p>
</li>
<li><p><code>gh copilot alias</code> generates shell aliases for Copilot subcommands</p>
</li>
</ul>
<p>Here's a quick example of <code>suggest</code> in action:</p>
<pre><code class="language-shell">gh copilot suggest "find all files modified in the last 24 hours and larger than 1MB"
</code></pre>
<p>Copilot will return something like:</p>
<pre><code class="language-shell">find . -mtime -1 -size +1M
</code></pre>
<p>It will also ask if you want to copy it, run it directly, or revise the request. This interactive loop is already useful – but by itself, Copilot CLI has no awareness of your project context. It doesn't know your repo structure, your running services, or your deployment environment. That's where MCP comes in.</p>
<h2 id="heading-what-is-the-model-context-protocol">What is the Model Context Protocol?</h2>
<p>The <strong>Model Context Protocol (MCP)</strong> is an open standard introduced by Anthropic in late 2024. Its goal is straightforward: give AI models a standardized way to connect to external tools, data sources, and services.</p>
<p>Think of MCP as a universal adapter layer between an AI model and the real world. Without MCP, each AI integration is custom-built: one plugin for GitHub, another for Postgres, another for Slack, all with incompatible interfaces. MCP defines a single protocol that any tool can implement, and any compatible AI client can consume.</p>
<p>An MCP server exposes <strong>tools</strong> (functions the AI can call), <strong>resources</strong> (data the AI can read), and <strong>prompts</strong> (reusable instruction templates). The AI client in our case, a Copilot-powered terminal discovers these capabilities at runtime and uses them autonomously to complete a task.</p>
<p>A few notable MCP servers that are already production-ready:</p>
<table>
<thead>
<tr>
<th>MCP Server</th>
<th>What it exposes</th>
</tr>
</thead>
<tbody><tr>
<td>@modelcontextprotocol/server-filesystem</td>
<td>Read/write access to local files</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-git</td>
<td>Git log, diff, blame, branch operations</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-github</td>
<td>GitHub Issues, PRs, repos via API</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-postgres</td>
<td>Live query execution on a Postgres DB</td>
</tr>
<tr>
<td>@modelcontextprotocol/server-docker</td>
<td>Container inspection, logs, stats</td>
</tr>
</tbody></table>
<p>The full registry lives at <code>github.com/modelcontextprotocol/servers</code>.</p>
<h2 id="heading-how-mcp-servers-work-in-a-terminal-context">How MCP Servers Work in a Terminal Context</h2>
<p>Before we get hands-on, it's worth understanding the communication model.</p>
<p>MCP servers run as local processes. They communicate with the AI client over <strong>stdio</strong> (standard input/output) or over an <strong>HTTP/SSE transport</strong>. The client sends JSON-RPC messages to the server, and the server responds with structured data.</p>
<p>Here's the simplified flow:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/e1844dc5-a869-4201-ad8a-fd1cb305f646.png" alt="An architectural flowchart illustrating the Model Context Protocol (MCP) workflow. The process starts with a user typing a natural language prompt, passes through the Copilot CLI (the MCP client), communicates via JSON-RPC over stdio with an MCP Server (e.g., server-git), executes real tools like git log, returns a structured result to Copilot, which finally synthesizes a context-aware response for the user." style="display:block;margin:0 auto" width="1408" height="768" loading="lazy">

<p>The key word here is <strong>grounded</strong>. Without MCP, Copilot responds based purely on its training data and your prompt. With MCP, it can call <code>git log --oneline -20</code> before answering your question about recent regressions and its answer is based on <em>your actual code history</em>, not a generalized assumption.</p>
<h3 id="heading-step-1-install-and-configure-github-copilot-cli">Step 1 – Install and Configure GitHub Copilot CLI</h3>
<p>If you haven't already, install the GitHub CLI:</p>
<pre><code class="language-shell"># macOS
brew install gh

# Ubuntu/Debian
sudo apt install gh

# Windows (via winget)
winget install --id GitHub.cli
</code></pre>
<p>Then authenticate:</p>
<pre><code class="language-shell">gh auth login
</code></pre>
<p>Follow the interactive prompts. Select <strong>GitHub.com</strong>, then <strong>HTTPS</strong>, and authenticate via browser when prompted.</p>
<p>Now install the Copilot CLI extension:</p>
<pre><code class="language-shell">gh extension install github/gh-copilot
</code></pre>
<p>Verify the installation:</p>
<pre><code class="language-shell">gh copilot --version
</code></pre>
<p>You should see output like <code>gh-copilot version 1.x.x</code>.</p>
<p><strong>Optional but recommended: set up shell aliases.</strong> This makes the workflow much faster. For <code>bash</code> or <code>zsh</code>:</p>
<pre><code class="language-shell"># Add to your ~/.bashrc or ~/.zshrc
eval "$(gh copilot alias -- bash)"   # for bash
eval "$(gh copilot alias -- zsh)"    # for zsh
</code></pre>
<p>After reloading your shell (<code>source ~/.bashrc</code>), you can use <code>ghcs</code> as shorthand for <code>gh copilot suggest</code> and <code>ghce</code> for <code>gh copilot explain</code>.</p>
<h3 id="heading-step-2-set-up-your-first-mcp-server">Step 2 – Set Up Your First MCP Server</h3>
<p>We'll start with <code>server-git</code>. It's the most immediately useful for a development workflow and has zero external dependencies.</p>
<p>Install it globally via npm:</p>
<pre><code class="language-shell">npm install -g @modelcontextprotocol/server-git
</code></pre>
<p>Test that it runs:</p>
<pre><code class="language-shell">mcp-server-git --version
</code></pre>
<p>This server exposes the following tools to any compatible MCP client:</p>
<ul>
<li><p><code>git_log</code> retrieve commit history with filters</p>
</li>
<li><p><code>git_diff</code> diff between branches or commits</p>
</li>
<li><p><code>git_status</code> current working tree status</p>
</li>
<li><p><code>git_show</code> inspect a specific commit</p>
</li>
<li><p><code>git_blame</code> annotate file lines with commit info</p>
</li>
<li><p><code>git_branch</code> list or switch branches</p>
</li>
</ul>
<p>Now create a configuration file. MCP clients look for a file called <code>mcp.json</code> to discover available servers. Create it in your project root or in a global config directory:</p>
<pre><code class="language-shell">mkdir -p ~/.config/mcp
touch ~/.config/mcp/mcp.json
</code></pre>
<p>Add the following content:</p>
<pre><code class="language-markdown">{
  "mcpServers": {
    "git": {
      "command": "mcp-server-git",
      "args": ["--repository", "."],
      "transport": "stdio"
    }
  }
}
</code></pre>
<p>A few notes on this config:</p>
<ul>
<li><p><code>command</code> is the binary to run. Make sure it's on your <code>$PATH</code>.</p>
</li>
<li><p><code>args</code> passes <code>--repository .</code> so the server scopes itself to the current working directory.</p>
</li>
<li><p><code>transport: "stdio"</code> means communication happens over standard input/output the simplest and most stable option for local servers.</p>
</li>
</ul>
<h3 id="heading-step-3-wire-copilot-cli-to-your-mcp-server">Step 3 – Wire Copilot CLI to Your MCP Server</h3>
<p>This is where the two systems connect. GitHub Copilot CLI supports MCP via its <code>--mcp-config</code> flag (available from version 1.3+). You point it at your <code>mcp.json</code>, and Copilot will automatically initialize the declared servers before processing your prompt.</p>
<p>Here's the basic invocation:</p>
<pre><code class="language-shell">gh copilot suggest --mcp-config ~/.config/mcp/mcp.json "why did the build break in the last commit?"
</code></pre>
<p>When you run this inside a Git repository, Copilot CLI will:</p>
<ol>
<li><p>Start the <code>mcp-server-git</code> process</p>
</li>
<li><p>Call <code>git_log</code> to retrieve recent commits</p>
</li>
<li><p>Call <code>git_diff</code> on the most recent commit</p>
</li>
<li><p>Synthesize an answer based on the actual diff output</p>
</li>
</ol>
<p>Try it yourself on a repo with a recent failing commit. The difference in response quality compared to a plain <code>gh copilot suggest</code> is immediately obvious.</p>
<p><strong>Tip: avoid retyping the flag every time.</strong> Add a shell function to your <code>.bashrc</code>/<code>.zshrc</code>:</p>
<pre><code class="language-shell">function aterm() {
  gh copilot suggest --mcp-config ~/.config/mcp/mcp.json "$@"
}
</code></pre>
<p>Now you just type:</p>
<pre><code class="language-shell">aterm "what changed between main and feature/auth?"
</code></pre>
<p>And you're running a fully context-aware, MCP-powered query from a single short command. This function name <code>aterm</code> for <em>agentic terminal</em> is what we'll use throughout the rest of this tutorial.</p>
<h3 id="heading-step-4-build-a-real-agentic-workflow">Step 4 – Build a Real Agentic Workflow</h3>
<p>Let's move beyond individual queries and build a workflow that chains multiple tool calls to complete a real developer task: <strong>diagnosing a regression</strong>.</p>
<p>Imagine you pushed a feature branch and your CI pipeline failed. You don't know exactly which change caused it. Here's how your agentic terminal handles it:</p>
<h4 id="heading-query-1-understand-what-changed">Query 1: understand what changed</h4>
<pre><code class="language-shell">aterm "summarize all commits on feature/auth that aren't on main yet"
</code></pre>
<p>Copilot calls <code>git_log</code> with branch filters, then returns a structured summary of commits unique to your branch. No copy-pasting SHAs manually.</p>
<h4 id="heading-query-2-isolate-the-diff">Query 2: isolate the diff</h4>
<pre><code class="language-shell">aterm "show me everything that changed in the auth middleware between main and feature/auth"
</code></pre>
<p>This triggers <code>git_diff</code> scoped to the path containing your middleware. Copilot returns the diff with an explanation of what each change does.</p>
<h4 id="heading-query-3-find-the-likely-culprit">Query 3: find the likely culprit</h4>
<pre><code class="language-shell">aterm "which of those changes could cause a JWT validation failure?"
</code></pre>
<p>At this point, Copilot has the diff in its context window from the previous tool calls. It reasons over the actual code changes not generic knowledge about JWT and pinpoints the likely issue.</p>
<h4 id="heading-query-4-generate-the-fix">Query 4: generate the fix</h4>
<pre><code class="language-shell">aterm "write the corrected version of that validation function"
</code></pre>
<p>Copilot generates a targeted fix based on the specific code it retrieved via MCP. You get a patch you can directly apply, not a generic code template.</p>
<p>This four-step sequence – understand, isolate, reason, fix – is a complete agentic loop. Each step is grounded in live repository data retrieved through MCP tools. The AI is not hallucinating context. Instead, it's reading your actual codebase.</p>
<h3 id="heading-step-5-extend-with-multiple-mcp-servers">Step 5 – Extend with Multiple MCP Servers</h3>
<p>One MCP server is useful. Multiple MCP servers working together is where the workflow becomes genuinely powerful. Let's add two more: <code>server-filesystem</code> and <code>server-docker</code>.</p>
<p>Install the additional servers:</p>
<pre><code class="language-shell">npm install -g @modelcontextprotocol/server-filesystem
npm install -g @modelcontextprotocol/server-docker
</code></pre>
<p>Update your <code>mcp.json</code>:</p>
<pre><code class="language-markdown">{
  "mcpServers": {
    "git": {
      "command": "mcp-server-git",
      "args": ["--repository", "."],
      "transport": "stdio"
    },
    "filesystem": {
      "command": "mcp-server-filesystem",
      "args": ["--root", "."],
      "transport": "stdio"
    },
    "docker": {
      "command": "mcp-server-docker",
      "transport": "stdio"
    }
  }
}
</code></pre>
<p>With all three servers active, your terminal can now answer cross-domain questions:</p>
<pre><code class="language-shell">aterm "my Express app container keeps restarting, check the logs and compare with what the healthcheck in my Dockerfile expects"
</code></pre>
<p>To answer this, Copilot will:</p>
<ol>
<li><p>Call <code>docker_logs</code> (server-docker) to pull the container's recent stderr output</p>
</li>
<li><p>Call <code>read_file</code> (server-filesystem) to read your <code>Dockerfile</code></p>
</li>
<li><p>Parse the <code>HEALTHCHECK</code> instruction</p>
</li>
<li><p>Cross-reference the log errors with the health endpoint path</p>
</li>
<li><p>Return a diagnosis explaining the mismatch and suggest the fix</p>
</li>
</ol>
<p>This is an <strong>agentic workflow</strong>: the model autonomously decides which tools to call, in what order, and synthesizes the results into a coherent answer. You didn't tell it to read the Dockerfile. It inferred that was necessary based on your question.</p>
<p><strong>A note on security:</strong> When running <code>server-filesystem</code>, always scope it to a specific directory using <code>--root</code>. Never point it at <code>/</code> or your home directory. Similarly, <code>server-docker</code> has access to your Docker socket run it only in trusted environments.</p>
<h2 id="heading-debugging-common-issues">Debugging Common Issues</h2>
<p><code>mcp-server-git: command not found</code></p>
<p>The npm global bin directory isn't on your <code>$PATH</code>. Fix:</p>
<pre><code class="language-shell">export PATH="\(PATH:\)(npm bin -g)"
# or for newer npm versions:
export PATH="\(PATH:\)(npm prefix -g)/bin"
</code></pre>
<p>Add this line to your <code>.bashrc</code>/<code>.zshrc</code> to persist it.</p>
<h4 id="heading-copilot-cli-doesnt-seem-to-be-using-mcp-tools">Copilot CLI doesn't seem to be using MCP tools</h4>
<p>Check your Copilot CLI version:</p>
<pre><code class="language-shell">gh copilot --version
</code></pre>
<p>MCP support requires version 1.3 or later. Update with:</p>
<pre><code class="language-shell">gh extension upgrade copilot
</code></pre>
<p>Also verify your <code>mcp.json</code> is valid JSON a trailing comma or missing bracket will silently prevent server initialization.</p>
<h4 id="heading-mcp-server-starts-but-returns-no-data">MCP server starts but returns no data</h4>
<p>Run the server manually to check for errors:</p>
<pre><code class="language-shell">mcp-server-git --repository .
</code></pre>
<p>If it exits immediately, check that you're running the command inside a valid Git repository. For <code>server-docker</code>, make sure the Docker daemon is running and your user has access to the Docker socket:</p>
<pre><code class="language-shell">sudo usermod -aG docker $USER
# Then log out and back in
</code></pre>
<h4 id="heading-responses-are-slow-with-multiple-servers">Responses are slow with multiple servers</h4>
<p>Each MCP server is a separate subprocess. Spawning several at once adds startup latency, especially on slower machines. Two optimizations:</p>
<ol>
<li><p>Only declare the servers you actually need for a given project in your <code>mcp.json</code></p>
</li>
<li><p>Use project-specific config files instead of one global config:</p>
</li>
</ol>
<pre><code class="language-shell"># project A (backend)
aterm --mcp-config ./mcp-backend.json "..."

# project B (infra)
aterm --mcp-config ./mcp-infra.json "..."
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You've just built an agentic terminal workflow from scratch. Here's a quick recap of what you did:</p>
<ul>
<li><p>Installed and configured GitHub Copilot CLI with shell aliases for fast access</p>
</li>
<li><p>Set up MCP servers (<code>server-git</code>, <code>server-filesystem</code>, <code>server-docker</code>) and wired them through a <code>mcp.json</code> config</p>
</li>
<li><p>Created a shell function (<code>aterm</code>) that transparently passes your MCP config to every Copilot query</p>
</li>
<li><p>Built a multi-step agentic loop for diagnosing regressions using live Git data</p>
</li>
<li><p>Extended the setup with cross-domain tool orchestration across Git, filesystem, and Docker</p>
</li>
</ul>
<p>The architecture you've built here is not a demo – it's a production-ready pattern. You can extend it with any MCP-compatible server: <code>server-postgres</code> for database-aware queries, <code>server-github</code> for issue and PR context, or custom MCP servers you write yourself for your internal APIs.</p>
<p>The terminal has always been the most powerful surface in a developer's environment. With Copilot CLI and MCP, it's finally becoming an intelligent one.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Context Hub (chub) to Build a Companion Relevance Engine
 ]]>
                </title>
                <description>
                    <![CDATA[ Large language models can write code quickly, but they still misremember APIs, miss version-specific details, and forget what they learned at the end of a session. That is the problem Context Hub is t ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-context-hub-chub-to-build-a-companion-relevance-engine/</link>
                <guid isPermaLink="false">69e299d0fd22b8ad6276817b</guid>
                
                    <category>
                        <![CDATA[ context-hub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ search ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ agentic AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ agents ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nataraj Sundar ]]>
                </dc:creator>
                <pubDate>Fri, 17 Apr 2026 20:36:32 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/14f9768e-436d-4c7e-b86c-3d380e821354.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Large language models can write code quickly, but they still misremember APIs, miss version-specific details, and forget what they learned at the end of a session.</p>
<p>That is the problem Context Hub is trying to solve.</p>
<p>Context Hub (<code>chub</code>) gives coding agents curated, versioned documentation and skills that they can search and fetch through a CLI. It also gives them two learning loops: local annotations for agent memory and feedback for maintainers.</p>
<p>In this tutorial, you'll learn how the official <code>chub</code> workflow works, how Context Hub organizes docs and skills, how annotations and feedback create a memory loop, and how to build a <a href="https://github.com/natarajsundar/context-hub-relevance-engine/">companion relevance engine</a> that improves retrieval without breaking the upstream content model.</p>
<p>This tutorial uses two public repositories side by side:</p>
<ul>
<li><p>the official upstream project: <a href="https://github.com/andrewyng/context-hub">andrewyng/context-hub</a></p>
</li>
<li><p>the companion implementation for this article: <a href="https://github.com/natarajsundar/context-hub-relevance-engine/">natarajsundar/context-hub-relevance-engine</a></p>
</li>
</ul>
<p>I've also opened a corresponding upstream pull request from my fork to the main project. If you want to track that work from the article, use the upstream pull request list filtered by author: <a href="https://github.com/andrewyng/context-hub/pulls?q=is%3Apr+author%3Anatarajsundar">andrewyng/context-hub pull requests by <code>natarajsundar</code></a>.</p>
<h2 id="heading-what-well-build">What We'll Build</h2>
<p>By the end of this tutorial, you'll have:</p>
<ul>
<li><p>a clear mental model for how Context Hub works</p>
</li>
<li><p>a working local install of the official <code>chub</code> CLI</p>
</li>
<li><p>a repeatable workflow for search, fetch, annotations, and feedback</p>
</li>
<li><p>a companion repo that adds an additive reranking layer on top of a Context-Hub-style content tree</p>
</li>
<li><p>a small benchmark and local comparison UI you can run end to end</p>
</li>
<li><p>a clear bridge between the companion repo and the smaller upstream PR</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, make sure you have:</p>
<ul>
<li><p>Node.js 18 or newer</p>
</li>
<li><p>npm</p>
</li>
<li><p>comfort with the terminal</p>
</li>
<li><p>basic familiarity with Markdown</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-how-to-understand-context-hub">How to Understand Context Hub</a></p>
</li>
<li><p><a href="#heading-how-to-understand-the-official-repo-the-companion-repo-and-the-upstream-pr">How to Understand the Official Repo, the Companion Repo, and the Upstream PR</a></p>
</li>
<li><p><a href="#heading-how-to-install-and-use-the-official-cli">How to Install and Use the Official CLI</a></p>
</li>
<li><p><a href="#heading-how-to-understand-docs-skills-and-the-content-layout">How to Understand Docs, Skills, and the Content Layout</a></p>
</li>
<li><p><a href="#heading-how-to-use-incremental-fetch-and-layered-sources">How to Use Incremental Fetch and Layered Sources</a></p>
</li>
<li><p><a href="#heading-how-to-use-annotations-and-feedback-to-create-a-memory-loop">How to Use Annotations and Feedback to Create a Memory Loop</a></p>
</li>
<li><p><a href="#heading-how-to-see-where-relevance-still-misses">How to See Where Relevance Still Misses</a></p>
</li>
<li><p><a href="#heading-how-the-companion-relevance-engine-improves-retrieval">How the Companion Relevance Engine Improves Retrieval</a></p>
</li>
<li><p><a href="#heading-how-to-run-the-companion-repo-end-to-end">How to Run the Companion Repo End to End</a></p>
</li>
<li><p><a href="#heading-how-to-read-the-benchmark-honestly">How to Read the Benchmark Honestly</a></p>
</li>
<li><p><a href="#heading-how-to-connect-the-companion-repo-to-the-upstream-pr">How to Connect the Companion Repo to the Upstream PR</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-sources">Sources</a></p>
</li>
</ol>
<h2 id="heading-how-to-understand-context-hub">How to Understand Context Hub</h2>
<p>Context Hub is easiest to understand as a workflow for turning fast-moving documentation into a reliable input for coding agents.</p>
<p>Instead of asking an agent to rely on whatever it remembers from training data, you give it a predictable contract:</p>
<ol>
<li><p>search for the right entry</p>
</li>
<li><p>fetch the right doc or skill</p>
</li>
<li><p>write code against that curated content</p>
</li>
<li><p>save local lessons as annotations</p>
</li>
<li><p>send doc-quality feedback back to maintainers</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/09d75c85-fbb0-4c9a-86d5-8acdff4e1abf.png" alt="Diagram showing the Context Hub loop from developer prompt to agent search and fetch, then annotations and maintainer feedback." style="display:block;margin:0 auto" width="1654" height="307" loading="lazy">

<p>That system boundary matters.</p>
<p>It makes the agent easier to audit, easier to improve, and easier to extend. It also keeps the interface small enough that you can reason about where the failures happen. If the agent still misses the answer, you can ask whether the problem happened during search, fetch, context selection, or generation.</p>
<h2 id="heading-how-to-understand-the-official-repo-the-companion-repo-and-the-upstream-pr">How to Understand the Official Repo, the Companion repo, and the Upstream PR</h2>
<p>This tutorial is intentionally split across two codebases and one contribution path.</p>
<p>The official upstream project, <a href="https://github.com/andrewyng/context-hub">andrewyng/context-hub</a>, is the source of truth for the real CLI, the content model, and the documented workflows. That's the codebase you should use to learn how <code>chub</code> works today.</p>
<p>The companion repository, <a href="https://github.com/natarajsundar/context-hub-relevance-engine/">natarajsundar/context-hub-relevance-engine</a>, is where the relevant ideas in this article are made concrete. It's a companion implementation, not a replacement product. Its job is to make retrieval tradeoffs visible, measurable, and easy to run locally.</p>
<p>The upstream PR is the bridge between those two worlds. The companion repo is where you can iterate faster on benchmarks, reranking, and the comparison UI. The upstream PR is where the smallest reviewable slices can be proposed back to the main project. You can track that thread here: <a href="https://github.com/andrewyng/context-hub/pulls?q=is%3Apr+author%3Anatarajsundar">upstream PR search filtered by author</a>.</p>
<p>That three-part framing keeps the article honest:</p>
<ul>
<li><p><strong>use the upstream repo</strong> to understand the current system</p>
</li>
<li><p><strong>use the companion repo</strong> to explore relevant improvements end to end</p>
</li>
<li><p><strong>use the upstream PR</strong> to show how a larger idea can be broken into reviewable pieces</p>
</li>
</ul>
<h2 id="heading-how-to-install-and-use-the-official-cli">How to Install and Use the Official CLI</h2>
<p>The official quick start is intentionally small.</p>
<pre><code class="language-bash">npm install -g @aisuite/chub
</code></pre>
<p>Once the CLI is installed, you can search for what is available and fetch a specific entry:</p>
<pre><code class="language-bash">chub search openai
chub get openai/chat --lang py
</code></pre>
<p>That's the happy path, but it helps to think through the request flow.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/c5ff71d4-5e51-48b8-bbd3-fc2aafa93b9d.png" alt="Sequence diagram showing the developer asking the agent for current docs, the agent calling chub search and chub get, and the CLI fetching docs from the registry." style="display:block;margin:0 auto" width="1416" height="683" loading="lazy">

<p>In practice, the most useful detail is that the CLI is designed for the <strong>agent</strong> to use, not just for the human to use by hand.</p>
<p>That's why the upstream CLI also ships a <code>get-api-docs</code> skill. For example, if you use Claude Code, you can copy the skill into your local project like this:</p>
<pre><code class="language-bash">mkdir -p .claude/skills
cp $(npm root -g)/@aisuite/chub/skills/get-api-docs/SKILL.md \
  .claude/skills/get-api-docs.md
</code></pre>
<p>That step teaches the agent a retrieval habit:</p>
<blockquote>
<p>Before you write code against a third-party SDK or API, use <code>chub</code> instead of guessing.</p>
</blockquote>
<p>That behavioral rule is often as important as the docs themselves.</p>
<h2 id="heading-how-to-understand-docs-skills-and-the-content-layout">How to Understand Docs, Skills, and the Content Layout</h2>
<p>Context Hub separates content into two categories:</p>
<ul>
<li><p><strong>docs</strong>, which answer “what should the agent know?”</p>
</li>
<li><p><strong>skills</strong>, which answer “how should the agent behave?”</p>
</li>
</ul>
<p>That distinction makes the content model easier to scale. Docs can be versioned and language-specific. Skills can stay short and operational.</p>
<p>The directory structure is also predictable. The content guide organizes entries by author, then by <code>docs</code> or <code>skills</code>, then by entry name.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/3ac72bc2-c869-4e2e-9294-d63b35991135.png" alt="Diagram showing the content tree from author to docs and skills, with DOC.md and SKILL.md feeding a build step that emits registry and search artifacts." style="display:block;margin:0 auto" width="674" height="739" loading="lazy">

<p>A small example looks like this:</p>
<pre><code class="language-text">author/docs/payments/python/DOC.md
author/docs/payments/python/references/errors.md
author/skills/login-flows/SKILL.md
</code></pre>
<p>This is one of the reasons Context Hub is easy to work with.</p>
<p>The shape of the content is plain Markdown, the main entry file is predictable, and the build output is inspectable. You don't have to reverse engineer a hidden prompt layer to figure out what the agent is reading.</p>
<h2 id="heading-how-to-use-incremental-fetch-and-layered-sources">How to Use Incremental Fetch and Layered Sources</h2>
<p>One of the best design choices in Context Hub is that it doesn't force you to inject every file into the model on every request.</p>
<p>Instead, the entry file gives you the overview, and the reference files hold the deeper material.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/88d80a48-c991-495a-af25-14a0c0ac9868.png" alt="Diagram showing how chub get can fetch just the main entry file, a specific reference file, or the full entry directory." style="display:block;margin:0 auto" width="592" height="460" loading="lazy">

<p>That lets you fetch content in progressively larger slices.</p>
<pre><code class="language-bash">chub get stripe/webhooks --lang py
chub get stripe/webhooks --lang py --file references/raw-body.md
chub get stripe/webhooks --lang py --full
</code></pre>
<p>This is a token-budget feature as much as it is a documentation feature. A good agent should first load the overview, decide what part of the task matters, and only then fetch the specific supporting file.</p>
<p>Context Hub also supports layered sources. You can merge public content with your own local build output through <code>~/.chub/config.yaml</code>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/67465254-7a7c-4cfc-b9f0-9e94d8c3e2f3.png" alt="Diagram showing community, official, and local team sources merging into one search surface for chub search and chub get." style="display:block;margin:0 auto" width="774" height="460" loading="lazy">

<p>A minimal configuration looks like this:</p>
<pre><code class="language-yaml">sources:
  - name: community
    url: https://cdn.aichub.org/v1
  - name: my-team
    path: /opt/team-docs/dist
</code></pre>
<p>That means you can keep public docs in one lane and team-specific runbooks in another lane while still giving the agent one search surface.</p>
<h2 id="heading-how-to-use-annotations-and-feedback-to-create-a-memory-loop">How to Use Annotations and Feedback to Create a Memory Loop</h2>
<p>Context Hub has two different improvement channels.</p>
<p>Annotations are local. They help your agent remember what worked last time. Feedback is shared. It helps maintainers improve the docs for everyone.</p>
<p>That distinction matters because not every lesson belongs in the shared registry. Some lessons are environment-specific. Others point to content quality issues that should be fixed centrally.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/a8514430-08cb-4085-8047-64df25c603c7.png" alt="Diagram showing the agent fetch/write cycle, then branching to local annotations or maintainer feedback before the next task." style="display:block;margin:0 auto" width="808" height="798" loading="lazy">

<p>Here is what local memory looks like in practice:</p>
<pre><code class="language-bash">chub annotate stripe/webhooks \
  "Remember: Flask request.data must stay raw for Stripe signature verification."
</code></pre>
<p>And here's the feedback path:</p>
<pre><code class="language-bash">chub feedback stripe/webhooks up
</code></pre>
<p>That loop is simple, but it's one of the most important ideas in the project. It turns a one-off debugging lesson into either persistent local memory or a signal that the shared docs need to improve.</p>
<h2 id="heading-how-to-see-where-relevance-still-misses">How to See Where Relevance Still Misses</h2>
<p>The upstream project already has a real ranking story. It uses BM25 and lexical rescue so that package-like identifiers, exact tokens, and fuzzy matches still have a chance to surface.</p>
<p>That is a strong baseline.</p>
<p>But developer queries are often much messier than package names.</p>
<p>People search for:</p>
<ul>
<li><p><code>rrf</code></p>
</li>
<li><p><code>signin</code></p>
</li>
<li><p><code>pg vector</code></p>
</li>
<li><p><code>hnsw</code></p>
</li>
<li><p><code>raw body stripe</code></p>
</li>
</ul>
<p>Those aren't “bad” queries. They're realistic shorthand.</p>
<p>And they expose an opportunity in the content model itself: many of the exact answers live in reference files such as <code>references/rrf.md</code>, <code>references/raw-body.md</code>, and <code>references/hnsw.md</code>.</p>
<p>So the question is not whether the current search works at all. It clearly does. The better question is this:</p>
<blockquote>
<p>How can you improve retrieval without breaking the content contract that already makes Context Hub useful?</p>
</blockquote>
<p>The answer in the companion repo is to keep the current model and add a reranking layer on top of it.</p>
<h2 id="heading-how-the-companion-relevance-engine-improves-retrieval">How the Companion Relevance Engine Improves Retrieval</h2>
<p>The companion repository in this article is <a href="https://github.com/natarajsundar/context-hub-relevance-engine/"><code>context-hub-relevance-engine</code></a>.</p>
<p>It keeps the same broad ideas that make Context Hub attractive:</p>
<ul>
<li><p>plain Markdown content</p>
</li>
<li><p><code>DOC.md</code> and <code>SKILL.md</code> entry points</p>
</li>
<li><p>build artifacts you can inspect</p>
</li>
<li><p>local annotations and feedback</p>
</li>
<li><p>progressive fetch behavior</p>
</li>
</ul>
<p>Then it adds one new build artifact: <code>signals.json</code>.</p>
<p>At build time, the engine extracts extra signals such as:</p>
<ul>
<li><p>headings from the main file</p>
</li>
<li><p>titles and tokens from reference files</p>
</li>
<li><p>language and version metadata</p>
</li>
<li><p>source metadata and freshness</p>
</li>
<li><p>annotation overlap</p>
</li>
<li><p>feedback priors</p>
</li>
</ul>
<p>The first pass stays cheap and transparent. The reranker only runs after the baseline has done its work.</p>
<img src="https://cdn.hashnode.com/uploads/covers/694ca88d5ac09a5d68c63854/2ed2dadb-8fff-41ee-904b-0792cafcf744.png" alt="Diagram showing the relevance pipeline from query to BM25 and lexical rescue, then synonym expansion, candidate set building, reranking signals, and final results." style="display:block;margin:0 auto" width="1399" height="541" loading="lazy">

<p>That approach matters for two reasons.</p>
<p>First, it's additive. You don't have to redesign the content tree.</p>
<p>Second, it's measurable. You can define concrete failure modes, fix them one by one, and run the same benchmark every time you change the scorer.</p>
<h2 id="heading-how-to-run-the-companion-repo-end-to-end">How to Run the Companion Repo End to End</h2>
<p>Open the repository on <a href="https://github.com/natarajsundar/context-hub-relevance-engine/">GitHub</a>, clone it using GitHub’s normal clone flow, and then run the commands below from the project root.</p>
<pre><code class="language-bash">cd context-hub-relevance-engine
npm install
npm run build
npm test
</code></pre>
<p>The repository has no third-party runtime dependencies, so <code>npm install</code> is mostly there to keep the workflow familiar. The main commands are all plain Node scripts.</p>
<h3 id="heading-how-to-reproduce-a-baseline-miss">How to Reproduce a Baseline Miss</h3>
<p>Start with the query <code>rrf</code>.</p>
<pre><code class="language-bash">node bin/chub-lab.mjs search rrf --mode baseline --lang python
</code></pre>
<p>Expected output:</p>
<pre><code class="language-text">No results.
</code></pre>
<p>Now run the improved mode.</p>
<pre><code class="language-bash">node bin/chub-lab.mjs search rrf --mode improved --lang python
</code></pre>
<p>Expected top result:</p>
<pre><code class="language-text">langchain/retrievers [doc] score=320.24
  Composable retrieval patterns for hybrid search, parent documents, query expansion, and reranking.
</code></pre>
<p>That win happens because the improved mode looks beyond the top-level entry description. It also sees the reference file title <code>rrf</code>, the related terms from query expansion, and the broader token overlap in the extracted signals.</p>
<h3 id="heading-how-to-reproduce-a-workflow-intent-win">How to Reproduce a Workflow-intent Win</h3>
<p>Try a sign-in query.</p>
<pre><code class="language-bash">node bin/chub-lab.mjs search signin --mode baseline
node bin/chub-lab.mjs search signin --mode improved
</code></pre>
<p>The baseline misses. The improved mode returns <code>playwright-community/login-flows</code> because the reranker treats <code>signin</code>, <code>sign in</code>, <code>login</code>, and <code>authentication</code> as related intent.</p>
<h3 id="heading-how-to-test-the-memory-loop">How to Test the Memory Loop</h3>
<p>Write a local note:</p>
<pre><code class="language-bash">node bin/chub-lab.mjs annotate stripe/webhooks \
  "Remember: Flask request.data must stay raw for Stripe signature verification."
</code></pre>
<p>Then fetch the doc:</p>
<pre><code class="language-bash">node bin/chub-lab.mjs get stripe/webhooks --lang python
</code></pre>
<p>You will see the main doc content, the list of available reference files, and the appended annotation.</p>
<p>That's the behavior you want from an agent memory loop: learn once, reuse many times.</p>
<h3 id="heading-how-to-run-the-benchmark">How to Run the Benchmark</h3>
<p>Start from an empty store:</p>
<pre><code class="language-bash">npm run reset-store
node bin/chub-lab.mjs evaluate
</code></pre>
<p>The included synthetic stress set reports the following summary with an empty store:</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Top-1 Accuracy</th>
<th>MRR</th>
</tr>
</thead>
<tbody><tr>
<td>baseline</td>
<td>0.333</td>
<td>0.333</td>
</tr>
<tr>
<td>improved</td>
<td>1.000</td>
<td>1.000</td>
</tr>
</tbody></table>
<p>You can also seed the store and rerun the evaluation:</p>
<pre><code class="language-bash">npm run seed-demo
node bin/chub-lab.mjs evaluate
</code></pre>
<p>That demonstrates how annotations and feedback can push relevant entries even higher when the query overlaps with the agent’s own history.</p>
<h3 id="heading-how-to-launch-the-local-comparison-ui">How to Launch the Local Comparison UI</h3>
<pre><code class="language-bash">npm run serve
</code></pre>
<p>Then open <code>http://localhost:8787</code> in your browser.</p>
<p>The UI lets you compare baseline and improved retrieval, inspect stored annotations and feedback, rebuild the local artifacts, and rerun the benchmark from one place.</p>
<h2 id="heading-how-to-read-the-benchmark-honestly">How to Read the Benchmark Honestly</h2>
<p>The benchmark in this repo is intentionally small.</p>
<p>That is a feature, not a flaw.</p>
<p>The point is not to claim universal search quality. The point is to make a handful of realistic failure modes easy to reproduce:</p>
<ul>
<li><p>acronym queries</p>
</li>
<li><p>shorthand workflow queries</p>
</li>
<li><p>reference-file topic queries</p>
</li>
<li><p>memory-aware reranking</p>
</li>
</ul>
<p>That keeps the evaluation honest.</p>
<p>If a future scoring change breaks <code>rrf</code>, <code>signin</code>, or <code>raw body stripe</code>, you'll know immediately. And if you add a stronger dataset later, you can keep these tests as regression guards.</p>
<p>The benchmark files included in the repo are:</p>
<ul>
<li><p><code>demo/benchmark.json</code></p>
</li>
<li><p><code>docs/benchmark-empty-store.json</code></p>
</li>
<li><p><code>docs/benchmark-seeded-store.json</code></p>
</li>
<li><p><code>docs/relevance-improvement-plan.md</code></p>
</li>
</ul>
<h2 id="heading-how-to-connect-the-companion-repo-to-the-upstream-pr">How to Connect the Companion Repo to the Upstream PR</h2>
<p>A good companion repo is broad enough to explore ideas quickly. A good upstream PR is narrow enough to review.</p>
<p>That's why the two shouldn't be identical.</p>
<p>The companion repository is where you can keep the full relevance story together:</p>
<ul>
<li><p>the local comparison UI</p>
</li>
<li><p>the synthetic benchmark</p>
</li>
<li><p>the richer reranking signals</p>
</li>
<li><p>the debug and explain surfaces</p>
</li>
<li><p>the documentation that walks through tradeoffs end to end</p>
</li>
</ul>
<p>The upstream PR should be smaller and more surgical. In practice, that usually means proposing the most reviewable slices first, such as:</p>
<ol>
<li><p>reference-file signal extraction</p>
</li>
<li><p>explainable score output for debugging</p>
</li>
<li><p>a lightweight benchmark fixture format</p>
</li>
<li><p>one additive reranking hook behind a flag</p>
</li>
</ol>
<p>That keeps the main repository maintainable while still letting the article and companion repo tell the full engineering story. The upstream thread for this work lives here: <a href="https://github.com/andrewyng/context-hub/pulls?q=is%3Apr+author%3Anatarajsundar">andrewyng/context-hub pull requests by <code>natarajsundar</code></a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>What makes Context Hub interesting is not just that it stores documentation. It gives you a clear system boundary for improving coding agents.</p>
<p>You can inspect what the agent reads. You can decide when it should retrieve. You can layer public and private sources. You can persist local lessons. And you can improve ranking without tearing the whole model apart.</p>
<p>The companion relevance engine shows how to keep what already works, make one part of the system measurably better, and package the result in a way other developers can run, inspect, and extend. The upstream PR, in turn, shows how to turn a broad idea into smaller pieces that are realistic to review in the main project.</p>
<h2 id="heading-diagram-attribution">Diagram Attribution</h2>
<p>All diagrams used in this article were created by the author specifically for this tutorial and its companion repository.</p>
<h2 id="heading-sources">Sources</h2>
<ul>
<li><p><a href="https://github.com/andrewyng/context-hub">Context Hub repository</a></p>
</li>
<li><p><a href="https://github.com/andrewyng/context-hub/blob/main/README.md">Context Hub README</a></p>
</li>
<li><p><a href="https://github.com/andrewyng/context-hub/blob/main/cli/README.md">Context Hub CLI README</a></p>
</li>
<li><p><a href="https://github.com/andrewyng/context-hub/blob/main/docs/cli-reference.md">Context Hub CLI reference</a></p>
</li>
<li><p><a href="https://github.com/andrewyng/context-hub/blob/main/docs/content-guide.md">Context Hub content guide</a></p>
</li>
<li><p><a href="https://github.com/andrewyng/context-hub/blob/main/docs/byod-guide.md">Context Hub bring-your-own-docs guide</a></p>
</li>
<li><p><a href="https://github.com/andrewyng/context-hub/blob/main/docs/feedback-and-annotations.md">Context Hub feedback and annotations guide</a></p>
</li>
<li><p><a href="https://github.com/natarajsundar/context-hub-relevance-engine/">Companion repository: <code>context-hub-relevance-engine</code></a></p>
</li>
<li><p><a href="https://github.com/andrewyng/context-hub/pulls?q=is%3Apr+author%3Anatarajsundar">Upstream pull request search filtered by author</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Top ngrok Alternatives for 2026 – How to Choose the Best Tunneling Tool for Your Use Case ]]>
                </title>
                <description>
                    <![CDATA[ ngrok is a tunneling tool that lets developers expose a local server to the public internet through a secure URL. In practice, this means you can run a web app on your laptop and instantly make it acc ]]>
                </description>
                <link>https://www.freecodecamp.org/news/top-ngrok-alternatives-tunneling-tools/</link>
                <guid isPermaLink="false">69b997ffc22d3eeb8ae5d4a6</guid>
                
                    <category>
                        <![CDATA[ ngrok alternative ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tunneling ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ networking ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Tue, 17 Mar 2026 18:05:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/79c953af-f868-4cbf-8426-8634c1bfaa8d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a href="https://ngrok.com/">ngrok</a> is a tunneling tool that lets developers expose a local server to the public internet through a secure URL.</p>
<p>In practice, this means you can run a web app on your laptop and instantly make it accessible to external services, teammates, or clients without configuring routers, DNS, or firewalls.</p>
<p>It's widely used for webhook testing, API development, demos, and remote debugging.</p>
<p>The core idea behind ngrok is simple: it creates an outbound connection from your local machine to a cloud relay service. That relay provides a public endpoint and forwards traffic back to your local port.</p>
<p>This outbound-only design avoids many networking problems and works even behind NAT or strict corporate firewalls.</p>
<p>But as teams scale or requirements change, many developers start looking for alternatives. Some want more control, some want open source tooling, and others want tighter security models or lower cost.</p>
<p>In 2026, the ecosystem around tunneling and secure exposure has matured significantly, and several tools now compete directly with ngrok depending on your use case.</p>
<p>This article explores five strong ngrok alternatives that developers are actively using today. Each one approaches tunneling slightly differently, and understanding those differences is important before choosing a tool for production or development workflows.</p>
<h2 id="heading-localxpose"><strong>LocalXpose</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/e940439d-081f-49de-8e40-aa57758a106d.png" alt="LocalXpose" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><a href="https://localxpose.io/">LocalXpose</a> positions itself as a reverse proxy designed specifically for developers who want to expose localhost services quickly while keeping debugging visibility. The platform supports multiple tunnel types, including HTTP, TCP, TLS, UDP, and more, which makes it flexible beyond simple web apps.</p>
<p>One notable aspect of LocalXpose is its emphasis on traffic inspection. Developers can inspect requests and replay payloads, which is extremely useful when working with webhooks or third-party integrations. Instead of simply forwarding traffic, it becomes a debugging layer that helps you understand exactly what external services are sending into your application.</p>
<p>From a workflow perspective, LocalXpose feels closer to a developer productivity tool than just a networking utility. The CLI allows fast tunnel creation, while configuration files make it possible to start multiple tunnels simultaneously, which is helpful when testing microservices or event-driven architectures.</p>
<p>The tradeoff is that it still relies on an external relay infrastructure, so teams with strict compliance requirements may prefer <a href="https://www.ssdnodes.com/blog/what-is-self-hosting/">self-hosted</a> solutions. But for everyday development and demos, it offers a polished experience that many developers find comparable or even superior to ngrok.</p>
<p>LocalXpose works particularly well if you value debugging visibility and want a smoother developer experience without managing infrastructure.</p>
<h2 id="heading-localtunnel"><strong>LocalTunnel</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/adb5ec79-40af-4b33-9986-31634d3fdad4.png" alt="Local Tunnel" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><a href="https://github.com/localtunnel/localtunnel">LocalTunnel</a> is one of the oldest and simplest alternatives in the ecosystem.</p>
<p>Its philosophy is minimalism. You run a single command, and your local server becomes publicly available through a generated URL. There is no heavy setup, no DNS configuration, and almost no learning curve.</p>
<p>Because it's open source, LocalTunnel appeals strongly to developers who prefer transparent tooling. The server component can be self-hosted, which gives teams more control over reliability and privacy if they don't want to depend on public infrastructure.</p>
<p>The simplicity of LocalTunnel is both its strength and its limitation. It focuses primarily on HTTP and HTTPS use cases. Advanced enterprise features, detailed analytics, and complex access controls are not the main goal. Instead, it excels at quick sharing during development, hackathons, or rapid testing cycles.</p>
<p>One important consideration is reliability. Since many people use public LocalTunnel servers, availability can vary depending on community infrastructure. Developers often solve this by deploying their own server instance when stability becomes important.</p>
<p>In 2026, LocalTunnel remains relevant because of its low friction. If your goal is simply to share a local service quickly and you prefer open source tools, it remains a practical and lightweight choice.</p>
<h2 id="heading-cloudflare-tunnel"><strong>Cloudflare Tunnel</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/21340d7e-bcbd-43e4-86ad-919adbff6f03.png" alt="Cloudflare Tunnel" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><a href="https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/">Cloudflare Tunnel</a> takes a more infrastructure-oriented approach compared to developer-centric tunneling tools. Instead of just exposing localhost, it integrates directly with Cloudflare’s global network and security platform.</p>
<p>The tunnel is created through the cloudflared daemon, which establishes outbound connections to Cloudflare and routes traffic through their edge network.</p>
<p>This architecture changes how you think about tunnels. Rather than temporary developer links, Cloudflare Tunnel can be used as a production-grade access layer for private services.</p>
<p>You can publish internal applications without opening inbound ports, which significantly reduces the attack surface. The connection is outbound-only, meaning your origin server doesn't accept direct internet traffic.</p>
<p>Another major advantage is ecosystem integration. Since Cloudflare Tunnel sits inside the broader Cloudflare platform, you can combine it with access policies, DNS management, and performance features. This makes it attractive for teams already using Cloudflare for domains or security.</p>
<p>The tradeoff is complexity. Compared to LocalXpose or LocalTunnel, setup involves authentication, configuration, and a deeper understanding of networking concepts. But once configured, it scales well and fits long-term deployments rather than temporary development sessions.</p>
<p>Cloudflare Tunnel is ideal when your tunneling needs start blending into infrastructure and security strategy instead of just development convenience.</p>
<h2 id="heading-tailscale"><strong>Tailscale</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/47704d75-e4fe-43dc-9a6a-765bc8c25b4e.png" alt="Tailscale" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><a href="https://github.com/tailscale/tailscale">Tailscale</a> isn't a traditional tunnel in the same sense as ngrok. It's primarily a mesh VPN built on WireGuard principles, designed to securely connect devices into a private network called a tailnet.</p>
<p>But features like Tailscale Funnel allow services inside that private network to be exposed safely to the public internet, effectively making it a strong alternative for certain tunneling scenarios.</p>
<p>The key difference is security architecture. Instead of routing everything through a central relay by default, Tailscale builds encrypted peer-to-peer connections whenever possible. This means your devices become part of a secure overlay network, and exposure to the internet becomes a deliberate extension rather than the default behaviour.</p>
<p>Tailscale Funnel allows developers to expose local services externally while maintaining strong isolation from the rest of the network. Funnel ingress nodes are specifically designed so they don't gain packet-level access to your private tailnet, which is an important security design detail.</p>
<p>From a practical standpoint, Tailscale is excellent for teams that already need secure remote access. Instead of adding a separate tunneling tool, you extend an existing secure network to share services when necessary.</p>
<p>The downside is conceptual overhead. Developers expecting a simple “run one command and get a URL” experience may find the networking model more complex. But for engineering teams thinking about long-term secure connectivity, Tailscale offers a modern alternative that aligns well with zero-trust principles.</p>
<h2 id="heading-boring-proxy-open-source-self-hosted-option"><strong>Boring Proxy (Open Source Self-Hosted Option)</strong></h2>
<img src="https://cdn.hashnode.com/uploads/covers/66c6d8f04fa7fe6a6e337edd/3b37a059-3bfd-4b6e-a22c-d3de475c04ea.png" alt="Boring Proxy" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><a href="https://github.com/boringproxy/boringproxy">Boring Proxy</a> represents a different philosophy entirely. It's designed for self-hosters who want full control over their tunneling infrastructure. Instead of relying on a third-party cloud relay, you deploy your own server and manage tunnels through a lightweight web interface.</p>
<p>The project describes itself as a no-frills HTTPS and SSH tunneling solution focused on automation. Features like automatic HTTPS and a fast web UI make it approachable even for developers who don't want to manually manage certificates or reverse proxy configurations.</p>
<p>One of the biggest advantages is ownership. Because everything runs on your infrastructure, you control uptime, data flow, and security policies. This makes Boring Proxy especially attractive for developers running homelabs, internal tools, or privacy-focused projects.</p>
<p>Community discussions often compare it to a simplified mix of Caddy and ngrok, emphasising its usability for self-hosted environments.</p>
<p>The tradeoff is that you must manage a server. Unlike hosted solutions, you're responsible for maintenance, updates, and reliability. For some teams, this is a burden, but for others it's precisely the point.</p>
<p>In 2026, Boring Proxy stands out as one of the most practical open source options for developers who want ngrok-style convenience without vendor dependence.</p>
<h2 id="heading-choosing-the-right-alternative"><strong>Choosing the Right Alternative</strong></h2>
<p>Selecting an ngrok alternative is less about features and more about intent.</p>
<p>If your goal is rapid development sharing, LocalTunnel or LocalXpose provides minimal friction. If you are thinking about secure production exposure, Cloudflare Tunnel is a strong infrastructure-level choice.</p>
<p>If you want network-centric security and remote access, Tailscale changes the model entirely. And if control and ownership matter most, Boring Proxy gives you a self-hosted path.</p>
<p>The tunneling ecosystem has matured significantly over recent years. Instead of a single dominant tool, developers now choose based on workflow philosophy. Some prioritise speed, some prioritise security, and others prioritise ownership.</p>
<p>The best approach is to treat tunneling as part of your architecture rather than a temporary utility. Once you do that, the right alternative becomes obvious based on how your team builds, deploys, and collaborates.</p>
<h3 id="heading-final-thoughts"><strong>Final Thoughts</strong></h3>
<p>ngrok remains influential, but it's no longer the only default choice. The tools covered here show how tunneling has evolved from simple developer shortcuts into a broader category that overlaps with networking, security, and infrastructure management.</p>
<p>LocalXpose and LocalTunnel keep things lightweight and developer-friendly. Cloudflare Tunnel introduces enterprise-grade edge networking. Tailscale blends secure mesh networking with public exposure when needed. Boring Proxy empowers developers who want to own the entire stack.</p>
<p>The right decision depends on where you sit on the spectrum between convenience and control. In 2026, you no longer need to compromise. There is an option tailored to almost every development workflow.</p>
<p><em>Hope you enjoyed this article. Learn more about me by visiting</em> <a href="https://manishmshiva.me/"><em>my website</em></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build AI Agents That Remember User Preferences (Without Breaking Context) ]]>
                </title>
                <description>
                    <![CDATA[ Why Personalization Breaks Most AI Agents Personalization is one of the most requested features in AI-powered applications. Users expect an agent to remember their preferences, adapt to their style, and improve over time. In practice, personalization... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-ai-agents-that-remember-user-preferences-without-breaking-context/</link>
                <guid isPermaLink="false">698cc32db8fec0245bd9996d</guid>
                
                    <category>
                        <![CDATA[ ai agents ]]>
                    </category>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ observability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nataraj Sundar ]]>
                </dc:creator>
                <pubDate>Wed, 11 Feb 2026 17:58:05 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770832641633/da49bdca-617e-4272-b5b7-012f3c6c1d61.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-why-personalization-breaks-most-ai-agents"><strong>Why Personalization Breaks Most AI Agents</strong></h2>
<p>Personalization is one of the most requested features in AI-powered applications. Users expect an agent to remember their preferences, adapt to their style, and improve over time.</p>
<p>In practice, personalization is unfortunately also one of the fastest ways to break an otherwise working AI agent.</p>
<p>Many agents start with a simple idea: keep adding more conversation history to the prompt. This approach works for demos, but it quickly fails in real applications. Context windows grow too large. Irrelevant information leaks into decisions. Costs increase. Debugging becomes nearly impossible.</p>
<p>If you want a personalized agent that survives production, you need more than a large language model. You need a way to connect the agent to tools, manage multi-step workflows, and store user preferences safely over time – without turning your system into a tangled mess of prompts and callbacks.</p>
<p>In this tutorial, you’ll learn how to design a personalized AI agent using three core building blocks:</p>
<ul>
<li><p><strong>Agent Development Kit (ADK)</strong> to orchestrate agent reasoning and execution</p>
</li>
<li><p><strong>Model Context Protocol (MCP)</strong> to connect tools with clear boundaries</p>
</li>
<li><p><strong>Long-term memory</strong> to store preferences without polluting context</p>
</li>
</ul>
<p>Rather than focusing on setup commands or vendor-specific walkthroughs, we'll focus on the architectural patterns that make personalized agents reliable, debuggable, and maintainable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578645884/2fd77443-31d5-4db3-98f0-bba685122a6f.png" alt="User preferences influence an AI agent’s personalized response" class="image--center mx-auto" width="1452" height="578" loading="lazy"></p>
<p><em>Figure 1 — Personalization influences agent responses</em></p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#what-personalized-means-in-a-real-ai-agent">What “Personalized” Means in a Real AI Agent</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-the-agent-architecture-fits-together">How the Agent Architecture Fits Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-design-the-agent-core-with-adk">How to Design the Agent Core with ADK</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-connect-tools-safely-with-mcp">How to Connect Tools Safely with MCP</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-add-long-term-memory-without-polluting-context">How to Add Long-Term Memory Without Polluting Context</a></p>
<ul>
<li><a class="post-section-overview" href="#privacy-consent-and-lifecycle-controls-production-checklist">Privacy, Consent, and Lifecycle Controls (Production Checklist)</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#how-the-end-to-end-agent-flow-works">How the End-to-End Agent Flow Works</a></p>
</li>
<li><p><a class="post-section-overview" href="#common-pitfalls-youll-hit-and-how-to-avoid-them">Common Pitfalls You’ll Hit (and How to Avoid Them)</a></p>
</li>
<li><p><a class="post-section-overview" href="#what-you-learned-and-where-to-go-next">What You Learned and Where to Go Next</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To follow along with this tutorial, you should have:</p>
<ul>
<li><p>Basic familiarity with Python</p>
</li>
<li><p>A general understanding of how large language models work</p>
</li>
<li><p>Optional: a Google Cloud account if you want to run an end-to-end demo. Otherwise, you can follow the architecture and code patterns locally with stubs. We’ll avoid deep infrastructure setup and focus on design patterns rather than deployment mechanics.</p>
</li>
</ul>
<p>You don’t need prior experience with ADK or MCP. I’ll introduce each concept as it appears.</p>
<h2 id="heading-what-personalized-means-in-a-real-ai-agent"><strong>What “Personalized” Means in a Real AI Agent</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578714303/4d25a7e4-fcdd-4a1a-a12c-411e41f2021f.png" alt="An AI agent accesses external tools through a protocol boundary/control layer" class="image--center mx-auto" width="1382" height="670" loading="lazy"></p>
<p><em>Figure 2 — Keep preferences out of the prompt: agent ↔ tools across a protocol boundary</em></p>
<p>Before writing any code, it’s important to define what personalization means in an AI agent.</p>
<p>Personalization is not the same as “remembering everything.” In practice, agent state usually falls into three categories:</p>
<ol>
<li><p><strong>Short-term context:</strong> Information needed to complete the current task. This belongs in the prompt.</p>
</li>
<li><p><strong>Session state:</strong> Temporary decisions or selections made during a workflow. This should be structured and scoped to a session.</p>
</li>
<li><p><strong>Long-term memory:</strong> Durable user preferences or facts that should persist across sessions.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577191953/3df5aa02-2eb9-4214-bbef-52f18ddb353a.png" alt="Three panels comparing short-term context, session state, and long-term memory" class="image--center mx-auto" width="946" height="510" loading="lazy"></p>
<p><em>Figure 3 — Three kinds of agent state: context (now), session (today), memory (always)</em></p>
<p>Most problems happen when these categories are mixed together.</p>
<p>If you store long-term preferences directly in the prompt, the agent’s behavior becomes unpredictable. If you store everything permanently, memory grows without bounds. If you don’t scope memory at all, unrelated sessions start influencing each other.</p>
<p>A well-designed, personalized agent treats memory as a first-class system component, not as extra text added to a prompt.</p>
<p>In the next section, we'll look at how to structure the agent so these concerns stay separated. </p>
<p>By the end of this tutorial, you’ll understand how to design a personalized AI agent that uses long-term memory safely, connects to tools through clear boundaries, and remains debuggable as it grows.</p>
<h2 id="heading-how-the-agent-architecture-fits-together"><strong>How the Agent Architecture Fits Together</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577351960/9b14cadf-d650-4098-8ce1-9fd706537bb9.png" alt="Reference architecture showing a user, an AI agent core, tools, a memory service, and an orchestration runtime" class="image--center mx-auto" width="1100" height="554" loading="lazy"></p>
<p><em>Figure 4 — Reference architecture: agent core + tools + memory service + orchestration runtime</em></p>
<p>The above diagram shows a high-level, personalized AI agent architecture. In it, an agent core handles reasoning and planning while interacting with a tool interface layer, a long-term memory service, and an orchestration runtime.</p>
<p>Let’s now understand the moving parts of a personalized agent and how they interact.</p>
<p>At a high level, the system has four responsibilities:</p>
<ol>
<li><p><strong>Reasoning</strong> – deciding what to do next</p>
</li>
<li><p><strong>Execution</strong> – calling tools and services</p>
</li>
<li><p><strong>Memory</strong> – storing and retrieving long-term preferences</p>
</li>
<li><p><strong>Boundaries</strong> – controlling what the agent is allowed to do</p>
</li>
</ol>
<p>A common mistake you’ll see is to blur these responsibilities together. For example, letting the model decide when to write memory, or allowing tools to execute actions without clear constraints.</p>
<p>Instead, you'll design the system so each responsibility has a clear owner. The core components look like this:</p>
<ul>
<li><p><strong>Agent core</strong>: Handles reasoning and planning</p>
</li>
<li><p><strong>Tools</strong>: Perform external actions (read or write)</p>
</li>
<li><p><strong>MCP layer</strong>: Defines how tools are exposed and invoked</p>
</li>
<li><p><strong>Memory services</strong>: Store long-term user data safely</p>
</li>
</ul>
<p>ADK sits at the center, orchestrating how requests flow between these components. The model never directly talks to databases or services. It reasons about actions, and ADK coordinates execution.</p>
<p>This separation makes the system easier to reason about, debug, and extend.</p>
<h2 id="heading-how-to-design-the-agent-core-with-adk"><strong>How to Design the Agent Core with ADK</strong></h2>
<p>Before we dive in, a quick note on what ADK is<strong>.</strong>  </p>
<p><strong>Agent Development Kit (ADK)</strong> is an agent orchestration framework – the glue code between a large language model and your application. Instead of treating the model as a black box that directly “does things”, ADK helps you structure the agent as a system:</p>
<ul>
<li><p>The model focuses on <strong>reasoning</strong> (turning user intent, context, and memory into a structured plan)</p>
</li>
<li><p>Your runtime stays in control of <strong>execution</strong> (deciding which tools can run, how they run, and what gets logged or persisted)</p>
</li>
</ul>
<p>In other words, ADK is what lets you take tool calling and multi-step workflows out of a giant prompt and turn them into a maintainable and testable architecture. In this tutorial, we’ll use ADK to refer to that orchestration layer. The same patterns apply if you use a different agent framework.</p>
<p><strong>Note:</strong> The following code snippets are simplified reference examples intended to illustrate architectural patterns. They’re not production-ready drop-ins.</p>
<p>Once you understand the architecture, you can start designing the agent core. The agent core is responsible for reasoning, not execution.</p>
<p>A helpful mental model is to think of the agent as a planner, not a doer. Its role is to interpret the user’s goal, consider available context and memory, and produce a structured plan that can later be executed in a controlled way.</p>
<p>To make this concrete, the following example shows how an agent can translate user input and memory into an explicit plan. In practice, ADK orchestrates this using a large language model, but the important idea is that the output is structured intent, not side effects.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example for illustration.</span>

<span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List, Dict, Any

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Step</span>:</span>
    tool: str
    args: Dict[str, Any]

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Plan</span>:</span>
    goal: str
    steps: List[Step]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">build_plan</span>(<span class="hljs-params">user_text: str, memory: Dict[str, Any]</span>) -&gt; Plan:</span>
    <span class="hljs-comment"># In practice, the LLM produces this structure via ADK orchestration.</span>
    goal = <span class="hljs-string">f"Help user: <span class="hljs-subst">{user_text}</span>"</span>
    steps = []
    <span class="hljs-keyword">if</span> memory.get(<span class="hljs-string">"prefers_short_answers"</span>):
        steps.append(Step(tool=<span class="hljs-string">"set_style"</span>, args={<span class="hljs-string">"verbosity"</span>: <span class="hljs-string">"low"</span>}))
    steps.append(Step(tool=<span class="hljs-string">"search_docs"</span>, args={<span class="hljs-string">"query"</span>: user_text}))
    steps.append(Step(tool=<span class="hljs-string">"summarize"</span>, args={<span class="hljs-string">"max_bullets"</span>: <span class="hljs-number">5</span>}))
    <span class="hljs-keyword">return</span> Plan(goal=goal, steps=steps)
</code></pre>
<p>This example illustrates an important constraint: the agent produces a plan, but it doesn’t execute anything directly.</p>
<p>The agent decides <em>what</em> should happen and <em>in what order</em>, while ADK controls <em>when</em> and <em>how</em> each step runs. This separation lets you inspect, test, and reason about decisions before they result in real-world actions.</p>
<p>When personalization is involved, this distinction becomes critical. Preferences may influence planning, but execution should remain tightly controlled by the runtime.</p>
<p>Again, we can consider the agent to be a planner, not a doer.</p>
<p>It should not:</p>
<ul>
<li><p>Perform side effects directly</p>
</li>
<li><p>Write to databases</p>
</li>
<li><p>Call external APIs without supervision</p>
</li>
</ul>
<p>In ADK, this separation is natural. The agent produces intents and tool calls, while the runtime controls how and when those calls are executed.</p>
<p>This design has two major benefits:</p>
<ol>
<li><p><strong>Safety</strong> – you can restrict which tools the agent can access</p>
</li>
<li><p><strong>Debuggability</strong> – you can inspect decisions before execution</p>
</li>
</ol>
<p>When personalization is involved, this becomes even more important. Preferences influence reasoning, but execution should remain tightly controlled.</p>
<h2 id="heading-how-to-connect-tools-safely-with-mcp"><strong>How to Connect Tools Safely with MCP</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578793149/2e3f8282-341a-4f03-9313-df3f8c9c5174.png" alt="Tool call routed through a control layer with request, validation, execution, and response steps." class="image--center mx-auto" width="1362" height="870" loading="lazy"></p>
<p><em>Figure 5 — Tool calls with guardrails: request → validate → execute → respond</em></p>
<p>Tools are how agents interact with the real world. They fetch data, generate artifacts, and sometimes perform actions with side effects.</p>
<p>Without clear boundaries, tool usage quickly becomes a source of fragility. Hardcoded API calls leak into prompts, tools evolve independently, and agents gain more authority than intended.</p>
<p>To avoid these problems, tools should be explicitly registered and invoked through a narrow interface. The following example shows a simple tool registry pattern that mirrors how MCP exposes tools to an agent without tightly coupling it to implementations.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Callable, Dict, Any

ToolFn = Callable[[Dict[str, Any]], Dict[str, Any]]

TOOLS: Dict[str, ToolFn] = {}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">register_tool</span>(<span class="hljs-params">name: str</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decorator</span>(<span class="hljs-params">fn: ToolFn</span>):</span>
        TOOLS[name] = fn
        <span class="hljs-keyword">return</span> fn
    <span class="hljs-keyword">return</span> decorator

<span class="hljs-meta">@register_tool("search_docs")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search_docs</span>(<span class="hljs-params">args: Dict[str, Any]</span>) -&gt; Dict[str, Any]:</span>
    query = args[<span class="hljs-string">"query"</span>]
    <span class="hljs-comment"># Replace with your MCP client call (or local tool implementation).</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"results"</span>: [<span class="hljs-string">f"doc://example?q=<span class="hljs-subst">{query}</span>"</span>]}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_tool</span>(<span class="hljs-params">name: str, args: Dict[str, Any]</span>) -&gt; Dict[str, Any]:</span>
    <span class="hljs-keyword">if</span> name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> TOOLS:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Tool not allowed: <span class="hljs-subst">{name}</span>"</span>)
    <span class="hljs-keyword">return</span> TOOLS[name](args)
</code></pre>
<p>The Model Context Protocol (MCP) provides a clean way to formalize this pattern. You can think of MCP the same way operating systems treat system calls.</p>
<p>An application does not directly manipulate hardware. Instead, it requests operations through well-defined system calls. The kernel decides whether the operation is allowed and how it executes.</p>
<p>In the same way, the agent knows <em>what</em> capabilities exist, MCP defines <em>how</em> those capabilities are invoked, and the runtime controls <em>when</em> and <em>whether</em> they execute.</p>
<p>This separation prevents several common problems, including hardcoded API details in prompts, unexpected breakage when tools change, and agents performing unrestricted side effects.</p>
<p>When designing tools, it helps to classify them by risk: read tools for safe queries, generate tools for planning or synthesis, and commit tools for irreversible actions. In a personalized agent, commit tools should be rare and tightly guarded.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770580271505/d5d34514-3b98-4997-85ed-dee55e65d711.png" alt="Observability around tool calls using logs, traces, and timing across decision points" class="image--center mx-auto" width="996" height="606" loading="lazy"></p>
<p><em>Figure 6 — Observability around tool calls: logs, traces, timing, decision points</em></p>
<h2 id="heading-how-to-add-long-term-memory-without-polluting-context"><strong>How to Add Long-Term Memory Without Polluting Context</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577944241/b2a3de65-c5e2-456e-8a33-e9fd4d2695f0.png" alt="Memory candidates extracted from user input, filtered and validated, then stored asynchronously" class="image--center mx-auto" width="1118" height="478" loading="lazy"></p>
<p><em>Figure 7 — Memory admission pipeline: extract → filter/validate → persist asynchronously</em></p>
<p>Memory is where personalization either succeeds or fails.</p>
<p>You can start by storing everything the user says and feed it back into the prompt. This works briefly, then collapses under its own weight as context grows, costs rise, and behavior becomes unpredictable.</p>
<p>A better approach is to treat memory as structured, curated data so you can control what the agent remembers and why with clear admission rules. Before persisting anything, the system should explicitly decide whether the information is worth remembering. The following function demonstrates a simple memory admission policy.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Simplified Reference Only</span>
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Optional, Dict, Any

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">memory_candidate</span>(<span class="hljs-params">user_text: str</span>) -&gt; Optional[Dict[str, Any]]:</span>
    text = user_text.lower()

    <span class="hljs-comment"># Durable</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"for this session"</span> <span class="hljs-keyword">in</span> text <span class="hljs-keyword">or</span> <span class="hljs-string">"ignore after"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

    <span class="hljs-comment"># Reusable</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"my preferred language is"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> {<span class="hljs-string">"type"</span>: <span class="hljs-string">"preference"</span>, <span class="hljs-string">"key"</span>: <span class="hljs-string">"language"</span>, <span class="hljs-string">"value"</span>: user_text.split()[<span class="hljs-number">-1</span>]}

    <span class="hljs-comment"># Safe (basic example; add PII checks for your use case)</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"password"</span> <span class="hljs-keyword">in</span> text <span class="hljs-keyword">or</span> <span class="hljs-string">"ssn"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>  <span class="hljs-comment"># default: don’t store</span>
</code></pre>
<p>This policy encodes three questions every memory candidate must answer:</p>
<ul>
<li><p>Is it durable? Will it still matter in the future?</p>
</li>
<li><p>Is it reusable? Will it influence future decisions meaningfully?</p>
</li>
<li><p>Is it safe to persist? Does it avoid sensitive or session-specific data?</p>
</li>
</ul>
<p>Only information that passes all three checks should become long-term memory. In practice, this usually includes stable preferences and long-lived constraints, not temporary instructions or intermediate reasoning.</p>
<h3 id="heading-privacy-consent-and-lifecycle-controls-production-checklist"><strong>Privacy, Consent, and Lifecycle Controls (Production Checklist)</strong></h3>
<p>Even if your admission rules are solid, long-term memory introduces governance requirements:</p>
<ul>
<li><p><strong>User control:</strong> allow users to view, export, and delete stored preferences at any time.</p>
</li>
<li><p><strong>Sensitive data handling:</strong> never store secrets/PII. Run PII detection on every memory candidate (and consider redaction).</p>
</li>
<li><p><strong>Retention + consent:</strong> use explicit consent for persistent memory and apply retention windows (TTL) so memory expires unless it’s still useful.</p>
</li>
<li><p><strong>Security + auditability:</strong> encrypt at rest, restrict access by service identity, and keep an audit log of memory writes/updates.</p>
</li>
</ul>
<p>Memory writes should also be asynchronous. The agent should never block while persisting memory, which keeps interactions responsive and avoids coupling reasoning to storage latency.</p>
<h2 id="heading-how-the-end-to-end-agent-flow-works"><strong>How the End-to-End Agent Flow Works</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578847727/f3cbc4b9-5bc9-4026-ae69-6fd7bc1625fc.png" alt="End-to-end flow showing user input, agent reasoning, tool invocation, and memory updates with feedback loops" class="image--center mx-auto" width="1134" height="308" loading="lazy"></p>
<p><em>Figure 8 — End-to-end request lifecycle: user input → plan → tools → memory updates</em></p>
<p>At this point, you can trace exactly how memory and tools interact during a single request. With the individual components in place, it’s helpful to see how they work together during a single request. The following example walks through the full lifecycle of a personalized interaction, from user input to response.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_request</span>(<span class="hljs-params">user_id: str, user_text: str</span>) -&gt; str:</span>
    memory = memory_store.get(user_id)  <span class="hljs-comment"># e.g., {"prefers_short_answers": True}</span>
    plan = build_plan(user_text, memory)

    tool_outputs = []
    <span class="hljs-keyword">for</span> step <span class="hljs-keyword">in</span> plan.steps:
        out = invoke_tool(step.tool, step.args)
        tool_outputs.append({step.tool: out})

    response = render_response(goal=plan.goal, tool_outputs=tool_outputs, memory=memory)

    cand = memory_candidate(user_text)
    <span class="hljs-keyword">if</span> cand:
        <span class="hljs-comment"># Never block the user on storage.</span>
        memory_store.write_async(user_id, cand)
    <span class="hljs-keyword">return</span> response
</code></pre>
<p>At a high level, the flow looks like this:</p>
<ol>
<li><p>The user sends a message.</p>
</li>
<li><p>Relevant long-term memory is retrieved.</p>
</li>
<li><p>The agent reasons about the request and produces a plan.</p>
</li>
<li><p>ADK invokes tools through MCP as needed.</p>
</li>
<li><p>Results flow back to the agent.</p>
</li>
<li><p>The agent decides whether new information should be persisted.</p>
</li>
<li><p>Memory is written asynchronously.</p>
</li>
<li><p>The final response is returned to the user.</p>
</li>
</ol>
<p>Notice what does <strong>not</strong> happen: the model does not directly write memory, tools do not execute without coordination, and context does not grow without bounds. This structure keeps personalization controlled and predictable.</p>
<h2 id="heading-common-pitfalls-youll-hit-and-how-to-avoid-them"><strong>Common Pitfalls You’ll Hit (and How to Avoid Them)</strong></h2>
<p>Even with a solid architecture, there are a few failure modes that show up repeatedly in real systems. Many of them stem from allowing agents to perform irreversible actions without explicit checks.</p>
<p>The following example shows a simple guardrail for commit-style tools that require approval before execution.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_commit_tool</span>(<span class="hljs-params">name: str, args: Dict[str, Any], approved: bool</span>) -&gt; Dict[str, Any]:</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> approved:
        <span class="hljs-comment"># Require explicit confirmation or policy approval before side effects.</span>
        <span class="hljs-keyword">return</span> {<span class="hljs-string">"status"</span>: <span class="hljs-string">"blocked"</span>, <span class="hljs-string">"reason"</span>: <span class="hljs-string">"commit tools require approval"</span>}

    <span class="hljs-comment"># For example: create_ticket, send_email, submit_order, update_record</span>
    <span class="hljs-keyword">return</span> invoke_tool(name, args)
</code></pre>
<p>This pattern forces a clear decision point before side effects occur. It also creates an audit trail that explains <em>why</em> an action was allowed or blocked.</p>
<p>Other common pitfalls include over-personalization, leaky memory that persists session-specific data, uncontrolled tool growth, and debugging blind spots caused by unclear boundaries. If you see these symptoms, it usually means responsibilities are not clearly separated.</p>
<h2 id="heading-what-you-learned-and-where-to-go-next"><strong>What You Learned and Where to Go Next</strong></h2>
<p>Personalized AI agents are powerful, but they require discipline. The key insight is that personalization is a <strong>systems problem</strong>, not a prompt problem.</p>
<p>By separating reasoning from execution, structuring memory carefully, and using protocols like MCP to enforce boundaries, you can build agents that scale beyond demos and remain maintainable in production.</p>
<p>As you extend this system, resist the urge to add “just one more prompt tweak.” Instead, ask whether the change belongs in memory, tools, or orchestration.  </p>
<p>That mindset will save you time as your agent grows in complexity.  </p>
<p>If you’d like to continue the conversation, you can find me on <a target="_blank" href="https://www.linkedin.com/in/natarajsundar/">LinkedIn</a>.</p>
<p>*All diagrams in this article were created by the author for educational purposes.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use WebCurate to Discover Helpful Developer Tools ]]>
                </title>
                <description>
                    <![CDATA[ Building Kreed, my collaborative project management tool, was no easy feat. But it did offer one perk: an endless stream of stress-induced caffeine highs. I don’t have a dedicated team, so I was juggling everything from design and deployment to marke... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-webcurate-to-discover-helpful-developer-tools/</link>
                <guid isPermaLink="false">672e31c2d0e4fe6467edeb83</guid>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Jaja ]]>
                </dc:creator>
                <pubDate>Fri, 08 Nov 2024 15:44:02 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1730908679140/f7c2ff53-9d06-4b2f-ae08-baaf219cba1f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building Kreed, my collaborative project management tool, was no easy feat. But it did offer one perk: an endless stream of stress-induced caffeine highs. I don’t have a dedicated team, so I was juggling everything from design and deployment to marketing and even user testing (because it’s not okay to test on production users—bummer 😂). </p>
<p>After quite a journey of trial and error, I found WebCurate, a platform that felt like the instruction manual I’d been missing. </p>
<p>In this article, I’ll take you through how WebCurate helped me simplify my journey with Kreed and share tips on discovering the best tools for your projects.</p>
<h2 id="heading-what-well-cover">What We’ll Cover</h2>
<ul>
<li><p><strong>What is WebCurate?:</strong> An overview of WebCurate’s platform </p>
</li>
<li><p><strong>How WebCurate Works:</strong> A look at WebCurate's curated categories to streamline your workflow.</p>
</li>
<li><p><strong>Finding the Right Tools:</strong> Tips for searching, filtering, and using WebCurate’s curated recommendations.</p>
</li>
<li><p><strong>Making the Most of WebCurate:</strong> Advice on leveraging the platform for your specific needs, with an example of finding a relevant tool.</p>
</li>
<li><p><strong>Essential Tools Discovered on WebCurate:</strong> A look at the essential tools WebCurate helped me discover for Kreed’s development and how they might help you.</p>
</li>
</ul>
<h2 id="heading-what-is-webcurate">What is WebCurate?</h2>
<p><a target="_blank" href="https://webcurate.co/">WebCurate</a> is a free platform designed to help developers, startups, and tech enthusiasts find high-value tools without the overwhelm.</p>
<p>Unlike other resources that flood you with endless options, WebCurate handpicks practical, relevant tools and resources tailored for productivity and development.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfUJtsMJ05qDT9ZcF9OG6xEDHFNFa2RBBG2fHmmxoftZStQuEwGOoZFkUVDdIBZUeXCLAqLCgkJ8oz2EQavBnSS8XLiUXPauzaYguT9HNJnpzvi-G6cFZhvormbptwXOohoRkOZ?key=PqN_g0HdcyYepwEb5e7OGQCR" alt="WebCurate's interface" width="1339" height="754" loading="lazy"></p>
<p>What makes WebCurate unique is its comprehensive scope. It offers a curated range of design tools and inspiration, developer tools, API integrations, debugging utilities, productivity aids, and even insights into industry trends.</p>
<p>Each resource on WebCurate is chosen for its ability to genuinely enhance workflow and project outcomes, so you spend less time searching and more time building.</p>
<h2 id="heading-how-webcurate-works">How WebCurate Works</h2>
<p>WebCurate provides resources across essential categories, like design assets, developer tools, and productivity guides. Think of it like a “greatest hits” album but for tools.</p>
<p>The platform features resources that span several essential areas:</p>
<ul>
<li><strong>Design Assets</strong>: From UI kits to icons and templates, WebCurate provides carefully selected design resources that can help developers and designers keep their projects on track without feeling lost in a sea of options.</li>
</ul>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeudKwe93_l9Syfw2oHmR9bzBC1H4ThH0WhH3Jo74UY4I_rDqA1venrKd2ZRNgz3d9IHUrHSrXwGmj0kNdlBHv291WH0Dfk_sbyylj9NE3VrWUeHujzuldUUvJd1hZ5aQtbAH-KNQ?key=PqN_g0HdcyYepwEb5e7OGQCR" alt="Screenshot showing the Best Design Tools section" width="1600" height="760" loading="lazy"></p>
<ul>
<li><strong>Developer Tools</strong>: This includes everything from code libraries to APIs and frameworks that can speed up development workflows. Tools like these are sorted into categories to ensure you find the best fit for your tech stack.</li>
</ul>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdCZ4HK8KR31C2-1hOAb0nj1Wu3FrSCN5QiH9A_kNDagmowpNHWdTxAdBHXNKb-E-v8T_4B8aCCPXTchJenfOab6-SajbfO1Il_DcFeetBmS0nWxLWbSTz_gM-W_M3CIJy1Mqbmtw?key=PqN_g0HdcyYepwEb5e7OGQCR" alt="Screenshot showing the Best Developer Tools section" width="1600" height="760" loading="lazy"></p>
<ul>
<li><strong>Industry Guides</strong>: WebCurate also offers practical guides tools, insights, and case studies to help developers stay ahead of industry trends and optimize their strategies.</li>
</ul>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeeQ5sWhrBGm3mkNLECtks9cHVG0nbKBLTnJ3utL8sNpJGqRpkQf0zyTgp1yXIRszucCzQAowVxmMzY3ZaA5y8Y-aeV2Bu7BHLpVI6CMKl7cuVOJ5QHLv7EN8gU8ts-4J3t8TJCXA?key=PqN_g0HdcyYepwEb5e7OGQCR" alt="Screenshot showing the Guide Tools section" width="1600" height="760" loading="lazy"></p>
<h2 id="heading-finding-the-right-tools-tips-for-navigation">Finding the Right Tools: Tips for Navigation</h2>
<h3 id="heading-efficient-search-amp-filtering"><strong>Efficient Search &amp; Filtering</strong></h3>
<p>WebCurate’s search and filtering functions make it simple to find exactly what you need, fast. Whether you’re after an API for a new feature or a project management tool, these features help narrow your options to the most relevant tools quickly and effectively.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXd7XSYwisbDhNsOim_MAtxnvsQH36z9ZVUkIr8H_84RIv_bDM7-4a6l-BRyV2vfGPsWjKpYnPoJ0lMIlgo5jG_95hTlyd1wNQAv2NCrQUUtNtkaOvzPCDFNiX5kqyxQxpSuCO3f5w?key=PqN_g0HdcyYepwEb5e7OGQCR" alt="Screenshot showing search" width="1600" height="760" loading="lazy"></p>
<h3 id="heading-curated-recommendations"><strong>Curated Recommendations</strong></h3>
<p>What sets WebCurate apart is its thoughtful curation. Instead of suggesting random, popular tools, it presents resources that address real developer needs, based on industry trends and emerging technologies. This means you’re seeing tools that make an impact—tailored to current, practical needs.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXf1RU5wgtnezPi-om7y63Boeyq9l_-5cCQ6CP6ELxa6cjwjIBDFJ3GEOffl8E7NWe-rX1UMliN67L0BxMJ_H-3w2b4R2yw6eZjpTAKAj0ZAYjHlcuWjimuPNyR9B0PK-pkFo2WdmQ?key=PqN_g0HdcyYepwEb5e7OGQCR" alt="Screenshot showing the &quot;Categories&quot; dropdown" width="1600" height="760" loading="lazy"></p>
<h4 id="heading-example-task-management-tools"><strong>Example: Task Management Tools</strong></h4>
<p>For instance, searching for “task management” within the "Productivity" filter quickly revealed tools aligned with my development approach.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe4rPt_A_jeeP55hsdvYx24KB-8-OZZh1LXNDOLtnC7T0BOMbDrH6gRG6QGwOQzcWJBbcbjlqryFXlqX7G9TZeVOrGBYbLbL3nybIZEYkjLnZ2Jb8go-Ie199MTaTrsMHP1j-CrAA?key=PqN_g0HdcyYepwEb5e7OGQCR" alt="Screenshot showing task management tools" width="1600" height="760" loading="lazy"></p>
<p>By using filters for categories like "Development" or "Productivity," WebCurate connects you with tools tailored to your tech stack.</p>
<h2 id="heading-how-you-can-make-the-most-of-webcurate">How You Can Make the Most of WebCurate</h2>
<p>I’ve told you a bit about how WebCurate helped me find essential tools and resources while building <a target="_blank" href="https://kreed.tech">Kreed</a>. But let’s be real, the value of a tool lies in how you use it.</p>
<p>So, if you’re thinking about trying WebCurate out for your next project or even just to optimize your workflow, here’s how you can make the most of it:</p>
<ol>
<li><p><strong>Start with Your Needs</strong>: The first step is identifying what you actually need. Are you a developer trying to optimize your front-end stack? A product manager looking for tools to boost team productivity? Or maybe you’re just exploring new ways to streamline your projects? WebCurate’s search and filtering system allows you to narrow down tools based on what you're looking for, so spend some time defining your needs.</p>
</li>
<li><p><strong>Use the Developer Section to Your Advantage</strong>: For anyone working in tech, <a target="_blank" href="https://webcurate.co/c/developer">the Developer section</a> is a <em>must</em>. It’s tailored to provide the most relevant resources for developers, from libraries and frameworks to APIs and deployment tools. For me, it saved hours of research and trial and error. Don’t just browse the section—dig into it, explore the recommendations, and filter by categories or specific tools you’re interested in.</p>
</li>
<li><p><strong>Filter and Refine Your Search</strong>: WebCurate lets you filter by categories like design, front-end development, back-end tools, and even productivity. When you search, take the time to refine your results so you’re only seeing what’s most relevant. For instance, if you’re building a React app, filter for tools that integrate well with React—WebCurate will show you options that are perfect for your tech stack. And since every tool is vetted by the community, you know you’re not wasting time with gimmicks.</p>
</li>
<li><p><strong>Keep an Open Mind and Explore</strong>: While it’s helpful to focus on exactly what you’re looking for, WebCurate also encourages a bit of exploration outside your comfort zone. You might stumble upon a tool or resource you hadn’t thought of but that could genuinely elevate your project. Just don’t get lost in endless scrolling—keep your objective in mind. It’s easy to get sidetracked exploring, but remember, you’re here to find tools that push your project forward, not to doom scroll your way to zero productivity. </p>
</li>
<li><p>Experiment with New Tools and Stay Updated: Building a product is fundamentally about iteration, and this principle extends to the tools you choose to support it. WebCurate is a dynamic resource that updates <strong>daily</strong> with fresh recommendations, making it a valuable tool for discovering new options. Even if you don't need a new tool right now, it's worth revisiting regularly for innovative ideas that could prove useful in the future.</p>
</li>
</ol>
<h2 id="heading-essential-tools-i-discovered-on-webcurate">Essential Tools I Discovered on WebCurate</h2>
<p>Here’s a rundown of a few tools that I found interesting on WebCurate—some are longtime favourites, while others were discoveries that have quickly become essentials.</p>
<h4 id="heading-tools-i-already-use-and-still-love"><strong>Tools I Already Use (and Still Love)</strong></h4>
<ul>
<li><p><a target="_blank" href="https://supabase.com/"><strong>Supabase</strong></a>: An open-source Firebase alternative that simplifies backend development with real-time updates and intuitive database management.</p>
</li>
<li><p><a target="_blank" href="https://gsap.com/"><strong>GSAP</strong></a>: A powerful animation library for creating smooth, high-performance animations (<a target="_blank" href="https://www.freecodecamp.org/news/build-an-interactive-accordion-component-with-react-and-gsap/">I even wrote an article about how I use it</a>!).</p>
</li>
<li><p><a target="_blank" href="https://chromewebstore.google.com/detail/json-viewer/gbmdgpbipfallnflgajpaliibnhdgobh"><strong>JsonViewer</strong></a>: A practical tool for viewing and formatting JSON data, making debugging and visualization easier.</p>
</li>
<li><p><a target="_blank" href="https://caniuse.com/"><strong>CanIUse</strong></a>: An essential resource for checking browser compatibility across HTML5, CSS3, SVG, and more—perfect for ensuring cross-platform support.</p>
</li>
</ul>
<h4 id="heading-new-tools-i-use-often-or-plan-on-using-soon"><strong>New Tools I Use Often or Plan on Using Soon</strong></h4>
<ul>
<li><p><a target="_blank" href="https://chromewebstore.google.com/detail/pesticide-for-chrome/bakpbgckdnepkmkeaiomhmfcnejndkbi?hl=en"><strong>Pesticide for Chrome</strong></a>: A Chrome extension that outlines page elements, helping spot layout issues and verify alignment quickly.</p>
</li>
<li><p><a target="_blank" href="https://wind-ui.com/"><strong>WindUI</strong></a>: A component library styled with Tailwind CSS, that speeds up development without compromising design quality.</p>
</li>
<li><p><a target="_blank" href="https://mobbin.com/"><strong>Mobbin</strong></a>: A curated collection of mobile design patterns, offering inspiration to refine and elevate mobile UI elements.</p>
</li>
<li><p><a target="_blank" href="https://www.mightymeld.com/"><strong>Mightly Meld</strong></a>: A collaborative design tool for React that makes feedback gathering and design iteration faster and more streamlined.</p>
</li>
<li><p><a target="_blank" href="https://www.rowy.io/"><strong>Rowy</strong></a>: A no-code platform connecting directly to databases, allowing for rapid development and deployment of data-driven applications without extensive code.</p>
</li>
</ul>
<h2 id="heading-wrapping-it-up">Wrapping It Up</h2>
<p>At the end of the day, building a startup is a mix of hustle, luck, and learning on the fly. And this article just represents a nerd like me geeking out over tools that made a difference (and resisting the urge to gatekeep 😂).</p>
<p>Finding WebCurate didn’t magically solve all my challenges, but it did cut down on the noise and brought useful resources to my fingertips—a massive win when every minute counts.</p>
<p>If you’re on your startup journey, know this is only part of the ride. Maybe one day I’ll share the full highs and lows of being a founder—assuming I get a break from being Batman 🦇. </p>
<h3 id="heading-like-my-articles">Like my articles?</h3>
<p>Feel free to <a target="_blank" href="https://www.buymeacoffee.com/JajaDavid">buy me a coffee here</a>, to keep my brain chugging and provide more articles like this.</p>
<p>Want to connect?</p>
<ul>
<li><p>Twitter / X: <a target="_blank" href="https://twitter.com/JajaDavid8">@jajadavid8</a></p>
</li>
<li><p>LinkedIn: <a target="_blank" href="https://www.linkedin.com/in/david-jaja-8084251b4/">David Jaja</a></p>
</li>
<li><p>Email: Jajadavidjid@gmail.com</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement Fine-Grained Authorization in Java and SpringBoot ]]>
                </title>
                <description>
                    <![CDATA[ Securing your application goes beyond simply granting or denying access at the surface level. As a developer, you need to implement fine-grained authorization (FGA) to manage permissions at a more detailed, granular level. FGA allows you to set up de... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/fine-grained-authorization-in-java-and-springboot/</link>
                <guid isPermaLink="false">671a85db0db570158155ec00</guid>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Springboot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Thu, 24 Oct 2024 17:37:31 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729783227124/9725e8cf-553d-42c3-a823-5215e8d4d0e9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Securing your application goes beyond simply granting or denying access at the surface level. As a developer, you need to implement <code>fine-grained authorization</code> (FGA) to manage permissions at a more detailed, granular level.</p>
<p>FGA allows you to set up detailed access controls that specify who can do what and under which conditions.</p>
<p>In this tutorial, you will learn how to implement <code>fine-grained authorization</code> in Java and Spring Boot using <a target="_blank" href="https://permit.io/">Permit.io</a>.</p>
<p>Here is the <a target="_blank" href="https://github.com/tyaga001/java-spring-fine-grained-auth">source code</a> (remember to give it a star ⭐).</p>
<p>I hope you enjoyed my previous <a target="_blank" href="https://www.freecodecamp.org/news/how-i-built-a-custom-video-conferencing-app-with-stream-and-nextjs/">blog</a> about building a custom video conferencing app with Stream and Next.js. These blogs reflect my journey in creating DevTools Academy, a platform designed to help developers discover amazing developer tools.</p>
<p>This tutorial is another effort to introduce you to a super helpful developer tool that I recently explored.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-permit">What is Permit</a>?</p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-fine-grained-authorization">What is Fine-Grained Authorization</a>?</p>
<ul>
<li><p><a class="post-section-overview" href="#heading-role-based-access-control-rbac">Role-Based Access Control (RBAC)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-attribute-based-access-control-abac">Attribute-Based Access Control (ABAC)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-relationship-based-access-control-rebac">Relationship-Based Access Control (ReBAC)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-fine-grained-authorization">How to Implement Fine-Grained Authorization</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-implementing-role-based-access-control">Implementing Role-Based Access Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-implementing-attribute-based-access-control">Implementing Attribute-Based Access Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-implementing-relationship-based-access-control">Implementing Relationship-Based Access Control</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-fga-in-java-and-springboot">How to Implement FGA in Java and SpringBoot</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-setting-up-the-e-commerce-application">Step 1: Setting Up the E-commerce Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-get-your-environment-api-key">Step 2: Get your Environment API Key</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-deploy-policy-decision-point-pdp">Step 3: Deploy Policy Decision Point (PDP)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-running-the-app">Step 4: Running the App</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-before-we-end">Before We End...</a></li>
</ul>
</li>
</ul>
<h2 id="heading-what-is-permit"><strong>What is</strong> Permit?</h2>
<blockquote>
<p><a target="_blank" href="https://www.permit.io">Permit.io</a> is a full stack, plug-and-play application-level authorization solution that allows you to implement a <code>secure</code>, <code>flexible</code>, <code>authorization</code> layer within minutes, so you can focus on what matters most.</p>
</blockquote>
<p><a target="_blank" href="https://www.permit.io"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729499767197/6e2b4312-8986-493e-9453-3b67e6aad155.png" alt="what is permit - screenshot of homepage" class="image--center mx-auto" width="3098" height="1810" loading="lazy"></a></p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To fully understand the tutorial, you need to have a basic understanding of <code>Java</code> and <code>Spring Boot</code>. You’ll also need the following:</p>
<ul>
<li><p><a target="_blank" href="http://Permit.io"><strong>Permit.io</strong></a>: A developer tool that simplifies the implementation of FGA.</p>
</li>
<li><p><strong>Spring Boot Starter Web</strong>: Provides essential components for building web applications, including RESTful APIs.</p>
</li>
<li><p><strong>Gradle</strong>: A build tool for managing dependencies.</p>
</li>
<li><p><strong>JDK 11 or later</strong>: The Java Development Kit version required to compile and run your Spring Boot app.</p>
</li>
<li><p><strong>Postman or cURL</strong>: Tools for testing your <code>API</code> endpoints.</p>
</li>
</ul>
<h2 id="heading-what-is-fine-grained-authorization"><strong>What is Fine-Grained Authorization?</strong></h2>
<p><a target="_blank" href="https://www.permit.io/blog/what-is-fine-grained-authorization-fga">Fine-grained authorization</a> offers access control to resources by determining who can access them, to what extent, and under specified conditions.</p>
<p>Contrary to coarse-grained authorization (that handles access based on categories like <code>user roles</code> such as "<code>admin</code>" or "<code>user</code>"), fine-grained authorization gives you the flexibility to define access at a granular level, for specific resources or actions and even attributes. </p>
<p>In <code>Fine Grained Authorization</code> there exist 3 types of policy models for managing authorization; <strong>Role Based Access Control (RBAC)</strong>, <strong>Attribute Based Access Control (ABAC)</strong>, and <strong>Relationship-Based Access Control (ReBAC)</strong>.</p>
<p>Let's take a look, at each of these approaches and see how you can implement them in your application.</p>
<h3 id="heading-role-based-access-control-rbac"><strong>Role-Based Access Control (RBAC)</strong></h3>
<p><a target="_blank" href="https://www.permit.io/blog/what-is-rebac">RBAC</a> is a security approach that controls resource access based on the roles of users within an organization. This model streamlines permissions by organizing users into roles and managing access control according to these defined roles.</p>
<p><strong>Key Concepts in RBAC:</strong></p>
<p><strong>Users:</strong> People who use the system such as employees or customers.</p>
<p><strong>Roles:</strong> A set of permissions or access privileges assigned to a group of users based on their responsibilities or tasks such as admin, manager, or customer.</p>
<p><strong>Permissions:</strong> The rights granted to users for interacting with resources, such as read, write, or delete.</p>
<h3 id="heading-attribute-based-access-control-abac"><strong>Attribute-Based Access Control (ABAC)</strong></h3>
<p><a target="_blank" href="https://www.permit.io/blog/what-is-abac">ABAC</a> is a versatile and adaptive access control model that decides who can or cannot access resources based on attributes, like user details. The ABAC model allows you to define fine-grained authorization based on user attributes.</p>
<p><strong>Key Concepts in ABAC:</strong></p>
<p><strong>Attributes:</strong> Characteristics or properties used to make access control decisions. Attributes are typically categorized into:</p>
<ul>
<li><p><strong>User Attributes:</strong> Information about the user (for example, role, department, job title, age, and so on).</p>
</li>
<li><p><strong>Resource Attributes:</strong> Characteristics of the resource (for example, file type, data classification level, creation date, owner).</p>
</li>
<li><p><strong>Action Attributes:</strong> The action the user is trying to perform (for example, read, write, delete, approve).</p>
</li>
<li><p><strong>Environmental Attributes:</strong> Contextual information about the access request (for example, time of day, location, device type, IP address).</p>
</li>
</ul>
<h3 id="heading-relationship-based-access-control-rebac"><strong>Relationship-Based Access Control (ReBAC)</strong></h3>
<p>ReBAC is an access control system that grants permissions to access resources based on the relationship between entities within a system. The approach emphasizes defining and managing access control by mapping out how users relate to resources and other entities such as organizations or groups.</p>
<p><strong>Key Concepts of ReBAC:</strong></p>
<p><strong>Entities:</strong> Users, resources (such as files and documents), and other entities, such as groups or organizational units.</p>
<p><strong>Relationships:</strong> The connections that specify the relationship between two entities. A user might be the "owner" of a document or a "member" of a team, for instance.</p>
<p><strong>Policies:</strong> Rules that use relationships to determine access rights. A user can access a resource or execute an action on it if they have a particular relationship with it.</p>
<h2 id="heading-how-to-implement-fine-grained-authorization"><strong>How to Implement Fine-Grained Authorization</strong></h2>
<p>Now that you have a basic understanding of <code>RBAC</code>, <code>ABAC</code>, and <code>ReBAC</code>, let’s see how we can implement these models in an e-commerce app.</p>
<h3 id="heading-implementing-role-based-access-control"><strong>Implementing Role-Based Access Control</strong></h3>
<p><strong>Step 1:</strong> Navigate to <a target="_blank" href="http://Permit.io">Permit.io</a>, and then create an account and your workspace.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729494514537/a49035e8-1eb2-495f-acee-6ac212d0076e.png" alt="Permit.io - create your workspace page" class="image--center mx-auto" width="3124" height="1844" loading="lazy"></p>
<p>By default, you should see a project that includes two environments: <code>Development</code> and <code>Production</code>. </p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong><em>Note:</em></strong><em> You need to define and test your policies in the development environment before deploying them to production.</em></div>
</div>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729494835226/8d0e6841-09e7-44e2-89fb-1ec3bacb316d.png" alt="Permit.io - project dashboard" class="image--center mx-auto" width="3124" height="1850" loading="lazy"></p>
<p><strong>Step 2:</strong> Create a resource named <strong>Products</strong>. To create the resource, open the <strong>Policy</strong> tab on the left sidebar and then open the <strong>Resources</strong> tab at the top. After that, click the <strong>Create a Resource</strong> button and then create a resource called <strong>Products</strong> with actions <code>read</code>, <code>create</code>, <code>update</code>, and <code>delete</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729495042599/91660d3d-eafe-4874-aeb2-50bf88c5a291.png" alt="Permit.io - how to add a new resource" class="image--center mx-auto" width="3128" height="1852" loading="lazy"></p>
<p><strong>Step 3:</strong> Create another resource called <strong>Reviews</strong> with actions <code>read</code>, <code>create</code>, <code>update</code>, and <code>delete</code>.</p>
<p><strong>Step 4:</strong> Open the <strong>Policy Editor</strong> tab. You’ll see that 3 roles named <code>admin</code>, <code>editor</code>, and <code>viewer</code> were created.</p>
<ul>
<li><p>Role admin has permission to <code>create</code>, <code>delete</code>, <code>read</code>, or <code>update</code> a product or a review.</p>
</li>
<li><p>Role <code>editor</code> has permission to <code>create</code>, <code>read</code>, or <code>update</code> a <code>product</code> or a <code>review</code> but not <code>delete</code> any.</p>
</li>
<li><p>Role <code>viewer</code> has permission to <code>create</code> and <code>read</code> a product or a <code>review</code> but not <code>delete</code> or <code>update</code> any.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729495227714/38553c90-5cc0-4fa0-a116-2f5051b5ebb8.png" alt="Permit.io - Policy editor" class="image--center mx-auto" width="3108" height="1858" loading="lazy"></p>
<h3 id="heading-implementing-attribute-based-access-control"><strong>Implementing Attribute-Based Access Control</strong></h3>
<p><strong>Step 1:</strong> Open the <strong>Resources</strong> tab, then click the <strong>Add Attributes</strong> button.</p>
<ul>
<li><p>Add an attribute called <strong>vendor</strong></p>
</li>
<li><p>Add an attribute called the <strong>customer</strong></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729495417262/94a09532-83b3-496c-8fef-6ee7c836a211.png" alt="Permit.io - edit resource" class="image--center mx-auto" width="3126" height="1850" loading="lazy"></p>
<p><strong>Step 2:</strong> Open the ABAC Rules tab, then create a new ABAC Resource Set called <strong>Own Products</strong> that depends on the Products resource. After that, add a condition that gives permissions only to the user who created a product based on the vendor attribute.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729495597939/d528f47f-710a-4bd6-b13a-1cf3a3c49031.png" alt="Permit.io - create your resource set page" class="image--center mx-auto" width="3122" height="1854" loading="lazy"></p>
<p><strong>Step 3:</strong> Create another ABAC Resource Set called <strong>Own Reviews</strong> that depends on the Reviews resource.</p>
<h3 id="heading-implementing-relationship-based-access-control"><strong>Implementing Relationship-Based Access Control</strong></h3>
<p><strong>Step 1:</strong> Open the Resources tab and edit the Products resource. Add role <code>vendor</code> in the <code>ReBAC</code> options section. Then set products as parent of reviews in the relations section.</p>
<p><strong>Step 2:</strong> Edit the Reviews resource by adding role customer in the <code>ReBAC</code> options section, as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729497355241/4f1a6235-7181-468a-82ce-c727df517604.png" alt="Permit.io - ABAC edit resource" class="image--center mx-auto" width="3124" height="1842" loading="lazy"></p>
<p><strong>Step 3:</strong> Go to <code>Policy</code> <code>Editor</code> tab and add:</p>
<ul>
<li><p>role <code>vendor</code> permission to update and delete own products.</p>
</li>
<li><p>role <code>customer</code> permission to update and delete their own reviews on products.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729497438508/935f79b7-3789-4047-b7b5-73e82654b617.png" alt="Permit.io - Policy editor" class="image--center mx-auto" width="3122" height="1852" loading="lazy"></p>
<h2 id="heading-how-to-implement-fga-in-java-and-springboot"><strong>How to Implement FGA in Java and SpringBoot</strong></h2>
<p>Now that we have defined <code>RBAC</code>, <code>ABAC</code>, and <code>ReBAC</code> policies in the Permit.io web interface, let’s learn how to enforce them in an E-Commerce Management System application using the Permit.io API.</p>
<p>There’s a lot of code coming up, so make sure you read through the extensive comments I’ve left throughout each code block. These will help you understand more fully what’s going on in this code.</p>
<h3 id="heading-step-1-setting-up-the-e-commerce-application"><strong>Step 1: Setting Up the E-commerce Application</strong></h3>
<p>To set up the e-commerce application and git clone the source code.</p>
<pre><code class="lang-powershell">git clone https://github.com/tyaga001/java<span class="hljs-literal">-spring</span><span class="hljs-literal">-fine</span><span class="hljs-literal">-grained</span><span class="hljs-literal">-auth</span>.git
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Then open the code in your Java IDE.&nbsp;I used <a target="_blank" href="https://www.jetbrains.com/idea/">JetBrains</a> for all my work.</div>
</div>

<h4 id="heading-installing-permit-package-sdk"><strong>Installing Permit package SDK</strong></h4>
<p>To install the Permit package SDK, you add the SDK under the dependencies block in the <code>build.graddle</code> file.</p>
<pre><code class="lang-java">## Dependencies

To set up the necessary dependencies <span class="hljs-keyword">for</span> your Spring Boot project, include the following in your `build.gradle` file:

```groovy
dependencies {
    implementation <span class="hljs-string">'org.springframework.boot:spring-boot-starter-web'</span>
    implementation <span class="hljs-string">'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'</span>
    developmentOnly <span class="hljs-string">'org.springframework.boot:spring-boot-devtools'</span>
    testImplementation <span class="hljs-string">'org.springframework.boot:spring-boot-starter-test'</span>
    testRuntimeOnly <span class="hljs-string">'org.junit.platform:junit-platform-launcher'</span>

    <span class="hljs-comment">// Add this line to install the Permit.io Java SDK in your project</span>
    implementation <span class="hljs-string">'io.permit:permit-sdk-java:2.0.0'</span>
}
</code></pre>
<h4 id="heading-initializing-the-permit-sdk"><strong>Initializing the Permit SDK</strong></h4>
<p>You can initialize the Permit <code>SDK</code> Client using the code below:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.boostmytool.store.config;

<span class="hljs-keyword">import</span> io.permit.sdk.Permit;
<span class="hljs-keyword">import</span> io.permit.sdk.PermitConfig;
<span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Value;
<span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;
<span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;

<span class="hljs-meta">@Configuration</span>  <span class="hljs-comment">// Marks this class as a configuration class for Spring IoC</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PermitClientConfig</span> </span>{

    <span class="hljs-meta">@Value("${permit.api-key}")</span>  <span class="hljs-comment">// Inject Permit API key from application properties</span>
    <span class="hljs-keyword">private</span> String apiKey;

    <span class="hljs-meta">@Value("${permit.pdp-url}")</span>  <span class="hljs-comment">// Inject Permit PDP (Policy Decision Point) URL from application properties</span>
    <span class="hljs-keyword">private</span> String pdpUrl;

    <span class="hljs-comment">/**
     * Creates a Permit client bean with custom configuration
     * <span class="hljs-doctag">@return</span> Permit client instance
     */</span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Permit <span class="hljs-title">permit</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Permit(
                <span class="hljs-keyword">new</span> PermitConfig.Builder(apiKey)  <span class="hljs-comment">// Initialize PermitConfig with API key</span>
                        .withPdpAddress(pdpUrl)   <span class="hljs-comment">// Set the PDP address</span>
                        .withDebugMode(<span class="hljs-keyword">true</span>)      <span class="hljs-comment">// Enable debug mode for detailed logging</span>
                        .build()                  <span class="hljs-comment">// Build the PermitConfig object</span>
        );
    }
}
</code></pre>
<h4 id="heading-syncing-users-with-sdk"><strong>Syncing Users with SDK</strong></h4>
<p>To start enforcing permissions, you should first sync a user to Permit, and then assign them a role.</p>
<p>In the code below, the <strong>UserService</strong> class provides methods for user login, signup, role assignment, and authorization, with exception handling for possible errors when interacting with the Permit API.</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.boostmytool.store.service;

<span class="hljs-keyword">import</span> com.boostmytool.store.exception.ForbiddenAccessException;
<span class="hljs-keyword">import</span> com.boostmytool.store.exception.UnauthorizedException;
<span class="hljs-keyword">import</span> io.permit.sdk.Permit;
<span class="hljs-keyword">import</span> io.permit.sdk.api.PermitApiError;
<span class="hljs-keyword">import</span> io.permit.sdk.api.PermitContextError;
<span class="hljs-keyword">import</span> io.permit.sdk.enforcement.Resource;
<span class="hljs-keyword">import</span> io.permit.sdk.enforcement.User;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Service;

<span class="hljs-keyword">import</span> java.io.IOException;

<span class="hljs-meta">@Service</span>  <span class="hljs-comment">// Marks this class as a Spring service, making it a candidate for component scanning</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Permit permit;

    <span class="hljs-comment">// Constructor injection for the Permit SDK</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">UserService</span><span class="hljs-params">(Permit permit)</span> </span>{
        <span class="hljs-keyword">this</span>.permit = permit;
    }

    <span class="hljs-comment">/**
     * Simulates user login by creating and returning a Permit User object.
     * 
     * <span class="hljs-doctag">@param</span> key User's unique key
     * <span class="hljs-doctag">@return</span> User object
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">login</span><span class="hljs-params">(String key)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> User.Builder(key).build();
    }

    <span class="hljs-comment">/**
     * Handles user signup by creating and syncing a new Permit User.
     * 
     * <span class="hljs-doctag">@param</span> key User's unique key
     * <span class="hljs-doctag">@return</span> Created and synced User object
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">signup</span><span class="hljs-params">(String key)</span> </span>{
        <span class="hljs-keyword">var</span> user = <span class="hljs-keyword">new</span> User.Builder(key).build();
        <span class="hljs-keyword">try</span> {
            permit.api.users.sync(user);  <span class="hljs-comment">// Syncs the new user with the Permit service</span>
        } <span class="hljs-keyword">catch</span> (PermitContextError | PermitApiError | IOException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Failed to create user"</span>, e);  <span class="hljs-comment">// Handles exceptions during user creation</span>
        }
        <span class="hljs-keyword">return</span> user;
    }

    <span class="hljs-comment">/**
     * Assigns a role to the user within the "default" environment.
     * 
     * <span class="hljs-doctag">@param</span> user User object to assign the role to
     * <span class="hljs-doctag">@param</span> role Role to be assigned
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">assignRole</span><span class="hljs-params">(User user, String role)</span> </span>{
        <span class="hljs-keyword">try</span> {
            permit.api.users.assignRole(user.getKey(), role, <span class="hljs-string">"default"</span>);  <span class="hljs-comment">// Assigns role in the "default" environment</span>
        } <span class="hljs-keyword">catch</span> (PermitApiError | PermitContextError | IOException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Failed to assign role to user"</span>, e);  <span class="hljs-comment">// Handles exceptions during role assignment</span>
        }
    }

    <span class="hljs-comment">/**
     * Checks if the user is authorized to perform a specific action on a resource.
     * 
     * <span class="hljs-doctag">@param</span> user User object requesting authorization
     * <span class="hljs-doctag">@param</span> action Action to be authorized
     * <span class="hljs-doctag">@param</span> resource Resource on which the action will be performed
     * <span class="hljs-doctag">@throws</span> UnauthorizedException if user is not logged in
     * <span class="hljs-doctag">@throws</span> ForbiddenAccessException if user is denied access
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">authorize</span><span class="hljs-params">(User user, String action, Resource resource)</span> </span>{
        <span class="hljs-keyword">if</span> (user == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">"Not logged in"</span>);  <span class="hljs-comment">// Throws exception if user is not logged in</span>
        }
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">var</span> permitted = permit.check(user, action, resource);  <span class="hljs-comment">// Performs authorization check</span>
            <span class="hljs-keyword">if</span> (!permitted) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ForbiddenAccessException(<span class="hljs-string">"Access denied"</span>);  <span class="hljs-comment">// Throws exception if access is denied</span>
            }
        } <span class="hljs-keyword">catch</span> (PermitApiError | IOException e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Failed to authorize user"</span>, e);  <span class="hljs-comment">// Handles exceptions during authorization</span>
        }
    }
}
</code></pre>
<p>Then in the code below, the <strong>UserController</strong> class exposes REST API endpoints for user signup and role assignment. It interacts with the UserService class to handle user-related business logic and provides appropriate HTTP responses.</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.boostmytool.store.controllers;

<span class="hljs-keyword">import</span> com.boostmytool.store.exception.UnauthorizedException;
<span class="hljs-keyword">import</span> com.boostmytool.store.service.UserService;
<span class="hljs-keyword">import</span> io.permit.sdk.enforcement.User;
<span class="hljs-keyword">import</span> jakarta.servlet.http.HttpServletRequest;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.PostMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestBody;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;

<span class="hljs-meta">@RestController</span>  <span class="hljs-comment">// Indicates that this class handles HTTP requests and returns JSON responses</span>
<span class="hljs-meta">@RequestMapping("/api/users")</span>  <span class="hljs-comment">// Base URL path for all user-related operations</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UserService userService;

    <span class="hljs-comment">// Constructor injection of UserService, containing business logic for user operations</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">UserController</span><span class="hljs-params">(UserService userService)</span> </span>{
        <span class="hljs-keyword">this</span>.userService = userService;
    }

    <span class="hljs-comment">/**
     * Handles user signup requests.
     * Endpoint: POST /api/users/signup
     * 
     * <span class="hljs-doctag">@param</span> key Unique key for the new user
     * <span class="hljs-doctag">@return</span> Created User object
     */</span>
    <span class="hljs-meta">@PostMapping("/signup")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">signup</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> String key)</span> </span>{
        <span class="hljs-keyword">return</span> userService.signup(key);  <span class="hljs-comment">// Calls the signup method in UserService to create a new user</span>
    }

    <span class="hljs-comment">/**
     * Handles assigning a role to the logged-in user.
     * Endpoint: POST /api/users/assign-role
     * 
     * <span class="hljs-doctag">@param</span> request HTTP request, used to retrieve the current user
     * <span class="hljs-doctag">@param</span> role Role to be assigned to the current user
     */</span>
    <span class="hljs-meta">@PostMapping("/assign-role")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">assignRole</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@RequestBody</span> String role)</span> </span>{
        <span class="hljs-comment">// Retrieves the current user from the request attributes</span>
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);

        <span class="hljs-comment">// Throws an exception if the user is not logged in</span>
        <span class="hljs-keyword">if</span> (currentUser == <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedException(<span class="hljs-string">"Not logged in"</span>);
        }

        <span class="hljs-comment">// Assigns the specified role to the current user</span>
        userService.assignRole(currentUser, role);
    }
}
</code></pre>
<h4 id="heading-creating-rbac-abac-and-rebac-policy-enforcement-point"><strong>Creating RBAC, ABAC, and ReBAC Policy Enforcement Point</strong></h4>
<p>In the code below, the <strong>ProductService</strong> class manages CRUD operations for products and reviews, handling permissions and roles via the Permit API.</p>
<p>Each operation includes user <code>authorization</code> checks, with appropriate exception handling for Permit API errors and resource not found scenarios.</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.boostmytool.store.service;

<span class="hljs-keyword">import</span> com.boostmytool.store.exception.ResourceNotFoundException;
<span class="hljs-keyword">import</span> com.boostmytool.store.model.Product;
<span class="hljs-keyword">import</span> com.boostmytool.store.model.Review;
<span class="hljs-keyword">import</span> io.permit.sdk.Permit;
<span class="hljs-keyword">import</span> io.permit.sdk.api.PermitApiError;
<span class="hljs-keyword">import</span> io.permit.sdk.api.PermitContextError;
<span class="hljs-keyword">import</span> io.permit.sdk.enforcement.Resource;
<span class="hljs-keyword">import</span> io.permit.sdk.enforcement.User;
<span class="hljs-keyword">import</span> io.permit.sdk.openapi.models.RelationshipTupleCreate;
<span class="hljs-keyword">import</span> io.permit.sdk.openapi.models.ResourceInstanceCreate;
<span class="hljs-keyword">import</span> io.permit.sdk.openapi.models.RoleAssignmentCreate;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Service;

<span class="hljs-keyword">import</span> java.io.IOException;
<span class="hljs-keyword">import</span> java.util.ArrayList;
<span class="hljs-keyword">import</span> java.util.HashMap;
<span class="hljs-keyword">import</span> java.util.List;
<span class="hljs-keyword">import</span> java.util.concurrent.atomic.AtomicInteger;

<span class="hljs-meta">@Service</span>  <span class="hljs-comment">// Marks this class as a Spring service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductService</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> List&lt;Product&gt; products = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();  <span class="hljs-comment">// In-memory list to store products</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AtomicInteger productIdCounter = <span class="hljs-keyword">new</span> AtomicInteger();  <span class="hljs-comment">// Counter to generate unique product IDs</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AtomicInteger reviewIdCounter = <span class="hljs-keyword">new</span> AtomicInteger();   <span class="hljs-comment">// Counter to generate unique review IDs</span>

    <span class="hljs-comment">// Builders for Permit resource instances (product and review)</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Resource.Builder productResourceBuilder = <span class="hljs-keyword">new</span> Resource.Builder(<span class="hljs-string">"product"</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Resource.Builder reviewResourceBuilder = <span class="hljs-keyword">new</span> Resource.Builder(<span class="hljs-string">"review"</span>);

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UserService userService;  <span class="hljs-comment">// Service for handling user-related operations</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Permit permit;  <span class="hljs-comment">// Permit SDK instance for handling authorization and resource management</span>

    <span class="hljs-comment">// Constructor for injecting dependencies</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ProductService</span><span class="hljs-params">(UserService userService, Permit permit)</span> </span>{
        <span class="hljs-keyword">this</span>.userService = userService;
        <span class="hljs-keyword">this</span>.permit = permit;
    }

    <span class="hljs-comment">// Method to authorize a user for a given action on a resource</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">authorize</span><span class="hljs-params">(User user, String action, Resource resource)</span> </span>{
        userService.authorize(user, action, resource);
    }

    <span class="hljs-comment">// Authorizes a user to perform an action on a specific product</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">authorize</span><span class="hljs-params">(User user, String action, Product product)</span> </span>{
        <span class="hljs-keyword">var</span> attributes = <span class="hljs-keyword">new</span> HashMap&lt;String, Object&gt;();
        attributes.put(<span class="hljs-string">"vendor"</span>, product.getVendor());  <span class="hljs-comment">// Add vendor attribute to the product</span>
        userService.authorize(user, action, productResourceBuilder.withKey(product.getId().toString()).withAttributes(attributes).build());
    }

    <span class="hljs-comment">// Authorizes a user to perform an action on a specific review</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">authorize</span><span class="hljs-params">(User user, String action, Review review)</span> </span>{
        <span class="hljs-keyword">var</span> attributes = <span class="hljs-keyword">new</span> HashMap&lt;String, Object&gt;();
        attributes.put(<span class="hljs-string">"customer"</span>, review.getCustomer());  <span class="hljs-comment">// Add customer attribute to the review</span>
        userService.authorize(user, action, reviewResourceBuilder.withKey(review.getId().toString()).withAttributes(attributes).build());
    }

    <span class="hljs-comment">// Retrieves a product by its ID, throws an exception if not found</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> Product <span class="hljs-title">getProductById</span><span class="hljs-params">(<span class="hljs-keyword">int</span> id)</span> </span>{
        <span class="hljs-keyword">return</span> products.stream().filter(product -&gt; product.getId().equals(id))
                .findFirst().orElseThrow(() -&gt; <span class="hljs-keyword">new</span> ResourceNotFoundException(<span class="hljs-string">"Product with id "</span> + id + <span class="hljs-string">" not found"</span>));
    }

    <span class="hljs-comment">// Retrieves all products, checks if the user is authorized to "read" products</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;Product&gt; <span class="hljs-title">getAllProducts</span><span class="hljs-params">(User user)</span> </span>{
        authorize(user, <span class="hljs-string">"read"</span>, productResourceBuilder.build());  <span class="hljs-comment">// User must have "read" permission</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ArrayList&lt;&gt;(products);  <span class="hljs-comment">// Return a copy of the products list</span>
    }

    <span class="hljs-comment">// Retrieves a product by its ID, checks if the user is authorized to "read" the product</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Product <span class="hljs-title">getProduct</span><span class="hljs-params">(User user, <span class="hljs-keyword">int</span> id)</span> </span>{
        authorize(user, <span class="hljs-string">"read"</span>, productResourceBuilder.build());
        <span class="hljs-keyword">return</span> getProductById(id);
    }

    <span class="hljs-comment">// Adds a new product, authorizes the user and creates resource instances and role assignments in Permit</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Product <span class="hljs-title">addProduct</span><span class="hljs-params">(User user, String content)</span> </span>{
        authorize(user, <span class="hljs-string">"create"</span>, productResourceBuilder.build());  <span class="hljs-comment">// Check if user can create a product</span>
        Product product = <span class="hljs-keyword">new</span> Product(productIdCounter.incrementAndGet(), user.getKey(), content);  <span class="hljs-comment">// Create new product</span>

        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// Create resource instance in Permit and assign "vendor" role to the user for this product</span>
            permit.api.resourceInstances.create(<span class="hljs-keyword">new</span> ResourceInstanceCreate(product.getId().toString(), <span class="hljs-string">"product"</span>).withTenant(<span class="hljs-string">"default"</span>));
            permit.api.roleAssignments.assign(<span class="hljs-keyword">new</span> RoleAssignmentCreate(<span class="hljs-string">"vendor"</span>, user.getKey()).withResourceInstance(<span class="hljs-string">"product:"</span> + product.getId()).withTenant(<span class="hljs-string">"default"</span>));
        } <span class="hljs-keyword">catch</span> (IOException | PermitApiError | PermitContextError e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Failed to create resource instance or role assignment: "</span> + e.getMessage());
        }

        products.add(product);  <span class="hljs-comment">// Add product to in-memory list</span>
        <span class="hljs-keyword">return</span> product;
    }

    <span class="hljs-comment">// Updates a product's content, checks if the user is authorized to "update" the product</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Product <span class="hljs-title">updateProduct</span><span class="hljs-params">(User user, <span class="hljs-keyword">int</span> id, String content)</span> </span>{
        Product product = getProductById(id);  <span class="hljs-comment">// Get the product by its ID</span>
        authorize(user, <span class="hljs-string">"update"</span>, product);  <span class="hljs-comment">// Check if user can update the product</span>
        product.setContent(content);  <span class="hljs-comment">// Update product content</span>
        <span class="hljs-keyword">return</span> product;
    }

    <span class="hljs-comment">// Deletes a product, checks if the user is authorized to "delete" the product</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">deleteProduct</span><span class="hljs-params">(User user, <span class="hljs-keyword">int</span> id)</span> </span>{
        <span class="hljs-keyword">boolean</span> isDeleted = products.removeIf(product -&gt; {
            <span class="hljs-keyword">if</span> (product.getId().equals(id)) {
                authorize(user, <span class="hljs-string">"delete"</span>, product);  <span class="hljs-comment">// Check if user can delete the product</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
            }
        });

        <span class="hljs-keyword">if</span> (!isDeleted) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ResourceNotFoundException(<span class="hljs-string">"Product with id "</span> + id + <span class="hljs-string">" not found"</span>);
        }

        <span class="hljs-keyword">try</span> {
            permit.api.resourceInstances.delete(<span class="hljs-string">"product:"</span> + id);  <span class="hljs-comment">// Remove product resource instance from Permit</span>
        } <span class="hljs-keyword">catch</span> (IOException | PermitApiError | PermitContextError e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
        }
    }

    <span class="hljs-comment">// Adds a review to a product, creates a resource instance and relationship in Permit</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Review <span class="hljs-title">addReview</span><span class="hljs-params">(User user, <span class="hljs-keyword">int</span> productId, String content)</span> </span>{
        authorize(user, <span class="hljs-string">"create"</span>, reviewResourceBuilder.build());  <span class="hljs-comment">// Check if user can create a review</span>
        Product product = getProductById(productId);  <span class="hljs-comment">// Get the product by its ID</span>
        Review review = <span class="hljs-keyword">new</span> Review(reviewIdCounter.incrementAndGet(), user.getKey(), content);  <span class="hljs-comment">// Create new review</span>

        <span class="hljs-keyword">try</span> {
            <span class="hljs-comment">// Create a resource instance for the review and set relationship with the product</span>
            permit.api.resourceInstances.create(<span class="hljs-keyword">new</span> ResourceInstanceCreate(review.getId().toString(), <span class="hljs-string">"review"</span>).withTenant(<span class="hljs-string">"default"</span>));
            permit.api.relationshipTuples.create(<span class="hljs-keyword">new</span> RelationshipTupleCreate(<span class="hljs-string">"product:"</span> + productId, <span class="hljs-string">"parent"</span>, <span class="hljs-string">"review:"</span> + review.getId()));
        } <span class="hljs-keyword">catch</span> (IOException | PermitApiError | PermitContextError e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
        }

        product.addReview(review);  <span class="hljs-comment">// Add the review to the product</span>
        <span class="hljs-keyword">return</span> review;
    }

    <span class="hljs-comment">// Updates a review's content, checks if the user is authorized to "update" the review</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Review <span class="hljs-title">updateReview</span><span class="hljs-params">(User user, <span class="hljs-keyword">int</span> productId, <span class="hljs-keyword">int</span> reviewId, String content)</span> </span>{
        Product product = getProductById(productId);  <span class="hljs-comment">// Get the product by its ID</span>
        Review review = product.getReviews().stream().filter(c -&gt; c.getId().equals(reviewId))
                .findFirst().orElseThrow(() -&gt; <span class="hljs-keyword">new</span> ResourceNotFoundException(<span class="hljs-string">"Review with id "</span> + reviewId + <span class="hljs-string">" not found"</span>));

        authorize(user, <span class="hljs-string">"update"</span>, review);  <span class="hljs-comment">// Check if user can update the review</span>
        review.setContent(content);  <span class="hljs-comment">// Update review content</span>
        <span class="hljs-keyword">return</span> review;
    }

    <span class="hljs-comment">// Deletes a review, checks if the user is authorized to "delete" the review</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">deleteReview</span><span class="hljs-params">(User user, <span class="hljs-keyword">int</span> productId, <span class="hljs-keyword">int</span> reviewId)</span> </span>{
        Product product = getProductById(productId);  <span class="hljs-comment">// Get the product by its ID</span>
        <span class="hljs-keyword">boolean</span> isDeleted = product.getReviews().removeIf(review -&gt; {
            <span class="hljs-keyword">if</span> (review.getId().equals(reviewId)) {
                authorize(user, <span class="hljs-string">"delete"</span>, review);  <span class="hljs-comment">// Check if user can delete the review</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
            }
        });

        <span class="hljs-keyword">if</span> (!isDeleted) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ResourceNotFoundException(<span class="hljs-string">"Review with id "</span> + reviewId + <span class="hljs-string">" not found"</span>);
        }

        <span class="hljs-keyword">try</span> {
            permit.api.resourceInstances.delete(<span class="hljs-string">"review:"</span> + reviewId);  <span class="hljs-comment">// Remove review resource instance from Permit</span>
        } <span class="hljs-keyword">catch</span> (IOException | PermitApiError | PermitContextError e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(e);
        }
    }
}
</code></pre>
<p>Then in the code below, the <strong>ProductController</strong> class handles HTTP requests related to products and their reviews. It exposes endpoints for managing products (like <code>creating</code>, <code>updating</code>, <code>deleting</code>, and <code>retrieving</code>) and for managing product reviews.</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.boostmytool.store.controllers;

<span class="hljs-keyword">import</span> com.boostmytool.store.model.Product;
<span class="hljs-keyword">import</span> com.boostmytool.store.model.Review;
<span class="hljs-keyword">import</span> com.boostmytool.store.service.ProductService;
<span class="hljs-keyword">import</span> io.permit.sdk.enforcement.User;
<span class="hljs-keyword">import</span> jakarta.servlet.http.HttpServletRequest;
<span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;
<span class="hljs-keyword">import</span> org.springframework.http.HttpStatus;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.*;

<span class="hljs-keyword">import</span> java.util.List;

<span class="hljs-meta">@RestController</span>  <span class="hljs-comment">// Indicates that this class is a Spring REST controller</span>
<span class="hljs-meta">@RequestMapping("/api/products")</span>  <span class="hljs-comment">// Base URL for all endpoints in this controller</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ProductService productService;  <span class="hljs-comment">// ProductService instance to handle product-related operations</span>

    <span class="hljs-meta">@Autowired</span>  <span class="hljs-comment">// Autowires ProductService bean automatically</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ProductController</span><span class="hljs-params">(ProductService productService)</span> </span>{
        <span class="hljs-keyword">this</span>.productService = productService;
    }

    <span class="hljs-comment">// GET request to retrieve all products</span>
    <span class="hljs-meta">@GetMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;Product&gt; <span class="hljs-title">getAllProducts</span><span class="hljs-params">(HttpServletRequest request)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        <span class="hljs-keyword">return</span> productService.getAllProducts(currentUser);  <span class="hljs-comment">// Calls ProductService to get all products for the user</span>
    }

    <span class="hljs-comment">// GET request to retrieve a product by its ID</span>
    <span class="hljs-meta">@GetMapping("/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Product <span class="hljs-title">getProductById</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@PathVariable("id")</span> <span class="hljs-keyword">int</span> id)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        <span class="hljs-keyword">return</span> productService.getProduct(currentUser, id);  <span class="hljs-comment">// Calls ProductService to get the product by ID for the user</span>
    }

    <span class="hljs-comment">// POST request to add a new product</span>
    <span class="hljs-meta">@PostMapping</span>
    <span class="hljs-meta">@ResponseStatus(HttpStatus.CREATED)</span>  <span class="hljs-comment">// Sets the response status to 201 (Created)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Product <span class="hljs-title">addProduct</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@RequestBody</span> String content)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        <span class="hljs-keyword">return</span> productService.addProduct(currentUser, content);  <span class="hljs-comment">// Calls ProductService to add a new product</span>
    }

    <span class="hljs-comment">// PUT request to update an existing product by its ID</span>
    <span class="hljs-meta">@PutMapping("/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Product <span class="hljs-title">updateProduct</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@PathVariable("id")</span> <span class="hljs-keyword">int</span> id, <span class="hljs-meta">@RequestBody</span> String content)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        <span class="hljs-keyword">return</span> productService.updateProduct(currentUser, id, content);  <span class="hljs-comment">// Calls ProductService to update the product by ID</span>
    }

    <span class="hljs-comment">// DELETE request to delete a product by its ID</span>
    <span class="hljs-meta">@DeleteMapping("/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">deleteProduct</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@PathVariable("id")</span> <span class="hljs-keyword">int</span> id)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        productService.deleteProduct(currentUser, id);  <span class="hljs-comment">// Calls ProductService to delete the product by ID</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Deleted product with id "</span> + id;  <span class="hljs-comment">// Returns a success message after deletion</span>
    }

    <span class="hljs-comment">// POST request to add a new review to a product by product ID</span>
    <span class="hljs-meta">@PostMapping("/{id}/review")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Review <span class="hljs-title">addReview</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@PathVariable("id")</span> <span class="hljs-keyword">int</span> id, <span class="hljs-meta">@RequestBody</span> String content)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        <span class="hljs-keyword">return</span> productService.addReview(currentUser, id, content);  <span class="hljs-comment">// Calls ProductService to add a review to the product</span>
    }

    <span class="hljs-comment">// PUT request to update an existing review by product and review ID</span>
    <span class="hljs-meta">@PutMapping("/{id}/review/{reviewId}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Review <span class="hljs-title">updateReview</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@PathVariable("id")</span> <span class="hljs-keyword">int</span> id, <span class="hljs-meta">@PathVariable("reviewId")</span> <span class="hljs-keyword">int</span> reviewId, <span class="hljs-meta">@RequestBody</span> String content)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        <span class="hljs-keyword">return</span> productService.updateReview(currentUser, id, reviewId, content);  <span class="hljs-comment">// Calls ProductService to update the review</span>
    }

    <span class="hljs-comment">// DELETE request to delete a review by product and review ID</span>
    <span class="hljs-meta">@DeleteMapping("/{id}/review/{reviewId}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">deleteReview</span><span class="hljs-params">(HttpServletRequest request, <span class="hljs-meta">@PathVariable("id")</span> <span class="hljs-keyword">int</span> id, <span class="hljs-meta">@PathVariable("reviewId")</span> <span class="hljs-keyword">int</span> reviewId)</span> </span>{
        User currentUser = (User) request.getAttribute(<span class="hljs-string">"user"</span>);  <span class="hljs-comment">// Gets the authenticated user from the request</span>
        productService.deleteReview(currentUser, id, reviewId);  <span class="hljs-comment">// Calls ProductService to delete the review</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Deleted review with id "</span> + reviewId + <span class="hljs-string">" from product "</span> + id;  <span class="hljs-comment">// Returns a success message after deletion</span>
    }
}
</code></pre>
<h3 id="heading-step-2-get-your-environment-api-key"><strong>Step 2: Get your Environment API Key</strong></h3>
<p>In the UI Dashboard, copy the Environment <code>API Key</code> of the active environment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729498343969/2bbbd4a0-512f-4b46-a82a-ca41ecb67a4c.png" alt="Permit.io - copy env key" class="image--center mx-auto" width="3114" height="1834" loading="lazy"></p>
<p>Then add the env <code>API key</code> and <code>PDP URL</code> in the <code>application.yaml</code> file.</p>
<pre><code class="lang-plaintext">permit:
  pdpUrl: 'http://localhost:7766'
  apiKey: "Your Permit environment API Key"
</code></pre>
<h3 id="heading-step-3-deploy-policy-decision-point-pdp"><strong>Step 3: Deploy Policy Decision Point (PDP)</strong></h3>
<p>The Policy Decision Point (PDP) is deployed in your VPC and is in charge of evaluating your authorization requests. The PDP will ensure zero latency, great performance, high availability, and improved security.</p>
<p>Use the command below to pull the <a target="_blank" href="http://Permit.io">Permit.io</a> PDP container from <code>Docker</code> Hub.</p>
<pre><code class="lang-dockerfile">docker pull permitio/pdp-v2:latest
</code></pre>
<p>Then run the container.</p>
<pre><code class="lang-dockerfile">docker <span class="hljs-keyword">run</span><span class="bash"> -it -p 7766:7000 --env PDP_DEBUG=True --env PDP_API_KEY=&lt;YOUR_API_KEY&gt; permitio/pdp-v2:latest</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729515246656/6bed08e3-6109-4643-a724-f55641d7c974.png" alt="Permit.io - local testing" class="image--center mx-auto" width="3150" height="1608" loading="lazy"></p>
<h3 id="heading-step-4-running-the-app"><strong>Step 4: Running the App</strong></h3>
<p>You can run the application using the following <code>Gradle</code> command:</p>
<pre><code class="lang-dockerfile">./gradlew bootRun
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729515837209/41556843-b8d5-4433-a2f3-93371562d27d.png" alt="Permit.io - how to run the app in local" class="image--center mx-auto" width="3152" height="1610" loading="lazy"></p>
<h4 id="heading-viewing-and-creating-products"><strong>Viewing and Creating Products</strong></h4>
<p>Let’s now interact with the application endpoints using <a target="_blank" href="https://reqbin.com/curl">REQBIN</a>.</p>
<p>First, create a new user using the <code>/api/users/signup</code> endpoint.</p>
<pre><code class="lang-dockerfile">curl -X POST <span class="hljs-string">"http://localhost:8080/api/users/signup"</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -d <span class="hljs-string">'johndoe'</span>
</code></pre>
<p>You should be able to view the user in your Permit project, under Directory &gt; All Tenants.</p>
<p>Initially, the user has no roles, so it cannot do much. For example, trying to list the products will result in a 403 Forbidden response, as shown below. The 403 error code means the user doesn’t have permissions to access the requested resource, which is products in this case. You can learn more about <a target="_blank" href="https://www.permit.io/blog/401-vs-403-error-whats-the-difference">the difference between 401 and 403 error codes here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729498632123/aaf26b76-a89f-4e6b-9324-85d082b8061d.png" alt="Permit.io - endpoints" class="image--center mx-auto" width="1922" height="1144" loading="lazy"></p>
<p>For the user to view a list of products, assign them a viewer role using the command below:</p>
<pre><code class="lang-dockerfile">curl -X POST <span class="hljs-string">"http://localhost:8080/api/users/assign-role"</span> \
-H <span class="hljs-string">"Authorization: Bearer johndoe"</span> \
-H <span class="hljs-string">"Content-Type: application/json"</span> \
-d <span class="hljs-string">'viewer'</span>
</code></pre>
<p>You should see that user <code>johndoe</code> was assigned role viewer, as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729498710784/ebd789fd-ec52-4146-bb94-6f300edb9d7e.png" alt="Permit.io - users" class="image--center mx-auto" width="1884" height="822" loading="lazy"></p>
<p>Since a viewer can create a product, use the command below to create a product with user <code>johndoe</code>.</p>
<pre><code class="lang-dockerfile">curl -X POST <span class="hljs-string">"http://localhost:8080/api/products"</span> -H <span class="hljs-string">"Authorization: Bearer johndoe"</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -d <span class="hljs-string">'MacBook'</span>
</code></pre>
<p>You should see that a new product is created with ID 1 and that the user <code>johndoe</code> has been added as the vendor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729498758160/c14169f0-d720-465e-9bc1-f7096e6da31a.png" alt="Permit.io - API end points " class="image--center mx-auto" width="1922" height="1202" loading="lazy"></p>
<h4 id="heading-adding-reviews-to-products"><strong>Adding Reviews To Products</strong></h4>
<p>To add reviews to products, create another user called <code>jane</code>.</p>
<pre><code class="lang-dockerfile">curl -X POST <span class="hljs-string">"http://localhost:8080/api/users/signup"</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -d <span class="hljs-string">'jane'</span>
</code></pre>
<p>For the user to add a review to products, assign them a <code>viewer</code> role using the command below:</p>
<pre><code class="lang-dockerfile">curl -X POST <span class="hljs-string">"http://localhost:8080/api/users/assign-role"</span> \
-H <span class="hljs-string">"Authorization: Bearer jane"</span> \
-H <span class="hljs-string">"Content-Type: application/json"</span> \
-d <span class="hljs-string">'viewer'</span>
</code></pre>
<p>Then you can add a review to the product added by <code>johndoe</code> using the command below:</p>
<pre><code class="lang-dockerfile">curl -X POST <span class="hljs-string">"http://localhost:8080/api/products/1/review"</span> -H <span class="hljs-string">"Authorization: Bearer jane"</span> -H <span class="hljs-string">"Content-Type: application/json"</span> -d <span class="hljs-string">'The product was in good quality'</span>
</code></pre>
<p>Congratulations! You’ve completed the project for this tutorial.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>Now that you've learned how to implement fine-grained authorization in your Java and Spring Boot applications using <a target="_blank" href="http://Permit.io">Permit.io</a>, you might want to explore further.</p>
<p>Here are some valuable resources:</p>
<ul>
<li><p><a target="_blank" href="https://docs.permit.io/">Permit.io docs</a></p>
</li>
<li><p><a target="_blank" href="https://www.permit.io/blog/rbac-vs-abac">RBAC VS ABAC: Choosing the Right Authorization Policy Model</a></p>
</li>
</ul>
<h3 id="heading-before-we-end">Before We End</h3>
<p>I hope you found this tutorial insightful.</p>
<p>Here are some of my other recent blog posts that you might enjoy:</p>
<ul>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/learn-react-key-concepts/">Learn React – A Guide to the Key Concepts</a></p>
</li>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/neon-vs-supabase"><strong>Neon Postgres vs Supabase</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/nextjs-clerk-neon-fullstack-development/">Full Stack Development with Next.js, Clerk, and Neon Postgres</a></p>
</li>
</ul>
<p>For more tutorials on amazing developer tools, be sure to check out my blog <a target="_blank" href="https://www.devtoolsacademy.com/">DTA</a>.</p>
<p>Follow me on <a target="_blank" href="https://x.com/TheAnkurTyagi">Twitter</a> to get live updates on my other projects.</p>
<p>Happy coding.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Boost Your Productivity With Cross-Browser DevTools Features ]]>
                </title>
                <description>
                    <![CDATA[ Building cross-browser web applications can be a difficult task, as many browsers offer few debugging tools that often vary between them. Fortunately, today most browsers support modern standards and provide helpful features for developers. Major bro... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/cross-browser-devtools-features/</link>
                <guid isPermaLink="false">66ff217ffdbf645a22d41640</guid>
                
                    <category>
                        <![CDATA[ Browsers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Uma Victor ]]>
                </dc:creator>
                <pubDate>Thu, 03 Oct 2024 22:58:07 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/JySoEnr-eOg/upload/e3e1bb6fdf9c408f7506093587114629.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building cross-browser web applications can be a difficult task, as many browsers offer few debugging tools that often vary between them. Fortunately, today most browsers support modern standards and provide helpful features for developers.</p>
<p>Major browsers, such as Chrome, Firefox, and Edge, continuously update their developer tools, with each update potentially bringing new features to web developers. These new features increase the ease of use by adding intuitive UIs, more advanced debugging, and enhanced performance analysis tools.</p>
<p>Staying up to date with these changes lets you use your browser <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/What_are_browser_developer_tools">DevTools</a> to its fullest potential to make your workflow easier and speed up the delivery of your web applications.</p>
<h2 id="heading-table-of-content">Table of Content</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-use-scroll-into-view-in-devtools">How to Use Scroll Into View in DevTools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-console-shortcuts-in-devtools">How to Use Console Shortcuts in DevTools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-block-resource-request-for-website-testing-in-devtools">How to Block Resource Request for Website Testing in DevTools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-edit-and-resend-network-request-in-devtools">How to Edit and Resend Network Request in DevTools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-detect-unused-source-code-in-devtools">How to Detect Unused Source Code in DevTools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-enable-accessibility-tree-in-devtools">How to Enable Accessibility Tree in DevTools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<p>In this article, we’ll discover some good cross-browser DevTools features and discuss how to use them.</p>
<p>Let’s get started!</p>
<h2 id="heading-how-to-use-scroll-into-view-in-devtools">How to Use Scroll Into View in DevTools</h2>
<p>When debugging, there may be a lot of HTML nodes to skim through to find out where your issue is. Most of the time, when you find the node, you won’t see it until you scroll to where it is on the page.</p>
<p>The Scroll Into View feature easily brings the DOM node into the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/Viewport_concepts">viewport</a> by right-clicking and selecting Scroll Into view in Chrome, Firefox, and Edge.</p>
<p>This feature saves a lot of time when debugging CSS issues or wanting to verify the correct placement of elements on a page, ensuring you can quickly locate elements on the page through the HTML nodes without needing to scroll through many lines of content manually.</p>
<p>In the image below, we are trying to find a <code>h2</code> element that is nested inside several layers of other elements.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727896981592/d326d8f8-6b79-4c0f-b7b6-d58d2f90344b.jpeg" alt="scroll into view devtools option" class="image--center mx-auto" width="1790" height="925" loading="lazy"></p>
<p>In the image above, instead of scrolling through the entire page to spot the <code>h2</code> element, we right-clicked and used the Scroll Into View feature to instantly bring the <code>h2</code> element into view. We can expand on this feature to do other things with the element when we have scrolled to it. We can tweak the CSS properties in real time via the styles panel and even find and fix layout issues.</p>
<h2 id="heading-how-to-use-console-shortcuts-in-devtools">How to Use Console Shortcuts in DevTools</h2>
<p>There are lots of shortcuts that can be used in the Console that allows you, as the developer, to debug faster. One of them is the <code>$_</code> shortcut. This shortcut returns the value of the most recent <a target="_blank" href="https://en.wikipedia.org/wiki/Expression_\(computer_science\)">expression</a> evaluated by the Console. Let’s say, for example, we have a multiplier function:</p>
<p>In the image below, you can see how the <code>$_</code> shortcut is used as a special variable in the browser Console to store the most recently evaluated expression:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727897503037/54aa559d-1ac2-4cf2-95f9-60f4d3c73520.png" alt="$_ shortcut used in the Console" width="2388" height="1142" loading="lazy"></p>
<p>Without the <code>$_</code> shortcut, you would have to either retype the entire function call or you might store the result in a variable like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> result = multiply(<span class="hljs-number">5</span>)
result(<span class="hljs-number">4</span>) <span class="hljs-comment">// returns 20</span>
</code></pre>
<p>In the code above, the <code>multiply(5)</code> function has to return a function and assign the function to result, which is then called with <code>4</code> as the parameter <code>result(4)</code>.</p>
<p>I'm sure you can see how this already introduces some redundancy and extra steps that can be cumbersome when you're dealing with more complex operations or when doing multiple steps of calculations. This is where the <code>$_</code> shortcut shines. When we run the <code>multiply(5)</code> code in the Console, a function is returned and stored in the <code>$_</code> variable by the Console, which we can access using the <code>$_</code> shortcut.</p>
<p>Another shortcut is using <code>$0</code> to access nodes from the Console. <code>$0</code> can be used to access the currently selected node in the DOM tree from within the Console. As you inspect a webpage with DevTools, you often browse the DOM tree in the Elements panel to find the element that you're interested in. Once you click on an element in this panel, DevTools internally keeps track of this element, and it becomes the currently selected element.</p>
<p>The <code>$0</code> is a shortcut that refers to the currently selected element in the Console, so you can manipulate it directly in the Console without writing a query to select it again.</p>
<p>The screenshot below shows how we can use <code>$0</code> in the Console to access the selected node in the DOM tree and change the background color to whatever we want.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727897399339/97ac2466-75b6-44ea-91b7-e0dee47e5ada.jpeg" alt="console shortcuts" width="2011" height="979" loading="lazy"></p>
<p>From the image above, we started by inspecting the desired element we wanted in the elements panel. Now, instead of querying the element again using <code>document.querySelector('#element')</code>, you can just use <code>$0</code> to manipulate it directly like this:</p>
<pre><code class="lang-javascript">$<span class="hljs-number">0.</span>style.backgroundColor = <span class="hljs-string">'lightblue'</span>;
</code></pre>
<p>This code changes the background color of the selected <code>&lt;div&gt;</code> to a gentle light blue. What really makes <code>$0</code> useful in this case is that it allows you to refer directly to the exact element you chose in the DOM, making sure that you're working with the correct element, even in cases where elements are dynamically generated or deeply nested.</p>
<h2 id="heading-how-to-block-resource-request-for-website-testing-in-devtools">How to Block Resource Request for Website Testing in DevTools</h2>
<p>The Block Resource Request DevTools feature is an important feature for web developers to test how their websites behave when specific resources cannot be loaded.</p>
<p>This feature enables you to simulate situations where an image, JavaScript, CSS, or an entire <a target="_blank" href="https://en.wikipedia.org/wiki/Domain_name">domain</a> becomes unreachable, and you get to see how your webpage would behave in that situation.</p>
<p>Resources requested by the browser aren’t always guaranteed to be downloaded, which can lead to unexpected experiences for users of your website. You can block requests to a resource on Chrome, Firefox, and Edge and test how your site behaves.</p>
<p>On Chrome and Edge:</p>
<ul>
<li><p>On the Network panel, right-click on the resource you want to block and select Block request URL.</p>
</li>
<li><p>Refresh the website, and the blocked resource won’t be downloaded and won’t affect the webpage.</p>
</li>
</ul>
<p>In the image below, we are using the Block request URL option in the Network tab to block a CSS request and see how the web page will look like if the selected CSS file fails to load.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727897541622/739a0a25-09bd-4e25-86c0-e5d1a0264131.png" alt="blocking requests" width="1391" height="710" loading="lazy"></p>
<p>From the image above, we can see all the network requests made by the webpage including requests for images, CSS files, JavaScript files, and so on. In my case I've filtered for just CSS files to be shown only.</p>
<p>From here, you can right-click on the CSS file in the Network panel and select Block request URL. This action will prevent the browser from loading the specific CSS file the next time the page is refreshed.</p>
<p>By blocking the request, we can watch out for weird behaviors and also measure how the absence of the blocked resource can affect the page load time and performance.</p>
<p>On Firefox:</p>
<ul>
<li><p>On the Network panel, right-click on the resource you want to block and select Block URL.</p>
</li>
<li><p>Reload the page.</p>
</li>
</ul>
<p>I’ve used this to test how my site behaves when I don’t load a particular JavaScript file. This feature can help developers debug issues that can arise when users disable JavaScript.</p>
<h2 id="heading-how-to-edit-and-resend-network-request-in-devtools">How to Edit and Resend Network Request in DevTools</h2>
<p>One of the coolest DevTools features is the ability to edit and resend <a target="_blank" href="https://en.wikipedia.org/wiki/Request%E2%80%93response">network requests</a> right in the browser. This feature can be really useful for debugging a network request problem. For example, scenarios where you’d want to see how changes to request parameters, headers, or body affect the response from the server, without having to make any changes to the frontend code or restarting the entire request process.</p>
<p>When making a network request, requests made to a backend service might fail or not respond with the intended data. It’s a pain to have to reload the whole page to retry the request, which is why the Edit and Resend feature is helpful.</p>
<p>In the Edge and Firefox browser, you can edit and resend a network request by right-clicking on the request you want to edit or resend and select Edit and Resend, just like in the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727897585816/68169a1f-987c-457a-a8ee-4335c2d88793.png" alt="Edit and replay" width="1020" height="649" loading="lazy"></p>
<p>In the image above, we tried to log into a website. When a user submits their credentials, the form sends a <code>POST</code> request to an API endpoint, <code>/auth/login</code>, with the user’s username and password.</p>
<p>Sometimes, the server may return a <code>400</code> Bad Request error, and for us to debug the error and find out why, we have to retry the request. We don't want to keep on filling out the form, so we use the Edit and Resend feature as shown below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727897620865/48eeb4fa-4bc4-44fb-b274-a23c5b037e1d.png" alt="modify xhr requests" width="2036" height="1114" loading="lazy"></p>
<p>The image above is the network Console or a sidebar that will open when you click on Edit and Resend, showing the details of the request. Here, you can edit:</p>
<ul>
<li><p>URL: If necessary, you can modify the URL or add query parameters.</p>
</li>
<li><p>Headers: You might notice a missing or incorrect Content-Type header, which you can fix here.</p>
</li>
<li><p>Body: This is where you can adjust the payload, such as correcting the username or password fields.</p>
</li>
</ul>
<p>In Chrome browsers, the edit and resend feature only works for <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XHR</a> requests, and you can use it by right-clicking on the request and selecting replay.</p>
<h2 id="heading-how-to-detect-unused-source-code-in-devtools">How to Detect Unused Source Code in DevTools</h2>
<p>The Coverage tool in DevTools enables developers to spot areas of their JavaScript and CSS files that remain unused during the loading and interaction stages of a webpage. It's an important feature for enhancing web performance by reducing file sizes and eliminating unnecessary code for faster page load times and a better user experience.</p>
<p>Removing unused JS and CSS code is a great way to save your users bandwidth. The Coverage tool allows you to find the unused code in your source code, and either remove it or defer it till the piece of code is needed.</p>
<p>On Chrome and Edge:</p>
<ul>
<li><p>In DevTools, press <code>Ctrl/cmd+Shift+P</code>, type coverage and select Start instrumenting coverage, refresh the page, then hit enter.</p>
</li>
<li><p>You’ll see a table of JS and CSS files with an unused byte column.</p>
</li>
<li><p>Click on any of the files to open it. The line by the side indicates which section of code isn’t unused in red.</p>
</li>
</ul>
<p>In the image below, we are identifying the unused CSS code to potentially remove or defer the loading of the code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727898041299/ef5feb00-38db-4893-90db-275721c26399.png" alt="Delete unused code" width="1664" height="1430" loading="lazy"></p>
<p>From the image above, after the recording is complete, the Coverage tool will display a list of CSS and JavaScript files loaded by the page, along with detailed metrics:</p>
<ul>
<li><p>Total Bytes: The size of the file.</p>
</li>
<li><p>Unused Bytes: The number of bytes in the file that were not used.</p>
</li>
<li><p>Usage Visualization: A visual bar representing the proportion of used versus unused code.</p>
</li>
</ul>
<p>On Safari: In the Sources panel, open the left navigation sidebar and click on any JS file. At the top right of the toolbar, click on the coverage icon <code>c</code> and refresh your page. You'd be able to see that the sections of code not executed are grayed out.</p>
<h2 id="heading-how-to-enable-accessibility-tree-in-devtools">How to Enable Accessibility Tree in DevTools</h2>
<p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Accessibility_tree">accessibility tree</a> is similar to the element DOM tree and is used by assistive technologies such as screen readers to read web content. Developers can use this feature to debug accessibility issues on their websites. Chromium browsers use <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/accessibilityFeatures">Chrome’s accessibility API</a> to make this possible, while Firefox has its own accessibility tool.</p>
<p>On Chrome and Edge:</p>
<ul>
<li><p>On the settings page, select the Experiments tab.</p>
</li>
<li><p>Check the box for the Enable full accessibility tree view option in the Elements panel.</p>
</li>
<li><p>Refresh DevTools and go to the Elements tool.</p>
</li>
<li><p>In the top-right corner of the element view, click Switch to DOM Tree view.</p>
</li>
</ul>
<p>For example, in the image below, we are checking if the links and buttons on our website are recognized correctly and accessible to users who rely on screen readers:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727898388525/1a73f242-5ccd-48ea-b167-440205285ba4.png" alt="Enable accessibility tree" width="2294" height="1430" loading="lazy"></p>
<p>With the accessibility tree enabled, you can see a simplified version of the DOM tree, focused on elements relevant for accessibility. When you select an element on the accessibility tree to view its properties, the tree displays the element’s role, name, and other important attributes, such as aria-label if they are present.</p>
<p>You’ll also see if the element is focusable and what its computed accessibility properties are.</p>
<p>This helps a great deal because if the element isn’t appearing correctly in the accessibility tree or is missing essential attributes, you may need to adjust your HTML or <a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/aria/">ARIA</a> attributes to improve accessibility.</p>
<p>On Firefox:</p>
<ul>
<li><p>In the Firefox DevTools, click on the accessibility tab and expand the document node.</p>
</li>
<li><p>You can click on different nodes to view their properties.</p>
</li>
<li><p>Accessibility issues for nodes will be displayed on the Checks tab.</p>
</li>
</ul>
<h2 id="heading-summary">Summary</h2>
<p>In a nutshell, staying current with the latest cross-browser DevTools features will save you time as a web developer. This post touches on element inspection tips, some Console shortcuts to make your debugging process easier, and some useful tips for network monitoring.</p>
<p>Hopefully, you will keep exploring and using more DevTools features to improve your developer experience.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How I Built a Custom Video Conferencing App with Stream and Next.js ]]>
                </title>
                <description>
                    <![CDATA[ Building full-stack apps can be tough. You have to think about frontend, APIs, databases, auth – plus you have to know how all of these things work together. And building a project like a video conferencing app from scratch can feel even more overwhe... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-built-a-custom-video-conferencing-app-with-stream-and-nextjs/</link>
                <guid isPermaLink="false">66fd86ff9cea0a9dc9177283</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Startups ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 17:46:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727433361539/498f0742-2ff1-4762-b268-2c25eb22017e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building full-stack apps can be tough. You have to think about frontend, APIs, databases, auth – plus you have to know how all of these things work together.</p>
<p>And building a project like a video conferencing app from scratch can feel even more overwhelming, especially with the complexities of managing video streams, user auth, and real-time interactions.</p>
<p>But what if I told you there’s an easier way to do this – one that lets you build your video conferencing app in a fraction of the time?</p>
<p>In this article, I’ll show you how I built a video conferencing app using <a target="_blank" href="https://getstream.io/">Stream</a> and Clerk in Next.js.</p>
<p>Here is the <a target="_blank" href="https://github.com/tyaga001/facetime-on-stream">source code</a> (remember to give it a star ⭐).</p>
<p>Before we start, let me tell you why I wrote this tutorial.</p>
<p>I’m a Software Engineer who cares about writing and I <strong>love</strong> to <strong>code</strong>, <strong>design</strong>, <strong>develop</strong>, and then <strong>teach</strong> people.</p>
<p>I've been using open-source projects, products, and services for a while now, and contributing to many of them to improve them how I can. Last month I built an open-source blog for “awesome developer tools“ called - <a target="_blank" href="https://www.devtoolsacademy.com/">devtoolsacademy</a></p>
<p><a target="_blank" href="https://www.devtoolsacademy.com/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727430858395/70ffbec4-69ab-4f31-a9cb-02b44066ac6b.png" alt="devtoolsacademy" class="image--center mx-auto" width="3072" height="1830" loading="lazy"></a></p>
<p>This article is about sharing the experience I’ve had using yet another awesome developer tool.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-stream">What is Stream?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-app-interface-with-nextjs">How to Build the App Interface with Next.js</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-the-create-link-modal">The Create Link Modal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-instant-meeting-modal">The Instant Meeting Modal</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-join-meeting-modal">The Join Meeting Modal</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-authenticate-users-with-clerk">How to Authenticate Users with Clerk</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-stream-in-a-nextjs-app">How to Set Up Stream in a Next.js app</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-and-join-calls-with-stream">How to Create and Join Calls with Stream</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-creating-and-scheduling-calls">Creating and Scheduling calls</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-joining-calls-and-the-meeting-page">Joining calls and the Meeting Page</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-retrieving-upcoming-calls">Retrieving Upcoming Calls</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ul>
<h2 id="heading-what-is-stream">What is Stream?</h2>
<p><a target="_blank" href="https://getstream.io/">Stream</a> is an open-source cloud-based platform that provides APIs and SDKs for building scalable and feature-rich real-time applications. It offers pre-built UI components for creating enterprise-grade software apps with features like chat, video, audio, and activity feeds.</p>
<p><a target="_blank" href="https://getstream.io"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726475007023/be45aa40-7794-434a-8f5d-f4b637d97fd8.png" alt="What is Stream" class="image--center mx-auto" width="3114" height="1778" loading="lazy"></a></p>
<p>Here's how I'll use <code>Stream</code> while building the app:</p>
<ul>
<li><p>Set up real-time video and audio calls</p>
</li>
<li><p>Use Stream's UI components to quickly build the interface</p>
</li>
<li><p>Implement key features like <code>video</code> and <code>audio</code> calls</p>
</li>
<li><p><code>Call Types</code> – I'll implement instant meetings and pre-scheduled calls using Stream</p>
</li>
<li><p>Leverage Stream's call and participant objects to manage <code>call state</code></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To fully understand the tutorial, you need to have a basic understanding of <a target="_blank" href="https://www.freecodecamp.org/news/learn-react-key-concepts/">React</a> and <a target="_blank" href="https://theankurtyagi.com/next-js/">Next.js</a>. You’ll also need the following:</p>
<ul>
<li><p><a target="_blank" href="https://getstream.io/chat/docs/sdk/react/">Stream React SDK</a> - provides pre-built UI components for adding video call features quickly.</p>
</li>
<li><p><a target="_blank" href="https://github.com/GetStream/stream-node">Stream Node.js SDK</a> - for managing server-side interactions and keeping Stream's state in sync.</p>
</li>
<li><p><a target="_blank" href="https://clerk.com/">Clerk</a> - a comprehensive user management platform to handle authentication effortlessly.</p>
</li>
<li><p><a target="_blank" href="https://headlessui.com/">Headless UI</a> - provides accessible UI components for building user-friendly applications.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/react-copy-to-clipboard">React Copy-to-Clipboard</a> - allows users to easily copy meeting links within the app.</p>
</li>
<li><p><a target="_blank" href="https://react-icons.github.io/react-icons/">React Icons</a> - offers a library of easily integrated icons.</p>
</li>
</ul>
<h2 id="heading-how-to-build-the-app-interface-with-nextjs">How to Build the App Interface with Next.js</h2>
<p>In this section, I'll guide you through creating the user interface for the video-conferencing app. The interface will allow users to easily create, join, and schedule meetings, as well as view their upcoming meetings.</p>
<p>First, let’s create a Next.js TypeScript project by running the code snippet below:</p>
<pre><code class="lang-bash">npx create-next-app facetime-app
</code></pre>
<p>Then install the following packages:</p>
<ul>
<li><p><a target="_blank" href="https://react-icons.github.io/react-icons/">React icons</a> - a popular React icons package</p>
</li>
<li><p><a target="_blank" href="https://headlessui.com/">Headless UI</a> - provides a set of accessible UI components</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/react-copy-to-clipboard">React-copy-to-clipboard</a> - a lightweight package that enables us to copy meeting links.</p>
</li>
</ul>
<pre><code class="lang-bash">npm install react-icons @headlessui/react react-copy-to-clipboard
</code></pre>
<p>Copy the code snippet below into the <code>app/page.tsx</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { FaLink, FaVideo } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> InstantMeeting <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/InstantMeeting"</span>;
<span class="hljs-keyword">import</span> UpcomingMeeting <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/UpcomingMeeting"</span>;
<span class="hljs-keyword">import</span> CreateLink <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/CreateLink"</span>;
<span class="hljs-keyword">import</span> JoinMeeting <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/modals/JoinMeeting"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [startInstantMeeting, setStartInstantMeeting] =
        useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [joinMeeting, setJoinMeeting] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [showUpcomingMeetings, setShowUpcomingMeetings] =
        useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [showCreateLink, setShowCreateLink] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;button
                className=<span class="hljs-string">' top-5 right-5 text-sm fixed bg-green-500 px-2 w-[150px] hover:bg-green-600 py-3 flex flex-col items-center text-white rounded-md shadow-sm cursor-pointer z-10'</span>
                onClick={<span class="hljs-function">() =&gt;</span> setJoinMeeting(<span class="hljs-literal">true</span>)}
            &gt;
                &lt;FaVideo className=<span class="hljs-string">'mb-[3px] text-white'</span> /&gt;
                Join FaceTime
            &lt;/button&gt;

            &lt;main className=<span class="hljs-string">'w-full h-screen flex flex-col items-center justify-center'</span>&gt;
                &lt;h1 className=<span class="hljs-string">'font-bold text-2xl text-center'</span>&gt;FaceTime&lt;/h1&gt;
                &lt;div className=<span class="hljs-string">'flex flex-col'</span>&gt;
                    &lt;button
                        className=<span class="hljs-string">'text-green-500 underline text-sm text-center cursor-pointer'</span>
                        onClick={<span class="hljs-function">() =&gt;</span> setShowUpcomingMeetings(<span class="hljs-literal">true</span>)}
                    &gt;
                        Upcoming FaceTime
                    &lt;/button&gt;
                &lt;/div&gt;

                &lt;div className=<span class="hljs-string">'flex items-center justify-center space-x-4 mt-6'</span>&gt;
                    &lt;button
                        className=<span class="hljs-string">'bg-gray-500 px-4 w-[200px] py-3 flex flex-col items-center hover:bg-gray-600 text-white rounded-md shadow-sm'</span>
                        onClick={<span class="hljs-function">() =&gt;</span> setShowCreateLink(<span class="hljs-literal">true</span>)}
                    &gt;
                        &lt;FaLink className=<span class="hljs-string">'mb-[3px] text-gray-300'</span> /&gt;
                        Create link
                    &lt;/button&gt;
                    &lt;button
                        className=<span class="hljs-string">'bg-green-500 px-4 w-[200px] hover:bg-green-600 py-3 flex flex-col items-center text-white rounded-md shadow-sm'</span>
                        onClick={<span class="hljs-function">() =&gt;</span> setStartInstantMeeting(<span class="hljs-literal">true</span>)}
                    &gt;
                        &lt;FaVideo className=<span class="hljs-string">'mb-[3px] text-white'</span> /&gt;
                        New FaceTime
                    &lt;/button&gt;
                &lt;/div&gt;
            &lt;/main&gt;

            {startInstantMeeting &amp;&amp; (
                &lt;InstantMeeting
                    enable={startInstantMeeting}
                    setEnable={setStartInstantMeeting}
                /&gt;
            )}
            {showUpcomingMeetings &amp;&amp; (
                &lt;UpcomingMeeting
                    enable={showUpcomingMeetings}
                    setEnable={setShowUpcomingMeetings}
                /&gt;
            )}
            {showCreateLink &amp;&amp; (
                &lt;CreateLink enable={showCreateLink} setEnable={setShowCreateLink} /&gt;
            )}
            {joinMeeting &amp;&amp; (
                &lt;JoinMeeting enable={joinMeeting} setEnable={setJoinMeeting} /&gt;
            )}
        &lt;/&gt;
    );
}
</code></pre>
<p>The code snippet above renders multiple buttons that allow users to perform actions like joining, creating, and scheduling a call. Each button opens a modal that prompts the user to provide additional details specific to the action they are performing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726481911712/286f7349-0d95-419d-97e5-193371307e13.png" alt="facetime-app-home-page" class="image--center mx-auto" width="3110" height="1818" loading="lazy"></p>
<p>Next, let’s create a <code>modals</code> folder within the Next.js app directory and add the following components to the <code>modals</code> folder:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> app
mkdir modals &amp;&amp; <span class="hljs-built_in">cd</span> modals
touch CreateLink.tsx InstantMeeting.tsx JoinMeeting.tsx UpcomingMeeting.tsx
</code></pre>
<p>The <code>CreateLink</code> modal allows users to provide a description and schedule a time for the call. The <code>InstantMeeting</code> modal lets users start an instant meeting by providing a call description. The <code>JoinMeeting</code> modal enables users to enter a call link and join a meeting. And the <code>UpcomingMeeting</code> modal displays all scheduled upcoming calls.</p>
<h3 id="heading-the-create-link-modal">The Create Link Modal</h3>
<p>Copy the code snippet below into the <code>CreateLink</code> modal:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {
    Dialog,
    DialogTitle,
    DialogPanel,
    Transition,
    Description,
    TransitionChild,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@headlessui/react"</span>;
<span class="hljs-keyword">import</span> { Fragment, SetStateAction, useState, Dispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> CopyToClipboard <span class="hljs-keyword">from</span> <span class="hljs-string">"react-copy-to-clipboard"</span>;
<span class="hljs-keyword">import</span> { FaCopy } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">CreateLink</span>(<span class="hljs-params">{ enable, setEnable }: Props</span>) </span>{
    <span class="hljs-keyword">const</span> [showMeetingLink, setShowMeetingLink] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [facetimeLink, setFacetimeLink] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> setEnable(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Transition appear show={enable} <span class="hljs-keyword">as</span>={Fragment}&gt;
                &lt;Dialog <span class="hljs-keyword">as</span>=<span class="hljs-string">'div'</span> className=<span class="hljs-string">'relative z-10'</span> onClose={closeModal}&gt;
                    &lt;TransitionChild
                        <span class="hljs-keyword">as</span>={Fragment}
                        enter=<span class="hljs-string">'ease-out duration-300'</span>
                        enterFrom=<span class="hljs-string">'opacity-0'</span>
                        enterTo=<span class="hljs-string">'opacity-100'</span>
                        leave=<span class="hljs-string">'ease-in duration-200'</span>
                        leaveFrom=<span class="hljs-string">'opacity-100'</span>
                        leaveTo=<span class="hljs-string">'opacity-0'</span>
                    &gt;
                        &lt;div className=<span class="hljs-string">'fixed inset-0 bg-black/75'</span> /&gt;
                    &lt;/TransitionChild&gt;

                    &lt;div className=<span class="hljs-string">'fixed inset-0 overflow-y-auto'</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex min-h-full items-center justify-center p-4 text-center'</span>&gt;
                            &lt;TransitionChild
                                <span class="hljs-keyword">as</span>={Fragment}
                                enter=<span class="hljs-string">'ease-out duration-300'</span>
                                enterFrom=<span class="hljs-string">'opacity-0 scale-95'</span>
                                enterTo=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leave=<span class="hljs-string">'ease-in duration-200'</span>
                                leaveFrom=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leaveTo=<span class="hljs-string">'opacity-0 scale-95'</span>
                            &gt;
                                &lt;DialogPanel className=<span class="hljs-string">'w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 align-middle shadow-xl transition-all text-center'</span>&gt;
                                    {showMeetingLink ? (
                                        &lt;MeetingLink facetimeLink={facetimeLink} /&gt;
                                    ) : (
                                        &lt;MeetingForm
                                            setShowMeetingLink={setShowMeetingLink}
                                            setFacetimeLink={setFacetimeLink}
                                        /&gt;
                                    )}
                                &lt;/DialogPanel&gt;
                            &lt;/TransitionChild&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/Dialog&gt;
            &lt;/Transition&gt;
        &lt;/&gt;
    );
}
</code></pre>
<p>The code snippet above renders a form that allows users to input a description and select a time to schedule a call. Once the call is created, the generated link is displayed and can be copied.</p>
<p>Finally, add the <code>MeetingForm</code> and <code>MeetingLink</code> components below the <code>CreateLink</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingForm = <span class="hljs-function">(<span class="hljs-params">{
    setShowMeetingLink,
    setFacetimeLink,
}: {
    setShowMeetingLink: React.Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">boolean</span>&gt;&gt;;
    setFacetimeLink: Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">string</span>&gt;&gt;;
}</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [description, setDescription] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [dateTime, setDateTime] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleStartMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        e.preventDefault();
        <span class="hljs-built_in">console</span>.log({ description, dateTime });
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Schedule a FaceTime
            &lt;/DialogTitle&gt;

            &lt;Description className=<span class="hljs-string">'text-xs opacity-40 mb-4'</span>&gt;
                Schedule a FaceTime meeting <span class="hljs-keyword">with</span> your cliq
            &lt;/Description&gt;

            &lt;form className=<span class="hljs-string">'w-full'</span> onSubmit={handleStartMeeting}&gt;
                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'description'</span>
                &gt;
                    Meeting Description
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
                    name=<span class="hljs-string">'description'</span>
                    id=<span class="hljs-string">'description'</span>
                    value={description}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDescription(e.target.value)}
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    required
                    placeholder=<span class="hljs-string">'Enter a description for the meeting'</span>
                /&gt;

                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'date'</span>
                &gt;
                    <span class="hljs-built_in">Date</span> and Time
                &lt;/label&gt;

                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'datetime-local'</span>
                    id=<span class="hljs-string">'date'</span>
                    name=<span class="hljs-string">'date'</span>
                    required
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    value={dateTime}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDateTime(e.target.value)}
                /&gt;

                &lt;button className=<span class="hljs-string">'w-full bg-green-600 text-white py-3 rounded mt-4'</span>&gt;
                    Create FaceTime
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/&gt;
    );
};
</code></pre>
<p>The <code>MeetingForm</code> component accepts the call description and scheduled time, while the <code>MeetingLink</code> component displays the generated call link and allows users to copy it.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingLink = <span class="hljs-function">(<span class="hljs-params">{ facetimeLink }: { facetimeLink: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [copied, setCopied] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> handleCopy = <span class="hljs-function">() =&gt;</span> setCopied(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Copy FaceTime Link
            &lt;/DialogTitle&gt;

            &lt;Description className=<span class="hljs-string">'text-xs opacity-40 mb-4'</span>&gt;
                You can share the facetime link <span class="hljs-keyword">with</span> your participants
            &lt;/Description&gt;

            &lt;div className=<span class="hljs-string">'bg-gray-100 p-4 rounded flex items-center justify-between'</span>&gt;
                &lt;p className=<span class="hljs-string">'text-xs text-gray-500'</span>&gt;
                    {<span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_FACETIME_HOST}</span>/<span class="hljs-subst">${facetimeLink}</span>`</span>}
                &lt;/p&gt;

                &lt;CopyToClipboard
                    onCopy={handleCopy}
                    text={<span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_FACETIME_HOST}</span>/<span class="hljs-subst">${facetimeLink}</span>`</span>}
                &gt;
                    &lt;FaCopy className=<span class="hljs-string">'text-green-600 text-lg cursor-pointer'</span> /&gt;
                &lt;/CopyToClipboard&gt;
            &lt;/div&gt;

            {copied &amp;&amp; (
                &lt;p className=<span class="hljs-string">'text-red-600 text-xs mt-2'</span>&gt;Link copied to clipboard&lt;/p&gt;
            )}
        &lt;/&gt;
    );
};
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726482044698/0cb22caa-3e5a-4f01-9fa2-25c7ce77b08a.png" alt="facetime-app-schedule-popup" class="image--center mx-auto" width="3098" height="1828" loading="lazy"></p>
<h3 id="heading-the-instant-meeting-modal">The Instant Meeting Modal</h3>
<p>Copy the following code snippet into the <code>InstantMeeting</code> modal:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {
    Dialog,
    DialogTitle,
    DialogPanel,
    Transition,
    Description,
    TransitionChild,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@headlessui/react"</span>;
<span class="hljs-keyword">import</span> { FaCopy } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/fa"</span>;
<span class="hljs-keyword">import</span> CopyToClipboard <span class="hljs-keyword">from</span> <span class="hljs-string">"react-copy-to-clipboard"</span>;
<span class="hljs-keyword">import</span> { Fragment, useState, Dispatch, SetStateAction } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">InstantMeeting</span>(<span class="hljs-params">{ enable, setEnable }: Props</span>) </span>{
    <span class="hljs-keyword">const</span> [showMeetingLink, setShowMeetingLink] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [facetimeLink, setFacetimeLink] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> setEnable(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Transition appear show={enable} <span class="hljs-keyword">as</span>={Fragment}&gt;
                &lt;Dialog <span class="hljs-keyword">as</span>=<span class="hljs-string">'div'</span> className=<span class="hljs-string">'relative z-10'</span> onClose={closeModal}&gt;
                    &lt;TransitionChild
                        <span class="hljs-keyword">as</span>={Fragment}
                        enter=<span class="hljs-string">'ease-out duration-300'</span>
                        enterFrom=<span class="hljs-string">'opacity-0'</span>
                        enterTo=<span class="hljs-string">'opacity-100'</span>
                        leave=<span class="hljs-string">'ease-in duration-200'</span>
                        leaveFrom=<span class="hljs-string">'opacity-100'</span>
                        leaveTo=<span class="hljs-string">'opacity-0'</span>
                    &gt;
                        &lt;div className=<span class="hljs-string">'fixed inset-0 bg-black/75'</span> /&gt;
                    &lt;/TransitionChild&gt;

                    &lt;div className=<span class="hljs-string">'fixed inset-0 overflow-y-auto'</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex min-h-full items-center justify-center p-4 text-center'</span>&gt;
                            &lt;TransitionChild
                                <span class="hljs-keyword">as</span>={Fragment}
                                enter=<span class="hljs-string">'ease-out duration-300'</span>
                                enterFrom=<span class="hljs-string">'opacity-0 scale-95'</span>
                                enterTo=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leave=<span class="hljs-string">'ease-in duration-200'</span>
                                leaveFrom=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leaveTo=<span class="hljs-string">'opacity-0 scale-95'</span>
                            &gt;
                                &lt;DialogPanel className=<span class="hljs-string">'w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 align-middle shadow-xl transition-all text-center'</span>&gt;
                                    {showMeetingLink ? (
                                        &lt;MeetingLink facetimeLink={facetimeLink} /&gt;
                                    ) : (
                                        &lt;MeetingForm
                                            setShowMeetingLink={setShowMeetingLink}
                                            setFacetimeLink={setFacetimeLink}
                                        /&gt;
                                    )}
                                &lt;/DialogPanel&gt;
                            &lt;/TransitionChild&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/Dialog&gt;
            &lt;/Transition&gt;
        &lt;/&gt;
    );
}
</code></pre>
<p>The code snippet above renders a form that allows users to provide a call description. Once the call is created, the link is generated and available to be copied before starting the call.</p>
<p>Finally, add the <code>MeetingForm</code> and <code>MeetingLink</code> components below the <code>CreateLink</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingForm = <span class="hljs-function">(<span class="hljs-params">{
    setShowMeetingLink,
    setFacetimeLink,
}: {
    setShowMeetingLink: Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">boolean</span>&gt;&gt;;
    setFacetimeLink: Dispatch&lt;SetStateAction&lt;<span class="hljs-built_in">string</span>&gt;&gt;;
}</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [description, setDescription] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> handleStartMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        e.preventDefault();
        <span class="hljs-built_in">console</span>.log({ description });
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Create Instant FaceTime
            &lt;/DialogTitle&gt;

            &lt;Description className=<span class="hljs-string">'text-xs opacity-40 mb-4'</span>&gt;
                You can start a <span class="hljs-keyword">new</span> FaceTime instantly.
            &lt;/Description&gt;

            &lt;form className=<span class="hljs-string">'w-full'</span> onSubmit={handleStartMeeting}&gt;
                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'description'</span>
                &gt;
                    Meeting Description
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
                    name=<span class="hljs-string">'description'</span>
                    id=<span class="hljs-string">'description'</span>
                    value={description}
                    required
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setDescription(e.target.value)}
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    placeholder=<span class="hljs-string">'Enter a description for the meeting'</span>
                /&gt;

                &lt;button className=<span class="hljs-string">'w-full bg-green-600 text-white py-3 rounded mt-4'</span>&gt;
                    Proceed
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/&gt;
    );
};
</code></pre>
<p>The <code>MeetingForm</code> component accepts the call description, while the <code>MeetingLink</code> component displays the generated call link and allows users to copy it before starting the call.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726482110082/638609aa-e0ae-4cc4-b520-2050966180b4.png" alt="facetime-app-create-instant-facetime" class="image--center mx-auto" width="3098" height="1792" loading="lazy"></p>
<h3 id="heading-the-join-meeting-modal">The Join Meeting Modal</h3>
<p>Copy the code snippet below into the <code>JoinMeeting.tsx</code> file. It renders a form that accepts the call link and redirects users to the call page.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> {
    Dialog,
    DialogTitle,
    DialogPanel,
    Transition,
    TransitionChild,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@headlessui/react"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { Fragment, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">JoinMeeting</span>(<span class="hljs-params">{ enable, setEnable }: Props</span>) </span>{
    <span class="hljs-keyword">const</span> closeModal = <span class="hljs-function">() =&gt;</span> setEnable(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Transition appear show={enable} <span class="hljs-keyword">as</span>={Fragment}&gt;
                &lt;Dialog <span class="hljs-keyword">as</span>=<span class="hljs-string">'div'</span> className=<span class="hljs-string">'relative z-10'</span> onClose={closeModal}&gt;
                    &lt;TransitionChild
                        <span class="hljs-keyword">as</span>={Fragment}
                        enter=<span class="hljs-string">'ease-out duration-300'</span>
                        enterFrom=<span class="hljs-string">'opacity-0'</span>
                        enterTo=<span class="hljs-string">'opacity-100'</span>
                        leave=<span class="hljs-string">'ease-in duration-200'</span>
                        leaveFrom=<span class="hljs-string">'opacity-100'</span>
                        leaveTo=<span class="hljs-string">'opacity-0'</span>
                    &gt;
                        &lt;div className=<span class="hljs-string">'fixed inset-0 bg-black/75'</span> /&gt;
                    &lt;/TransitionChild&gt;

                    &lt;div className=<span class="hljs-string">'fixed inset-0 overflow-y-auto'</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex min-h-full items-center justify-center p-4 text-center'</span>&gt;
                            &lt;TransitionChild
                                <span class="hljs-keyword">as</span>={Fragment}
                                enter=<span class="hljs-string">'ease-out duration-300'</span>
                                enterFrom=<span class="hljs-string">'opacity-0 scale-95'</span>
                                enterTo=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leave=<span class="hljs-string">'ease-in duration-200'</span>
                                leaveFrom=<span class="hljs-string">'opacity-100 scale-100'</span>
                                leaveTo=<span class="hljs-string">'opacity-0 scale-95'</span>
                            &gt;
                                &lt;DialogPanel className=<span class="hljs-string">'w-full max-w-2xl transform overflow-hidden rounded-2xl bg-white p-6 align-middle shadow-xl transition-all text-center'</span>&gt;
                                    &lt;CallLinkForm /&gt;
                                &lt;/DialogPanel&gt;
                            &lt;/TransitionChild&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/Dialog&gt;
            &lt;/Transition&gt;
        &lt;/&gt;
    );
}
</code></pre>
<p>Add the <code>CallLinkForm</code> below the <code>JoinMeeting</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> CallLinkForm = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [link, setLink] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> router = useRouter();

    <span class="hljs-keyword">const</span> handleJoinMeeting = <span class="hljs-function">(<span class="hljs-params">e: React.FormEvent&lt;HTMLFormElement&gt;</span>) =&gt;</span> {
        e.preventDefault();
        router.push(<span class="hljs-string">`<span class="hljs-subst">${link}</span>`</span>);
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;DialogTitle
                <span class="hljs-keyword">as</span>=<span class="hljs-string">'h3'</span>
                className=<span class="hljs-string">'text-lg font-bold leading-6 text-green-600'</span>
            &gt;
                Join FaceTime
            &lt;/DialogTitle&gt;

            &lt;form className=<span class="hljs-string">'w-full'</span> onSubmit={handleJoinMeeting}&gt;
                &lt;label
                    className=<span class="hljs-string">'block text-left text-sm font-medium text-gray-700'</span>
                    htmlFor=<span class="hljs-string">'link'</span>
                &gt;
                    Enter the FaceTime link
                &lt;/label&gt;
                &lt;input
                    <span class="hljs-keyword">type</span>=<span class="hljs-string">'url'</span>
                    name=<span class="hljs-string">'link'</span>
                    id=<span class="hljs-string">'link'</span>
                    value={link}
                    onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setLink(e.target.value)}
                    className=<span class="hljs-string">'mt-1 block w-full text-sm py-3 px-4 border-gray-200 border-[1px] rounded mb-3'</span>
                    placeholder=<span class="hljs-string">'Enter the FaceTime link'</span>
                /&gt;

                &lt;button className=<span class="hljs-string">'w-full bg-green-600 text-white py-3 rounded mt-4'</span>&gt;
                    Join now
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/&gt;
    );
};
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726482173301/09881faa-54f8-4293-a186-b608ef5a0e05.png" alt="facetime-app-join-popup" class="image--center mx-auto" width="3104" height="1788" loading="lazy"></p>
<p>Congratulations! You’ve completed the app’s interface.</p>
<h2 id="heading-how-to-authenticate-users-with-clerk">How to Authenticate Users with Clerk</h2>
<p><a target="_blank" href="https://clerk.com/">Clerk</a> is a user management platform that enables you to add auth to web apps.</p>
<p>You can install the <a target="_blank" href="https://clerk.com/docs/quickstarts/nextjs">Clerk Next.js SDK</a> by running the following code snippet in your terminal:</p>
<pre><code class="lang-bash">npm install @clerk/nextjs
</code></pre>
<p>Create a <code>middleware.ts</code> file within the Next.js <code>src</code> folder and copy the code snippet below into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { clerkMiddleware, createRouteMatcher } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs/server"</span>;

<span class="hljs-keyword">const</span> protectedRoutes = createRouteMatcher([
    <span class="hljs-string">"/facetime(.*)"</span>,
    <span class="hljs-string">"/dashboard"</span>,
    <span class="hljs-string">"/"</span>,
]);

<span class="hljs-comment">//👇🏻 protects the route</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> clerkMiddleware(<span class="hljs-function">(<span class="hljs-params">auth, req</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (protectedRoutes(req)) {
        auth().protect();
    }
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
    matcher: [<span class="hljs-string">"/((?!.*\\\\..*|_next).*)"</span>, <span class="hljs-string">"/"</span>, <span class="hljs-string">"/(api|trpc)(.*)"</span>],
};
</code></pre>
<p>The <code>createRouteMatcher</code> function accepts an array containing routes to be protected from unauthenticated users and the <code>clerkMiddleware()</code> function ensures the routes are protected.</p>
<p>Next, import the following Clerk components into the <code>app/layout.tsx</code> file and update the <code>RootLayout</code> function as shown below:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {
    ClerkProvider,
    SignInButton,
    SignedIn,
    SignedOut,
    UserButton,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
    children,
}: {
    children: React.ReactNode;
}</span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;ClerkProvider&gt;
            &lt;html lang=<span class="hljs-string">'en'</span>&gt;
                &lt;body className={inter.className}&gt;
                    &lt;nav className=<span class="hljs-string">'w-full py-4 md:px-8 px-4 text-center flex items-center justify-between sticky top-0 bg-white '</span>&gt;
                        &lt;div className=<span class="hljs-string">'flex items-center justify-end gap-5'</span>&gt;
                            {<span class="hljs-comment">/*-- if user is signed out --*/</span>}
                            &lt;SignedOut&gt;
                                &lt;SignInButton mode=<span class="hljs-string">'modal'</span> /&gt;
                            &lt;/SignedOut&gt;
                            {<span class="hljs-comment">/*-- if user is signed in --*/</span>}
                            &lt;SignedIn&gt;
                                &lt;UserButton /&gt;
                            &lt;/SignedIn&gt;
                        &lt;/div&gt;
                    &lt;/nav&gt;

                    {children}
                &lt;/body&gt;
            &lt;/html&gt;
        &lt;/ClerkProvider&gt;
    );
}
</code></pre>
<p>After completing this, users will be prompted to create an account or sign in before they can access the application pages.</p>
<p>Finally, create a <a target="_blank" href="https://clerk.com">Clerk account</a> and set up a new Clerk application. Add your Clerk publishable and secret keys to the <code>.env.local</code> file in your project.</p>
<pre><code class="lang-bash">NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=&lt;publishable_key&gt;
CLERK_SECRET_KEY=&lt;secret_key&gt;
</code></pre>
<h2 id="heading-how-to-set-up-stream-in-a-nextjs-app">How to Set Up Stream in a Next.js app</h2>
<p>First, create a <a target="_blank" href="https://getstream.io/">Stream account</a> and set up an organization to house your app. Then, copy the following credentials into your <code>.env.local</code> file:</p>
<pre><code class="lang-bash">STREAM_APP_ID=&lt;your_app_id&gt;
NEXT_PUBLIC_STREAM_API_KEY=&lt;your_stream_api_key&gt;
STREAM_SECRET_KEY=&lt;your_stream_secret_key&gt;
NEXT_PUBLIC_FACETIME_HOST=http://localhost:3000/facetime
</code></pre>
<p>Next, install <a target="_blank" href="https://www.npmjs.com/package/@stream-io/video-react-sdk">Stream React Video SDK</a> and the <a target="_blank" href="https://getstream.io/video/docs/api/#installation">Stream Node.js SDK</a>.</p>
<pre><code class="lang-bash">npm install @stream-io/video-react-sdk @stream-io/node-sdk
</code></pre>
<p>Create a <code>providers</code> folder containing a <code>StreamVideoProvider.tsx</code> file and copy the following code snippet into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { tokenProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/actions/stream.actions"</span>;
<span class="hljs-keyword">import</span> { StreamVideo, StreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useState, ReactNode, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;

<span class="hljs-keyword">const</span> apiKey = process.env.NEXT_PUBLIC_STREAM_API_KEY!;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> StreamVideoProvider = <span class="hljs-function">(<span class="hljs-params">{ children }: { children: ReactNode }</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [videoClient, setVideoClient] = useState&lt;StreamVideoClient&gt;();

    <span class="hljs-keyword">const</span> { user, isLoaded } = useUser();

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (!isLoaded || !user || !apiKey) <span class="hljs-keyword">return</span>;
        <span class="hljs-keyword">if</span> (!tokenProvider) <span class="hljs-keyword">return</span>;
        <span class="hljs-keyword">const</span> client = <span class="hljs-keyword">new</span> StreamVideoClient({
            apiKey,
            user: {
                id: user?.id,
                name: user?.primaryEmailAddress?.emailAddress,
                image: user?.imageUrl,
            },
            tokenProvider, <span class="hljs-comment">//👉🏻 pending creation</span>
        });

        setVideoClient(client);
    }, [user, isLoaded]);

    <span class="hljs-keyword">if</span> (!videoClient) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

    <span class="hljs-keyword">return</span> &lt;StreamVideo client={videoClient}&gt;{children}&lt;/StreamVideo&gt;;
};
</code></pre>
<p>Let’s wrap the entire app with the <code>StreamVideoProvider</code> component, which initializes a Stream client to identify each user.</p>
<p>The <code>StreamVideoClient</code> function takes an object containing the API key, the user object with details from Clerk, and a <code>tokenProvider</code>.</p>
<p>Next, let’s create a <a target="_blank" href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations">Next.js server action</a> (<code>tokenProvider</code>) that generates the token.</p>
<p>Create an <code>actions</code> folder, add a <code>stream.actions.ts</code> file, and copy the following code snippet into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//👇🏻 tokenPrvoider function</span>
<span class="hljs-string">"use server"</span>;

<span class="hljs-keyword">import</span> { currentUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs/server"</span>;
<span class="hljs-keyword">import</span> { StreamClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/node-sdk"</span>;

<span class="hljs-keyword">const</span> STREAM_API_KEY = process.env.NEXT_PUBLIC_STREAM_API_KEY!;
<span class="hljs-keyword">const</span> STREAM_API_SECRET = process.env.STREAM_SECRET_KEY!;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> tokenProvider = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> currentUser();

    <span class="hljs-keyword">if</span> (!user) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"User is not authenticated"</span>);
    <span class="hljs-keyword">if</span> (!STREAM_API_KEY) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Stream API key secret is missing"</span>);
    <span class="hljs-keyword">if</span> (!STREAM_API_SECRET) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Stream API secret is missing"</span>);

    <span class="hljs-keyword">const</span> streamClient = <span class="hljs-keyword">new</span> StreamClient(STREAM_API_KEY, STREAM_API_SECRET);

    <span class="hljs-keyword">const</span> expirationTime = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>) + <span class="hljs-number">3600</span>;
    <span class="hljs-keyword">const</span> issuedAt = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>) - <span class="hljs-number">60</span>;

    <span class="hljs-comment">//👇🏻 generates a Stream user token</span>
    <span class="hljs-keyword">const</span> token = streamClient.generateUserToken({
        user_id: user.id,
        exp: expirationTime,
        validity_in_seconds: issuedAt,
    });
    <span class="hljs-comment">//👇🏻 returns the user token</span>
    <span class="hljs-keyword">return</span> token;
};
</code></pre>
<p>Finally, update the <code>RootLayout</code> function in the <code>app/layout.tsx</code> file by wrapping the entire application with the <code>StreamVideoProvider</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-string">"@stream-io/video-react-sdk/dist/css/styles.css"</span>;
<span class="hljs-keyword">import</span> { StreamVideoProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"./providers/StreamVideoProvider"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
    children,
}: {
    children: React.ReactNode;
}</span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;ClerkProvider&gt;
            &lt;html lang=<span class="hljs-string">'en'</span>&gt;
                &lt;body className={inter.className}&gt;
                    &lt;StreamVideoProvider&gt;
                        &lt;nav className=<span class="hljs-string">'w-full py-4 md:px-8 px-4 text-center flex items-center justify-between sticky top-0 bg-white '</span>&gt;
                            &lt;div className=<span class="hljs-string">'flex items-center justify-end gap-5'</span>&gt;
                                {<span class="hljs-comment">/*-- if user is signed out --*/</span>}
                                &lt;SignedOut&gt;
                                    &lt;SignInButton mode=<span class="hljs-string">'modal'</span> /&gt;
                                &lt;/SignedOut&gt;
                                {<span class="hljs-comment">/*-- if user is signed in --*/</span>}
                                &lt;SignedIn&gt;
                                    &lt;UserButton /&gt;
                                &lt;/SignedIn&gt;
                            &lt;/div&gt;
                        &lt;/nav&gt;

                        {children}
                    &lt;/StreamVideoProvider&gt;
                &lt;/body&gt;
            &lt;/html&gt;
        &lt;/ClerkProvider&gt;
    );
}
</code></pre>
<p>Congratulations! You've successfully integrated Stream into the Next.js app.</p>
<h2 id="heading-how-to-create-and-join-calls-with-stream">How to Create and Join Calls with Stream</h2>
<p>In this section, you'll learn how to create, schedule, and join calls using the Stream SDK. You'll also learn how to set up the meeting room with the necessary components and fetch upcoming calls from Stream.</p>
<h3 id="heading-creating-and-scheduling-calls">Creating and Scheduling calls</h3>
<p>To create an instant meeting, execute the <code>handleStartMeeting</code> function. It generates a random ID for the call and creates the meeting using the current date and the provided description.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">const</span> client = useStreamVideoClient();
<span class="hljs-keyword">const</span> { user } = useUser();

<span class="hljs-keyword">const</span> handleStartMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (!client || !user) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> id = crypto.randomUUID();
        <span class="hljs-keyword">const</span> call = client.call(<span class="hljs-string">"default"</span>, id);
        <span class="hljs-keyword">if</span> (!call) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to create meeting"</span>);

        <span class="hljs-keyword">await</span> call.getOrCreate({
            data: {
                starts_at: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now()).toISOString(),
                custom: {
                    description,
                },
            },
        });

        setFacetimeLink(<span class="hljs-string">`<span class="hljs-subst">${call.id}</span>`</span>);
        setShowMeetingLink(<span class="hljs-literal">true</span>);
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
        alert(<span class="hljs-string">"Failed to create Meeting"</span>);
    }
};
</code></pre>
<p>The <code>call.getOrCreate()</code> function accepts an optional call description along with the current date and time to initiate the call.</p>
<p>It also allows you to schedule calls for a specific time in the future. In this case, you can specify the desired date and time, and Stream will automatically schedule the call for that period.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">const</span> client = useStreamVideoClient();
<span class="hljs-keyword">const</span> { user } = useUser();

<span class="hljs-keyword">const</span> handleScheduleMeeting = <span class="hljs-keyword">async</span> (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (!client || !user) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> id = crypto.randomUUID();
        <span class="hljs-keyword">const</span> call = client.call(<span class="hljs-string">"default"</span>, id);
        <span class="hljs-keyword">if</span> (!call) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to create meeting"</span>);

        <span class="hljs-keyword">await</span> call.getOrCreate({
            data: {
                <span class="hljs-comment">//👇🏻 only necessary changes</span>
                starts_at: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(dateTime).toISOString(),
                custom: {
                    description,
                },
            },
        });
        setFacetimeLink(<span class="hljs-string">`<span class="hljs-subst">${call.id}</span>`</span>);
        setShowMeetingLink(<span class="hljs-literal">true</span>);
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Failed to create Meeting"</span>);
    }
};
</code></pre>
<h3 id="heading-joining-calls-and-the-meeting-page">Joining calls and the Meeting Page</h3>
<p>Recall that the meeting link in the app is declared as:</p>
<pre><code class="lang-jsx"><span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_FACETIME_HOST}</span>/<span class="hljs-subst">${facetimeLink}</span>`</span>
<span class="hljs-comment">// 👉🏻 format: &lt;http://localhost:3000/facetime/&gt;&lt;call.id&gt;</span>
</code></pre>
<p>Therefore, we need to create the <code>/facetime/&lt;callID&gt;</code> route to enable users to join a call. To do this, create a <code>facetime</code> folder with an <code>[id]</code> directory inside, and within that directory, add a <code>page.tsx</code> file. Then, copy the following code snippet into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { useGetCallById } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/hooks/useGetCallById"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> {
    StreamCall,
    StreamTheme,
    PaginatedGridLayout,
    CallControls,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
<span class="hljs-keyword">import</span> { useParams, useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">FaceTimePage</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> { id } = useParams&lt;{ id: <span class="hljs-built_in">string</span> }&gt;();
    <span class="hljs-keyword">const</span> [confirmJoin, setConfirmJoin] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [camMicEnabled, setCamMicEnabled] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> router = useRouter();
    <span class="hljs-comment">//👇🏻 gets call details by ID</span>
    <span class="hljs-keyword">const</span> { call, isCallLoading } = useGetCallById(id);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (camMicEnabled) {
            call?.camera.enable();
            call?.microphone.enable();
        } <span class="hljs-keyword">else</span> {
            call?.camera.disable();
            call?.microphone.disable();
        }
    }, [call, camMicEnabled]);

    <span class="hljs-comment">//👇🏻 enable users to join calls</span>
    <span class="hljs-keyword">const</span> handleJoin = <span class="hljs-function">() =&gt;</span> {
        call?.join();
        setConfirmJoin(<span class="hljs-literal">true</span>);
    };

    <span class="hljs-keyword">if</span> (isCallLoading) <span class="hljs-keyword">return</span> &lt;p&gt;Loading...&lt;/p&gt;;

    <span class="hljs-keyword">if</span> (!call) <span class="hljs-keyword">return</span> &lt;p&gt;Call not found&lt;/p&gt;;

    <span class="hljs-keyword">return</span> (
        &lt;main className=<span class="hljs-string">'min-h-screen w-full items-center justify-center'</span>&gt;
            &lt;StreamCall call={call}&gt;
                &lt;StreamTheme&gt;
                    {confirmJoin ? (
                        &lt;MeetingRoom /&gt;
                    ) : (
                        &lt;div className=<span class="hljs-string">'flex flex-col items-center justify-center gap-5'</span>&gt;
                            &lt;h1 className=<span class="hljs-string">'text-3xl font-bold'</span>&gt;Join Call&lt;/h1&gt;
                            &lt;p className=<span class="hljs-string">'text-lg'</span>&gt;
                                Are you sure you want to join <span class="hljs-built_in">this</span> call?
                            &lt;/p&gt;
                            &lt;div className=<span class="hljs-string">'flex gap-5'</span>&gt;
                                &lt;button
                                    onClick={handleJoin}
                                    className=<span class="hljs-string">'px-4 py-3 bg-green-600 text-green-50'</span>
                                &gt;
                                    Join
                                &lt;/button&gt;
                                &lt;button
                                    onClick={<span class="hljs-function">() =&gt;</span> router.push(<span class="hljs-string">"/"</span>)}
                                    className=<span class="hljs-string">'px-4 py-3 bg-red-600 text-red-50'</span>
                                &gt;
                                    Cancel
                                &lt;/button&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    )}
                &lt;/StreamTheme&gt;
            &lt;/StreamCall&gt;
        &lt;/main&gt;
    );
}
</code></pre>
<p>When users visit the meeting page, they are presented with a confirmation message, allowing them to confirm that they want to join the call.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726483083226/26ccb1d9-dc33-4a31-81a9-c4b0a3d00b91.png" alt="facetime-app-live" class="image--center mx-auto" width="3092" height="1834" loading="lazy"></p>
<p>In the code snippet above:</p>
<ul>
<li><p>The <code>useGetCallById</code> hook is a custom function that retrieves call details based on the call ID.</p>
</li>
<li><p>The <code>handleJoin</code> function allows users to join the call and then displays the <code>&lt;MeetingRoom /&gt;</code> component.</p>
</li>
</ul>
<p>Add the <code>MeetingRoom</code> component below the <code>FaceTimePage</code> component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> MeetingRoom = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> router = useRouter();

    <span class="hljs-keyword">const</span> handleLeave = <span class="hljs-function">() =&gt;</span> {
        confirm(<span class="hljs-string">"Are you sure you want to leave the call?"</span>) &amp;&amp; router.push(<span class="hljs-string">"/"</span>);
    };

    <span class="hljs-keyword">return</span> (
        &lt;section className=<span class="hljs-string">'relative min-h-screen w-full overflow-hidden pt-4'</span>&gt;
            &lt;div className=<span class="hljs-string">'relative flex size-full items-center justify-center'</span>&gt;
                &lt;div className=<span class="hljs-string">'flex size-full max-w-[1000px] items-center'</span>&gt;
                    &lt;PaginatedGridLayout /&gt;
                &lt;/div&gt;
                &lt;div className=<span class="hljs-string">'fixed bottom-0 flex w-full items-center justify-center gap-5'</span>&gt;
                    &lt;CallControls onLeave={handleLeave} /&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/section&gt;
    );
};
</code></pre>
<p>The <a target="_blank" href="https://getstream.io/video/docs/react/ui-components/core/call-layout/#paginatedgridlayout"><code>PaginatedGridLayout</code></a> arranges participants in a grid layout with pagination, allowing you to manage larger video calls by displaying a set number of participants per page.</p>
<p>The <code>CallControls</code> component provides built-in actions, such as muting, video toggling, and screen sharing, that can be performed during a call. Both components are part of the Stream SDK, making integration seamless.</p>
<p>Additionally, you can switch to the <a target="_blank" href="https://getstream.io/video/docs/react/ui-components/core/call-layout/#speakerlayout"><code>SpeakerLayout</code></a>, which highlights the dominant speaker or shared screen while displaying other participants in a smaller view.</p>
<p>Finally, create a <code>hooks</code> folder containing the <code>useGetCallById.ts</code> file and copy the code snippet below into the file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Call, useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useGetCallById = <span class="hljs-function">(<span class="hljs-params">id: <span class="hljs-built_in">string</span> | <span class="hljs-built_in">string</span>[]</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [call, setCall] = useState&lt;Call&gt;();
    <span class="hljs-keyword">const</span> [isCallLoading, setIsCallLoading] = useState(<span class="hljs-literal">true</span>);

    <span class="hljs-keyword">const</span> client = useStreamVideoClient();

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (!client) <span class="hljs-keyword">return</span>;

        <span class="hljs-keyword">const</span> loadCall = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">const</span> { calls } = <span class="hljs-keyword">await</span> client.queryCalls({
                    filter_conditions: { id },
                });

                <span class="hljs-keyword">if</span> (calls.length &gt; <span class="hljs-number">0</span>) setCall(calls[<span class="hljs-number">0</span>]);

                setIsCallLoading(<span class="hljs-literal">false</span>);
            } <span class="hljs-keyword">catch</span> (error) {
                <span class="hljs-built_in">console</span>.error(error);
                setIsCallLoading(<span class="hljs-literal">false</span>);
            }
        };

        loadCall();
    }, [client, id]);

    <span class="hljs-keyword">return</span> { call, isCallLoading };
};
</code></pre>
<p>The code snippet above filters the call list and <a target="_blank" href="https://getstream.io/video/docs/react/guides/querying-calls/#filters">returns the call with a matching ID</a>, allowing users to join the specified call.</p>
<h3 id="heading-retrieving-upcoming-calls">Retrieving Upcoming Calls</h3>
<p>To retrieve upcoming calls from Stream, you can create a custom hook that <a target="_blank" href="https://getstream.io/video/docs/react/guides/querying-calls/#calls-the-user-has-created-or-is-a-member-of">fetches all the calls created by the user</a>, as well as the calls they are a member of.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> { Call, useStreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useGetCalls = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> { user } = useUser();
    <span class="hljs-keyword">const</span> client = useStreamVideoClient();
    <span class="hljs-keyword">const</span> [calls, setCalls] = useState&lt;Call[]&gt;();
    <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">false</span>);

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> loadCalls = <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">if</span> (!client || !user?.id) <span class="hljs-keyword">return</span>;
            setIsLoading(<span class="hljs-literal">true</span>);
            <span class="hljs-keyword">try</span> {
                <span class="hljs-comment">//👇🏻 gets all the calls the user is featured in</span>
                <span class="hljs-keyword">const</span> { calls } = <span class="hljs-keyword">await</span> client.queryCalls({
                    sort: [{ field: <span class="hljs-string">"starts_at"</span>, direction: <span class="hljs-number">-1</span> }],
                    filter_conditions: {
                        starts_at: { $exists: <span class="hljs-literal">true</span> },
                        $or: [
                            { created_by_user_id: user.id },
                            { members: { $in: [user.id] } },
                        ],
                    },
                });

                setCalls(calls);
            } <span class="hljs-keyword">catch</span> (error) {
                <span class="hljs-built_in">console</span>.error(error);
            } <span class="hljs-keyword">finally</span> {
                setIsLoading(<span class="hljs-literal">false</span>);
            }
        };

        loadCalls();
    }, [client, user?.id]);

    <span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();

    <span class="hljs-comment">//👇🏻 gets only calls that are yet to start</span>
    <span class="hljs-keyword">const</span> upcomingCalls = calls?.filter(<span class="hljs-function">(<span class="hljs-params">{ state: { startsAt } }: Call</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> startsAt &amp;&amp; <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(startsAt) &gt; now;
    });

    <span class="hljs-keyword">return</span> { upcomingCalls, isLoading };
};
</code></pre>
<p>The <code>useGetCalls</code> hook <a target="_blank" href="https://getstream.io/video/docs/react/guides/querying-calls/#calls-the-user-has-created-or-is-a-member-of">retrieves the list of upcoming calls</a>, which can then be displayed in the <code>UpcomingMeeting</code> modal.</p>
<p>Congratulations! You’ve completed the project for this tutorial.</p>
<p>Check out the live app <a target="_blank" href="https://facetime-on-stream.vercel.app/">here.</a></p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>So far, you’ve learned how to build a video conferencing app. If you'd like to learn more about how you can leverage Stream to build scalable apps, then check out these resources:</p>
<ul>
<li><p><a target="_blank" href="https://getstream.io/chat/">How to integrate Stream Chat Messaging</a></p>
</li>
<li><p><a target="_blank" href="https://getstream.io/video/">How to integrate Stream Audio and Video calls</a></p>
</li>
<li><p><a target="_blank" href="https://getstream.io/activity-feeds/">How integrate Stream Activity Feeds</a></p>
</li>
</ul>
<h2 id="heading-before-we-end"><strong>Before We End...</strong></h2>
<p>I hope you found it insightful and that it has given you enough motivation on how to build apps using awesome developer tools.</p>
<p>These are some of my other most recent blog posts.</p>
<ul>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/state-of-databases-2024">State of Databases for Serverless in 2024</a></p>
</li>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/neon-vs-supabase"><strong>Neon Postgres vs Supabase</strong></a></p>
</li>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/mongoDB-vs-postgreSQL"><strong>MongoDB vs. PostgreSQL</strong></a></p>
</li>
</ul>
<p>Check out <a target="_blank" href="https://theankurtyagi.com/">my blog</a> for more tutorials like this on awesome developer tools.</p>
<p>Follow me on <a target="_blank" href="https://x.com/theankurtyagi">Twitter</a> to stay updated on my side projects and ongoing learning.</p>
<p>Happy coding.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a React App in 2024 ]]>
                </title>
                <description>
                    <![CDATA[ In 2024, there are more ways than ever to get your React projects started. But which one should you choose? In this article, I'm going to break down all of the major ways you can use to create a React app and which one you should pick based off of ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-a-react-app-in-2024/</link>
                <guid isPermaLink="false">66d03779dcd3a41034854bbe</guid>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Reed ]]>
                </dc:creator>
                <pubDate>Fri, 26 Jan 2024 13:44:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/build-react-app-2024.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In 2024, there are more ways than ever to get your React projects started. But which one should you choose?</p>
<p>In this article, I'm going to break down all of the major ways you can use to create a React app and which one you should pick based off of your project's needs.</p>
<p>I'll also include a quick guide at the end to show you exactly how to choose between each according to the type of project you're building.</p>
<p>Let's get started!</p>
<h2 id="heading-why-you-shouldnt-use-create-react-app">❌ Why You Shouldn't Use Create React App</h2>
<p>In 2023, the Create React App tool was deprecated, which means that it was no longer being maintained. Create React App has been the go-to way to make a new React project, but it's been dethroned by a number of different alternatives.</p>
<p>For that reason, Create React App is not an option I would recommend for creating a new React project in 2024.</p>
<h2 id="heading-how-to-create-a-react-app-with-vite">💨 How to Create a React App with Vite</h2>
<p>You may be asking, "What's a good replacement for Create React App?" That option is Vite.</p>
<p>Vite is ideal for making client-rendered React projects that run exclusively in the browser.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/vite.png" alt="Image" width="600" height="400" loading="lazy">
<em>Vite docs home page</em></p>
<p>To spin up a new React project with Vite, you just have to run a single command:</p>
<pre><code class="lang-bash">npm create vite@latest my-react-app -- --template react
</code></pre>
<p>The great thing about Vite is, as its name indicates, it's much faster than virtually every alternative. Where Vite really shines is how quickly it runs in development.</p>
<p>Vite is unopinionated, however, which means you will likely need to install a suite of third-party libraries for basic functionality, like routing and data fetching.</p>
<p>If your application doesn't have a server or you are using an external API and it doesn't need server-side rendering, Vite is a perfect choice.</p>
<p>Additionally, Vite comes with its own config file, <strong>vite.config.js</strong>, which might require reading the documentation in order to configure things such as environment variables, build options, and image options.</p>
<h2 id="heading-how-to-create-a-react-app-with-nextjs">🥞 How to Create a React App with Next.js</h2>
<p>If you're looking for a way to build a React app that gives you a single-page app (SPA) experience but with server-side rendering and server components, Next.js is your choice.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/nextjs.png" alt="Image" width="600" height="400" loading="lazy">
<em>Next.js docs home page</em></p>
<p>Next.js is the only option at the moment that comes with server components, which allows you to mark a component as <code>async</code> and <code>await</code> in some operation on the server.</p>
<pre><code class="lang-tsx">async function getData() {
  const res = await fetch('https://api.example.com/...');
  return res.json();
}

export default async function Page() {
  const data = await getData();

  return &lt;main&gt;{data}&lt;/main&gt;;
}
</code></pre>
<p>The benefit of server components is that you don't have to show a loading spinner in your app before fetching data. Server components allow you to ship far less JavaScript to the client, which leads to faster load times for your users.</p>
<p>Server components do require that you have a server, however, this means that it can't be deployed as simply as a client-rendered React app made with Vite.</p>
<p>Next.js is powerful because it comes with a ton of built-in features, such as a dedicated file-based routing, image optimization, and font loading, to name a few.</p>
<p>Next.js also allows you to tap into server actions, which is a new React feature that allows you to run server code by calling a function.</p>
<pre><code class="lang-tsx">// Server Component
export default function Page() {
  // Server Action
  async function create() {
    'use server'

    // ...
  }

  return (
    // ...
  )
}
</code></pre>
<p>Next.js also has route handlers, which lets you to make HTTP requests to an API endpoint. This is something that client-rendered React apps can't do, because there's no server.</p>
<p>With all of Next.js' benefits, it comes with a steeper learning curve. There are a number of Next.js-specific concepts that may seem to go against some React concepts you've already learned.</p>
<p>For example, within server components, you can't use React Hooks. This means that you have to rely on patterns such as storing state in the URL.</p>
<p>Despite the learning curve, Next.js is the most popular React framework and is relied upon to build impressive React apps that power small startups to Fortune 500 companies.</p>
<p>If you want to make something impressive with React, you can certainly do it with Next.js.</p>
<h2 id="heading-how-to-create-a-react-app-with-remix">💿 How to Create a React App with Remix</h2>
<p>Remix, like Next.js, is a React-based framework that has a different focus on web standards to deliver a better user experience. Remix allows you to also write server and client React code.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/remix.png" alt="Image" width="600" height="400" loading="lazy">
<em>Remix docs home page</em></p>
<p>Remix prides itself on serving static and dynamic content faster than Next.js. This means it's equally good at building full-stack applications as well as static websites.</p>
<p>Instead of server components and server actions, Remix simply has actions. Actions allows you to handle data mutations on your server, which is anything that isn't a GET request. Actions are just simple functions with the name action.</p>
<p>To get data, you use simple functions called loaders. React Remix uses React Router under the hood. So if you're comfortable with React Router, you'll likely feel at home with Remix as well.</p>
<pre><code class="lang-tsx">export async function loader() {
  return json(await fakeGetTodos());
}

export default function Todos() {
  const data = useLoaderData&lt;typeof loader&gt;();
  return (
    &lt;div&gt;
      &lt;TodoList todos={data} /&gt;
      &lt;Form method="post"&gt;
        &lt;input type="text" name="title" /&gt;
        &lt;button type="submit"&gt;Create Todo&lt;/button&gt;
      &lt;/Form&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>Remix is a bit more stable than Next.js and has a less steep learning curve, still allowing you to build just as impressive applications, with the only downside being that it's not nearly as popular as Next.js. So it doesn't have the same community support and libraries around it.</p>
<p>But if you do want something with server-side rendering and client-side rendering, Remix still remains a great option to build your next React project.</p>
<h2 id="heading-how-to-create-a-react-app-with-astro">🚀 How to Create a React App with Astro</h2>
<p>The newest way to build a React project is definitely the most performant. React apps can also be built using a framework called Astro.  </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/astro.png" alt="Image" width="600" height="400" loading="lazy">
<em>Astro docs home page</em></p>
<p>Astro's goal is deliver content to users quickly through something called "island architecture".</p>
<p>In short, this means that JavaScript is loaded only when the user needs them, making for a much more optimal user experience. If you want the fastest website possible, I'd highly recommend looking at Astro.</p>
<p>Astro supports server-side rendering and is great for SEO-focused websites that are largely static. However, what's neat about Astro is that it can also run code on the server if you choose to. It's not as popular to build fully dynamic, full-stack applications with Astro, but it is possible to do so.</p>
<p>Astro is also very flexible as it's not even tied to React. You don't need to use React whatsoever to build an Astro app, and you can use React alongside other frameworks such as Vue and Svelte within the same app.</p>
<p>If you're building a website that has posts or content that uses markdown or MDX, Astro should be your top choice. It uses a feature called "collections" to describe all the data within your markdown files so that you know exactly what content is going to be rendered in your React components.</p>
<p>Astro is gaining rapidly in popularity, and it's probably the best choice out there if you are interested in making a static website that doesn't need a database or authentication, or a website that is largely static.</p>
<h2 id="heading-so-what-should-i-choose">🤔 So What Should I Choose?</h2>
<p>If you've read up to this point and you're still trying to figure out which framework would be best for you to build a React project in 2024, here's the rundown:</p>
<ul>
<li>If you're just starting out and learning the React basics but want to build a simple or medium-sized project, stick with Vite.</li>
<li>If you want a full-stack framework with all the bells and whistles, like server components, and you don't mind spending time learning additional concepts, check out Next.js.</li>
<li>If you've tried Next.js and find some of its concepts difficult to understand, but still want to build a full-stack React application, definitely look into Remix.</li>
<li>Finally, if you have a static or content-driven website, and you don't really need a database or authentication, I would highly recommend using Astro.</li>
</ul>
<h2 id="heading-become-a-professional-react-developer">🏆 Become a Professional React Developer</h2>
<p>Looking for the ultimate resource to learn React from start to finish?</p>
<p>✨ <strong><a target="_blank" href="https://www.thereactbootcamp.com">Introducing: The React Bootcamp</a></strong></p>
<p>The bootcamp features every resource to help you succeed with React:</p>
<ul>
<li>🎬 200+ in-depth videos</li>
<li>🕹️ 100+ hands-on React challenges</li>
<li>🛠️ 5+ impressive portfolio projects</li>
<li>📄 10+ essential React cheat sheets</li>
<li>🥾 A complete Next.js bootcamp</li>
<li>🖼️ A complete series of animated videos</li>
</ul>
<p>Click below to try the React Bootcamp for yourself.</p>
<p><a target="_blank" href="https://www.thereactbootcamp.com"><img src="https://reedbarger.nyc3.digitaloceanspaces.com/reactbootcamp/react-bootcamp-cta-alt.png" alt="Click to join the React Bootcamp" width="600" height="400" loading="lazy"></a><br><em>Click to get started</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use React Dev Tools – With Example Code and Videos ]]>
                </title>
                <description>
                    <![CDATA[ When you're working on a React project, one of the easiest ways to debug your code is using the React Dev Tools. React Dev Tools is an extension created by the React team. It enables developers to deb ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-react-dev-tools/</link>
                <guid isPermaLink="false">66d45d9937bd2215d1e2457a</guid>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aman Kalra ]]>
                </dc:creator>
                <pubDate>Wed, 11 Jan 2023 18:18:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/thumbnail.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you're working on a React project, one of the easiest ways to debug your code is using the React Dev Tools.</p>
<p>React Dev Tools is an extension created by the React team. It enables developers to debug their code inside their Developer Tools.</p>
<p>Once you add the extension, you will get 2 new tabs in DevTools called Components and Profiler, respectively.</p>
<img src="https://www.freecodecamp.org/news/content/images/2023/01/Components-and-Profiler-Tabs-1.png" alt="Image" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Components and Profiler tabs in Chrome Dev Tools</em></p>
<p>To use the full functionality of the extension, you need to be in development mode. This is because in production mode, the component names get changed to letters (as you can see in above screenshot) and you will not be able to profile your components.</p>
<h2 id="heading-how-to-use-the-components-tab">How to Use the Components Tab</h2>
<p>The Components tab allows you to debug your code in dev tools with various functionalities. Let's cover them one by one:</p>
<h3 id="heading-you-can-check-the-props-state-hooks-of-the-component">You can check the props/ state/ hooks of the component</h3>
<img src="https://www.freecodecamp.org/news/content/images/2023/01/Props-and-State-in-Component-s-Tab-1.png" alt="Props and State in Component's Tab" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Viewing Props and State</em></p>
<p>This tab will help you to look for a component and check its respective props/ state in your dev tools, without having to log them in the console separately.</p>
<h3 id="heading-you-can-edit-the-props-state-from-dev-tools">You can edit the props/ state from dev tools</h3>
<div class="embed-wrapper"><iframe width="640" height="360" src="https://player.vimeo.com/video/787154069" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="Vimeo embed" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen="" loading="lazy"></iframe></div>

<p>One of the amazing features is that you can edit the props/ state of your component in the browser itself. This helps you to reflect changes in real time without having to reload or refresh your webpage. Above is a sample video so you can see how it works.</p>
<h3 id="heading-you-can-search-for-a-component">You can search for a component</h3>
<div class="embed-wrapper"><iframe width="640" height="360" src="https://player.vimeo.com/video/787155384" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="Vimeo embed" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen="" loading="lazy"></iframe></div>

<p>You can easily search for a component within your whole application by just typing out the name in the search bar provided. It will show you all related components with the keywords typed in. Then you can navigate between the matching results.</p>
<h3 id="heading-you-can-check-the-components-tree">You can check the component's tree</h3>
<img src="https://www.freecodecamp.org/news/content/images/2023/01/Component-s-Hierrarchy.png" alt="Component's Hierarchy" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Component's Hierarchy</em></p>
<p>While debugging, its important to note which parent components caused the child component to re-render. Checking this in your code is sometimes tedious. But the "<strong>rendered by</strong>" section makes it easier for you by showing all the parent components in a single place.</p>
<h3 id="heading-you-can-log-a-components-data-in-the-console">You can log a component's data in the console</h3>
<img src="https://www.freecodecamp.org/news/content/images/2024/08/logging-components.jpg" alt="Image" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Logging a component's data in the console</em></p>
<p>There are developers who like to work in console, and this feature allows you to log all of your component's data to console with just one click. It shows all relevant information regarding the component like props received, hooks present, which node is it in the DOM, and the file's location in your system.</p>
<h3 id="heading-you-can-check-subcomponents">You can check subcomponents</h3>
<div class="embed-wrapper"><iframe width="640" height="360" src="https://player.vimeo.com/video/787165596" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="Vimeo embed" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen="" loading="lazy"></iframe></div>

<p>Just like checking parent components isn't always easy in your code, the same applies with checking child components.</p>
<p>To overcome this issue, you can double click on a component name, which will show you all the subcomponents present inside the target component in one go.</p>
<h2 id="heading-how-to-use-the-profiler-tab">How to Use the Profiler Tab</h2>
<p>This tab allows you to test the performance of your components and shows which components to focus on for improvements.</p>
<img src="https://www.freecodecamp.org/news/content/images/2023/01/Profiler-tab-1.png" alt="Image" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Profiler tab</em></p>
<p>As you can see above, I've marked a few things on the screenshot. Let's cover them one by one:</p>
<ul>
<li><p><strong>A</strong> is the record button, which will help you record a profiling session.</p>
</li>
<li><p><strong>B</strong> is the refresh button, which will help you refresh the page for a session.</p>
</li>
<li><p><strong>C</strong> is the clear button, which will help you clear the profiling session's result.</p>
</li>
<li><p><strong>D</strong> is the commit chart, which will show you the list of commits during a session.</p>
</li>
<li><p><strong>E</strong> is the component list, which will show you the components rendered during a session.</p>
</li>
<li><p><strong>F</strong> is the flame chart button, which will show you the component list like <strong>E.</strong></p>
</li>
<li><p><strong>G</strong> is the ranked chart button, which will show you the component list in a ranked manner.</p>
</li>
</ul>
<p>Now let's dive into different functionalities present in this tab and how to check the performance of your web page:</p>
<h3 id="heading-how-to-read-the-commit-chart">How to Read the Commit Chart</h3>
<p>The commit chart shows the list of commits during a session. If you can see in the above image, in section <strong>D</strong>, there are 3 bars. These denote 3 commits in a session. And each commit shows a side effect that caused the DOM to update.</p>
<blockquote>
<p><a href="https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#browsing-commits">As per the docs</a>, "The colour and height of each bar corresponds to how long that commit took to render. (Taller, yellow bars took longer than shorter, blue bars.)"</p>
</blockquote>
<p>You can even navigate between the bars and check each commit separately.</p>
<h3 id="heading-how-to-read-the-flame-chart">How to Read the Flame Chart</h3>
<p>Flame chart shows the list of components rendered in a commit. If you can see in the above image, when you click on the section labeled <strong>F</strong>, all horizontal bars in the section <strong>E</strong> represent different components of the 1st commit.</p>
<blockquote>
<p><a href="https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html#flame-chart">As per the docs</a>, "The colour of a bar indicates how long the component (and its children) took to render in the selected commit. Yellow components took more time, blue components took less time, and gray components did not render at all during a commit."</p>
</blockquote>
<h3 id="heading-how-to-check-the-performance-of-your-web-page">How to Check the Performance of Your Web Page</h3>
<p>To check the performance of your web page, all you need to do is:</p>
<ul>
<li><p>Click on the record button.</p>
</li>
<li><p>Use your web page so that the Profiler will be able to analyse the rendering of components.</p>
</li>
<li><p>Click the record button again to finish recording.</p>
</li>
</ul>
<p>Then you will see the flame chart, and you can analyse which components are taking more time to render.</p>
<p>Note that the flame chart also shows:</p>
<ol>
<li><p>When exactly the component rendered.</p>
</li>
<li><p>And how long it took to render in a profiling session.</p>
</li>
</ol>
<p><strong>For example</strong>: In below image, the <strong>Home component</strong> was rendered at <strong>1.5s</strong> of the profiling session and it took <strong>27.7 ms</strong> to render.</p>
<img src="https://www.freecodecamp.org/news/content/images/2023/01/Flame-Chart.png" alt="Image" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Flame Chart</em></p>
<h3 id="heading-how-to-read-a-ranked-chart">How to Read a Ranked Chart</h3>
<p>When you click the <strong>Ranked chart</strong> icon as shown in section <strong>G</strong> of the image, you will get a chart view of the components. This view is in descending order, that is the component that took the most time to render will be at the top and the component which took the least time will be at the bottom.</p>
<p>You can see this in the below image:</p>
<img src="https://www.freecodecamp.org/news/content/images/2023/01/Ranked-Chart.png" alt="Image" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Ranked Chart</em></p>
<p><strong>Note</strong>: You can get more information about why your component rendered by just enabling the "Record why each component rendered while profiling." checkbox in settings of the "Profiler" tab. I've attached an image for reference below:</p>
<img src="https://www.freecodecamp.org/news/content/images/2023/01/Render-description.png" alt="Image" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><em>Render description</em></p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>React Dev Tools is a great extension to have when you're working on your React applications. They can help you easily debug your code and find performance bottlenecks in your application.</p>
<p>Do try out these features next time in your React project.</p>
<h2 id="heading-thanks-for-reading">Thanks for reading!</h2>
<p>If you found this article useful, do share it with your friends and colleagues.</p>
<p>If you like to see similar tips and tricks in HTML, CSS, JavaScript and React, do <a href="https://www.linkedin.com/in/amankalra1">Follow me on LinkedIn</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Inspect an Element – Chrome Shortcut ]]>
                </title>
                <description>
                    <![CDATA[ The Inspect Element feature of the Google Chrome browser is a powerful yet easy-to-use tool. It’s an important part of Chrome Developer Tools that you can use to check the source code of any website. But it doesn’t end there. You can take things furt... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-inspect-an-element-chrome-shortcut/</link>
                <guid isPermaLink="false">66adf1313bf50764799b9cac</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Fri, 15 Apr 2022 01:40:32 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/inspect-element.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The Inspect Element feature of the Google Chrome browser is a powerful yet easy-to-use tool.</p>
<p>It’s an important part of Chrome Developer Tools that you can use to check the source code of any website.</p>
<p>But it doesn’t end there. You can take things further by changing the elements and styles that make up the website – that is, the HTML, CSS, and JavaScript code of the website. This is why a lot of developers use the Inspect tool for debugging purposes.</p>
<p>If you’re a beginner in web development, the Inspect tool is a useful feature you can take advantage of to learn about how websites are built, the fonts, icons, and plugins used, and even who made the website.</p>
<p>The good news is that you don’t need to be the developer of the website to use this powerful tool as it is available to users as well. You don’t even need to be a developer at all to use it.</p>
<p>In this article, you will learn how to open the Chrome Developer Tools so you can get access to the Inspect feature, and how to inspect specific elements on a website. I will also show you how you can manipulate the elements of a website by changing the texts and styles.</p>
<p>I'll be using freeCodeCamp.org to show you how things work with the Inspect tool.</p>
<h2 id="heading-how-to-open-the-chrome-developer-tools">How to Open the Chrome Developer Tools</h2>
<p>To open the Chrome Developer Tools, click the vertical dots at the top-right corner of your browser:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss1-3.png" alt="ss1-3" width="600" height="400" loading="lazy"></p>
<p>Then hover over “More tools” and select “Developer tools”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss2-5.png" alt="ss2-5" width="600" height="400" loading="lazy"></p>
<p>You will then get access to the tabs of the developer tools such as Elements (the HTML and CSS that makes up the website), Console with which you can run JavaScript, Sources, and many more.</p>
<p>You can drag these tabs around and place them wherever you want:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/drag.gif" alt="drag" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-do-i-open-inspect-element-in-chrome-with-the-keyboard">How Do I Open Inspect Element in Chrome with the Keyboard?</h2>
<p>You can open the Inspect element tool on Linux by pressing <code>CTRL</code> + <code>SHIFT</code> + <code>C</code> or <code>F12</code> on Windows.</p>
<p>If you are on Mac, press <code>Command</code> + <code>SHIFT</code> + <code>C</code>.</p>
<h2 id="heading-how-to-inspect-specific-elements-on-a-website">How to Inspect Specific Elements on a Website</h2>
<p>To inspect any element you see on a website, whether it's text, a button, a video, or an image, right-click on the element and click “Inspect”. </p>
<p>In this case, I will right-click on the “Learn to code – for free” text on the freeCodeCamp.org landing page.
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss3-3.png" alt="ss3-3" width="600" height="400" loading="lazy"></p>
<p>The source code will open and the element will be highlighted for you, like this:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss4-2.png" alt="ss4-2" width="600" height="400" loading="lazy"></p>
<p>You can see the text is an <code>h1</code> element.</p>
<h2 id="heading-how-to-manipulate-the-elements-of-a-website-with-the-inspect-tool">How to Manipulate the Elements of a Website with the Inspect Tool</h2>
<p>You can change the text contents of a website with the Inspect tool. </p>
<p>As an example, I’m going to change the “Build projects” text on the freeCodeCamp.org landing page to “Build real-world projects”.</p>
<p>To do this, right-click on the element you would like to change and click “Inspect”. In this case, it's the “Build projects” text:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss5-2.png" alt="ss5-2" width="600" height="400" loading="lazy"></p>
<p>Double-click on the “Build projects” text:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss6-2.png" alt="ss6-2" width="600" height="400" loading="lazy"></p>
<p>Type in “Build real-world projects” and hit <code>ENTER</code>:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss7-1.png" alt="ss7-1" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/ss8-1.png" alt="ss8-1" width="600" height="400" loading="lazy">
You can see the text has been changed to “Build real-world projects”.</p>
<h2 id="heading-how-to-change-styles-with-the-inspect-tool">How to Change Styles with the Inspect Tool</h2>
<p>Let’s change the background of the “Get started (it’s free)” button to my favorite color – #2ecc71.</p>
<p>Right-click on the button and select “Inspect”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss9-1.png" alt="ss9-1" width="600" height="400" loading="lazy"></p>
<p>Double-click on the value of the “background-image” property on the right, that is <code>linear-gradient(#fecc4c,#ffac33)</code>.</p>
<p>Change the colors to <code>#2ecc71,#2ecc72</code> and hit <code>ENTER</code>:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss10-1.png" alt="ss10-1" width="600" height="400" loading="lazy">
You can see the background of the button has changed.</p>
<p>We have now made 2 changes on the freeCodeCamp.org landing page – we changed the “Build projects” text to “Build real-world projects” and we changed the background of the “Get Started (it’s free)” button:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss11-1.png" alt="ss11-1" width="600" height="400" loading="lazy"></p>
<h2 id="heading-are-the-changes-you-make-with-the-inspect-tool-permanent">Are the changes you make with the Inspect tool permanent?</h2>
<p>No. Any change you make with the Inspect tool is not permanent. Once you reload the page, the changes are gone.</p>
<p>This is because the website has been deployed to a server. So when you make another request to that server by reloading the page, the content from the server is loaded by your browser.</p>
<p>So don't worry - you playing around in this way with the Inspect tool won't change a website permanently. It just helps you learn more about it and practice your coding :)</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article showed you how to get access to the Developer tools of Google Chrome, how to use its Inspect feature to view the source code of a website, and how to change the elements and styles of a website with it.</p>
<p>If you just started learning to code in HTML, CSS, and JavaScript, the Inspect feature of Chrome Dev tools is a powerful tool you can use to view the source code of any website so that you can learn about how they are built.  </p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use CSS Overview in Chrome Dev Tools ]]>
                </title>
                <description>
                    <![CDATA[ If you're a web developer, you probably appreciate a well-designed and attractive website.  And you might see a color scheme or font on a specific website that you want to incorporate into your blog or web app. But you'll need a browser extension to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-css-overview-in-chrome-developer-tools/</link>
                <guid isPermaLink="false">66bb89f4add24ba4273250ed</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Cess ]]>
                </dc:creator>
                <pubDate>Mon, 07 Feb 2022 14:52:34 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/HTML-Best-Practices---How-to-Build-a-Better-HTML-Based-Website-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're a web developer, you probably appreciate a well-designed and attractive website. </p>
<p>And you might see a color scheme or font on a specific website that you want to incorporate into your blog or web app. But you'll need a browser extension to see the website's color scheme and other CSS features.</p>
<p>The <strong>CSS overview</strong> feature in Chrome Dev Tools lets you see these CSS properties.</p>
<p>In this post, we'll go over how to use the <strong>CSS overview</strong> feature in Chrome Developer Tools. We'll also learn how to use <strong>CSS overview</strong> to get the colors and other CSS properties you want to use in creating a web page.</p>
<p>Let's get started. 💃</p>
<h2 id="heading-what-is-the-css-overview-panel">What is the CSS overview panel?</h2>
<p>The <strong>CSS overview panel</strong> is one of the newest features of Chrome Developer Tools. It serves as a preview tool that allows you to see the different CSS properties used in creating a web page.</p>
<p>It displays CSS properties such as:</p>
<ul>
<li>The colors used on a web page.</li>
<li>The line height of each element used on a web page.</li>
<li>The font-size of each element used on a web page</li>
<li>The font-families of each element on a web page.</li>
<li>The font weights of each element used on a web page.</li>
</ul>
<h2 id="heading-what-are-chrome-developer-tools">What are Chrome Developer Tools?</h2>
<p><strong>Chrome Developer Tools</strong> is also known as Chrome Dev Tools.</p>
<p><strong>Chrome Dev Tools</strong> are a suite of web developer tools that come pre-installed in the Chrome browser. </p>
<p>Check out this article to know more about <a target="_blank" href="https://developer.chrome.com/docs/devtools/">Chrome developer tools.</a></p>
<p>Here are some of the advantages of using <strong>Chrome Dev Tools</strong>:</p>
<ul>
<li><p>It enables you to create better websites in a shorter amount of time.</p>
</li>
<li><p>It enables you to make changes to your code, test it, and inspect it.</p>
</li>
<li><p>Chrome Dev Tools give developers more control over their web applications and browsers. </p>
</li>
<li><p>It enables you to assess the general performance of a website. </p>
</li>
</ul>
<h2 id="heading-how-to-access-chrome-developer-tools-in-your-browser">How to Access Chrome Developer Tools in Your Browser</h2>
<p>You can access Chrome Developer tools in three different ways:</p>
<ol>
<li><p><strong>Chrome’s Menu</strong>:</p>
</li>
<li><p>Click on the three vertical dots located on the top right corner of your chrome browser. It will bring up a drop-down menu with <code>more tools</code> at the bottom of the screen. </p>
</li>
<li><p>Click on more tools.</p>
</li>
<li><p>Click on developer tools.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design.png" alt="Untitled-design" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li><p><strong>Inspect:</strong> </p>
</li>
<li><p>Right-click on the chrome browser.</p>
</li>
<li><p>Click on inspect.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--1--2.png" alt="Untitled-design--1--2" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li><p><strong>Shortcuts:</strong> </p>
</li>
<li><p>For Windows - <code>CTRL</code> + <code>Shift</code> + <code>I</code> OR <code>F12</code>. </p>
</li>
<li><p>For Mac - <code>CMD</code> + <code>Shift</code> + <code>I</code>. </p>
</li>
</ol>
<p>Once you click on the shortcut keys, the developer tools open.</p>
<p>When you press <code>CTRL</code> + <code>Shift</code> + <code>I</code>, it displays the last panel you opened by default. It shows the element, console, network, or performance panel, among other things.</p>
<p><code>CTRL</code> + <code>Shift</code> + <code>C</code> opens the <strong>element panel</strong> first by default.</p>
<h2 id="heading-how-to-use-css-overview-in-chrome-dev-tools">How to Use CSS Overview in Chrome Dev Tools</h2>
<p>The steps below will walk you through how to use the CSS overview feature to get the CSS properties used on a web page.</p>
<h2 id="heading-step-1-open-chrome-dev-tools">Step 1 - Open Chrome Dev tools</h2>
<p>We've already covered the various methods for accessing Chrome developer tools. You should be familiar with them now.</p>
<p>As a quick reminder, you can open Chrome dev tools by pressing <code>Ctrl</code> + <code>Shift</code> + <code>I</code> on Windows and Linux. Use <code>CMD</code> + <code>Option</code> + <code>I</code> on Mac.</p>
<h2 id="heading-step-2-click-on-more-tools">Step 2 - Click on More tools</h2>
<p>Click on the three vertical dots located on the top-right of Chrome dev tools.</p>
<p>Select "More Tools" from the drop-down menu. </p>
<p>You'll discover a variety of options when you click "More Tools." From the various options, select the <strong>CSS overview</strong> feature.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--1--3.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-3-click-on-capture-overview">Step 3 - Click on Capture Overview</h2>
<p>When you click on <strong>CSS Overview</strong>, you'll see a list of its functions.</p>
<p>Functions such as:</p>
<ul>
<li><p>Capture an overview of your page's CSS.</p>
</li>
<li><p>Identify potential CSS improvements.</p>
</li>
<li><p>Locate the affected elements in the element panel.</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--2-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click on the <strong>Capture Overview button</strong>.</p>
<p>A menu with five sections appears after clicking the Capture Overview button.</p>
<p>The five sections are:</p>
<ul>
<li>Overview summary</li>
<li>Colors</li>
<li>Font info</li>
<li>Unused declarations</li>
<li>Media queries</li>
</ul>
<p>Let's go over each of the five sections one by one to see how they work.</p>
<h2 id="heading-css-overview-summary">CSS Overview Summary</h2>
<p>The <strong>Overview summary</strong> contains a list of the CSS elements used in building the web page.</p>
<p>The Overview summary displays a summary of the CSS on your website, such as:</p>
<ul>
<li>The number of elements used on the web page.</li>
<li>The different types of selectors used in creating the web page.</li>
<li>The number of inline style elements used on the web page.</li>
<li>The number of external stylesheets used on the web page.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/overview-summary.png" alt="Image" width="600" height="400" loading="lazy">
<em>An Illustration of the overview summary.</em></p>
<p>The example above shows the various CSS elements used to build the web page.</p>
<h2 id="heading-colors">Colors</h2>
<p>The color panel displays all the colors used in creating the web page. It has a palette of colors for the background, text, fill, and borders. It also highlights low-contrast texts issues on the web page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/color-ya-boy.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the color panel.</em></p>
<p>The image above shows you the different colors used in creating the web page.</p>
<p>The beauty of the Color panel is that each color is clickable. If you click on a particular color in the Color panel, a list of elements that use that color appears. When you click on each element, it takes you to the element panel for inspection.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--6-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>I clicked on color <code>#49FCD4</code> in the image above, and it brought up a list of elements with that color.</p>
<p>You can also hover over an element in the lists of elements displayed. When you move your cursor over the element, it highlights the element on the web page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--7-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>When I hover my mouse over the <code>header</code> element in the image above, it highlights the header  on the web page.</p>
<p>Just a quick note: hovering refers to moving your cursor over anything. It means to place a cursor over text, an image, or other objects on the screen without clicking on them.</p>
<h2 id="heading-font-info">Font Info</h2>
<p>The font info panel displays the typefaces used in the development of the website. It shows you the <code>font-size</code>, <code>line-height</code>, <code>font-weight</code>, and <code>font families</code> used in creating the website. If you click on the <strong>occurrences</strong>, you will see a list of the affected elements.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/use-this-one-for-header.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the font info panel.</em></p>
<p>The above image shows you the different typefaces used in creating the web page. </p>
<h2 id="heading-unused-declarations">Unused Declarations</h2>
<p>You can find CSS styles that do not affect the web page by using <strong>unused declarations</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/declare-my-boy.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the unused declaration panel.</em></p>
<p>The image above shows the number of unused declarations on the web page. The vertical alignment applied to the element that isn't inline or a table cell will not affect the page.</p>
<p>You can also click on the <strong>occurrences</strong> to see a list of elements affected, like the <strong>font info</strong> and <strong>color panel</strong>.</p>
<h2 id="heading-media-queries">Media Queries</h2>
<p>The media query panel displays a list of all the media queries used in creating the web page. You will be able to examine the various widths and screen resolutions used in creating the web page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/media-query-my-boy.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the media query panel.</em></p>
<p>The above example displays the number of media queries used in creating the web page. It lists the screen resolutions used in order of occurrence, from highest to lowest. If you click on the <strong>occurrences</strong>, you will see a list of the affected elements.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When it comes to evaluating CSS attributes on a web page, the <strong>CSS overview</strong> tool comes in handy. It allows front-end developers and designers to inspect the CSS properties on a web page.</p>
<p>Thank you for reading 💙. If you would like to chat or have any questions, please feel free to contact me anytime on Twitter: <a target="_blank" href="https://twitter.com/Cessss_">@cessss_</a> and linkedIn: <a target="_blank" href="https://www.linkedin.com/in/success-eriamiantoe/">Success</a>.</p>
<p>Also, follow my blog to read some of my other stuff <a target="_blank" href="https://cesscode.hashnode.dev">@cesscode</a>.</p>
<p>Happy coding! 💙</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Best No-Code Tools for Developers in 2021 ]]>
                </title>
                <description>
                    <![CDATA[ By Edidiong Asikpo No-code tools let people across the world build different products and applications without writing code.    Before no-code tools, building simple websites or mobile applications took weeks or months and only experienced software d... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/best-no-code-tools-for-developers/</link>
                <guid isPermaLink="false">66d45e3c55db48792eed3f5b</guid>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Low Code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 24 May 2021 14:54:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/05/Software-Developers.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Edidiong Asikpo</p>
<p>No-code tools let people across the world build different products and applications without writing code.   </p>
<p>Before no-code tools, building simple websites or mobile applications took weeks or months and only experienced software developers could do it. </p>
<p>But that's not the case anymore. Now, you can spin up blogs, websites, and more using the right no-code tools. And even if you know how to code, these tools can help you become more productive.  </p>
<p>There are tons of no-code tools available today. So, choosing the right ones can be tricky. This article aims to highlight some of the best no-code tools for developers to use in 2021.</p>
<h2 id="heading-webflow">Webflow</h2>
<p><a target="_blank" href="https://webflow.com/">Webflow</a> is a powerful, web-based design tool that gives you the superpower of designing, building, and launching responsive websites without writing a single line of code. Amazing right? I know!</p>
<p>Rather than sketching/designing your projects and then coding them into an actual product, you use a different approach with Webflow.</p>
<p>Websites built with Webflow are powered by Amazon Cloudfront, hosted on Fastly, and don't require external plugins.</p>
<p><strong>With Webflow, you can:</strong> </p>
<ul>
<li>Create automatic site backups (versioning) and staging URLs. </li>
<li>Move from prototype to mockup in minutes. </li>
<li>You can design hundreds of pages at once.</li>
<li>Design, build and launch with no stress at all. </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-33.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing">Pricing 💵</h4>
<p>Webflow has a free tier and four other paid tiers listed below:</p>
<ul>
<li><strong>Basic:</strong> $12 a month (paid annually) or $15 a month (paid monthly)</li>
<li><strong>CMS:</strong> $16 a month (paid annually) or $20 a month (paid monthly)</li>
<li><strong>Business:</strong> $36 a month (paid annually) or $45 a month (paid monthly)</li>
<li><strong>Enterprise:</strong> You have to contact the sales team to get this information</li>
</ul>
<h2 id="heading-buildbox">BuildBox</h2>
<p>Did you ever think it was possible to create exciting games without writing code? I am happy to let you know that it is!</p>
<p>Imagine if you are already busy with coding a game app, but a new client asked you to create a game for them. Instead of rejecting the offer, you can use <a target="_blank" href="https://signup.buildbox.com/">BuildBox</a> to make the game. It's a win-win situation if you ask me. 😉</p>
<p>Buildbox is the world's first software that truly allows anyone to create amazing games regardless of technical skill. Due to its unique user interface, making games becomes a fluid process that doesn't require any scripting, programming, or software design experience.</p>
<p><strong>As a developer, you can use Buildbox to:</strong></p>
<ul>
<li>Drag and drop to create unique and professional 3D games.</li>
<li>Add smart assets into your game.</li>
<li>Add action effects and logic to your games and more! 🚀</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-44.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-1">Pricing 💵</h4>
<p>Buildbox has a free tier and two other paid tiers listed below:</p>
<ul>
<li><strong>Plus:</strong> $9.99 a month or $89.99 per year</li>
<li><strong>Pro:</strong> $224.99 per year</li>
</ul>
<h2 id="heading-hashnode"><strong>Hashnode</strong></h2>
<p>As a developer, blogging lets you share your technical knowledge and experience with the developer community. And it also helps you reinforce your learning on every topic you write.</p>
<p><a target="_blank" href="https://hashnode.com/?source=freecodecamp">Hashnode</a> enables developers to create a blog mapped to their custom domain for free. So you only have to focus on publishing articles on your blog while Hashnode takes care of the rest – customization, readership, visibility, web monetization, and so much more.</p>
<p>Over the years, I've seen many developers build blogs from scratch <strong>only</strong> because they wanted to map it to their personal domain and give it a peculiar look and feel. Well guess what? This no-code tool lets you achieve all that and more. </p>
<p><strong>With Hashnode, you:</strong> </p>
<ul>
<li>Don't have to worry about maintaining or constantly updating your blog since Hashnode handles everything.</li>
<li>Only have to focus on writing and publish articles on the blog.</li>
<li>Will get readership on your blog from day zero.</li>
<li>Can customize your blog to meet your needs and so much more!</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-32.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-2">Pricing 💵</h4>
<p>Hashnode is free forever for developers. However, <a target="_blank" href="https://hashnode.com/post/hashnode-is-changing-more-about-our-business-model-new-mission-and-goal-ckbhowv9100lyzes1oo6qneri">they plan to launch a paid team tier for business</a>. </p>
<h2 id="heading-bubble">Bubble</h2>
<p><a target="_blank" href="https://bubble.io/">Bubble</a> lets you create interactive, multi-user apps for desktop and mobile web browsers, including all the features you need to build a site like Facebook or Airbnb.</p>
<p>Traditional web applications require you to manage your code and set up a deployment process to a web server. Not Bubble – it handles your deployment and hosting for you.</p>
<p>Most importantly, there are no hard limits on the number of users, the volume of traffic, or data storage.</p>
<p><strong>Here a couple of ways you can use Bubble as a developer:</strong></p>
<ul>
<li>You can create mobile-friendly layouts and dynamic content for a polished product that you'll be proud to show off to your prospects, customers, or investors.</li>
<li>You can build responsive web apps.</li>
<li>You can create hybrid mobile apps.</li>
<li>You can connect to external services and hardware through APIs.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-35.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-3">Pricing 💵</h4>
<p>Bubble has a free tier and three more paid tiers. Find the paid tiers below:</p>
<ul>
<li><strong>Personal:</strong> $25 a month (paid annually) or $29 a month (paid monthly)</li>
<li><strong>Professional:</strong> $115 a month (paid annually) or $129 a month (paid monthly)</li>
<li><strong>Production:</strong> $475 a month (paid annually) or $529 a month (paid monthly)</li>
</ul>
<h2 id="heading-coda">Coda</h2>
<p><a target="_blank" href="https://coda.io/welcome">Coda</a> is an all-in-one doc that brings all of your words and data into one flexible surface.</p>
<p>With Coda, you no longer have an unconnected web of documents, spreadsheets, databases, and niche workflow apps to get things done – because everything will be unified and brought into one location.</p>
<p>Coda comes with building blocks like tables and buttons – and time-saving templates so your documentation can grow and evolve with the needs of your team.</p>
<p><strong>Here are a few ways you can use Coda as a developer:</strong></p>
<ul>
<li>You can manage data from somewhere else in a precise way for that specific instance.</li>
<li>You can edit, contextualize, or "massage" data very quickly.</li>
<li>You can show or hide rows or columns, drag-and-drop columns, even group by elements to create a pivot table or kanban board.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-36.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-4">Pricing 💵</h4>
<p>Coda has a free tier and three more paid tiers highlighted below:</p>
<ul>
<li><strong>Pro:</strong> $10/month per doc maker (paid annually) or $12 a month (paid monthly)</li>
<li><strong>Team:</strong> $30/month per doc maker (paid annually) or $36 a month (paid monthly)</li>
<li><strong>Enterprise:</strong> You have to contact the sales team to get this information</li>
</ul>
<h2 id="heading-gumroad"><strong>Gumroad</strong></h2>
<p><a target="_blank" href="https://gumroad.com/">Gumroad</a> lets developers start selling their digital products in seconds. It also gives you access to a great list of tools to help you engage with an audience that cares about you and your work.</p>
<p>You can personalize your landing page to your heart’s content, embed a follow form, add a fantastic checkout and consumption experience, and gain access to robust data based on the usage of your product.</p>
<p><strong>With Gumroad, you can:</strong></p>
<ul>
<li>Be as powerful as the world’s largest retailers by setting up automatic workflows and up-sells.</li>
<li>Keep your audience close by adding <a target="_blank" href="https://gumroad.com/widgets">Gumroad's widgets</a> directly on your website.</li>
<li>Make money selling courses or e-books about tech, career growth, and more!</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-37.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-5">Pricing 💵</h4>
<p>Gumroad makes money when the creator makes money. So, the platform is free until you have more than 1000 customers. Then, you will start paying $10 monthly or $108 per year. </p>
<h2 id="heading-notion">Notion</h2>
<p><a target="_blank" href="https://www.notion.so/">Notion</a> is a fantastic tool that helps you organize your work, and you can pretty much adjust it to fit all of your needs. Also, there are <a target="_blank" href="https://www.notion.so/Notion-Template-Gallery-181e961aeb5c4ee6915307c0dfd5156d">a lot of templates</a> to choose from made by incredibly creative people.</p>
<p><strong>Here a couple of ways you can use Notion as a developer:</strong></p>
<ul>
<li>To publish and advertise your schedule or a schedule of an upcoming event you are organizing.</li>
<li>You can use it for note-taking.</li>
<li>It helps you collaborate with others.</li>
<li>You can use it to compile and share public documents and more.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-47.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-6">Pricing 💵</h4>
<p>Coda has a free tier and three more paid tiers highlighted below:</p>
<ul>
<li><strong>Personal Pro:</strong> $4 a month (paid annually) or $5 a month (paid monthly)</li>
<li><strong>Team:</strong> $8 a month (paid annually) or $10 a month (paid monthly)</li>
<li><strong>Enteprise:</strong> You have to contact the sales team to get this information</li>
</ul>
<h2 id="heading-voiceflow"><strong>Voiceflow</strong></h2>
<p><a target="_blank" href="https://www.voiceflow.com/">Voiceflow</a> provides you with the resources you need to design, prototype, and launch voice and chatbots without writing any lines of code.</p>
<p>Its fast and visual drag-n-drop canvas allows you to leverage components, robust context models, interaction model exports, and more.</p>
<p>This no-code tool is a game-changer when it comes to creating voice or chatbots. You should check it out.</p>
<p><strong>With Voiceflow, you can:</strong></p>
<ul>
<li>Design prototypes for Alexa and Google Assistant.</li>
<li>Build voice apps as good as custom code, easier and faster.</li>
<li>Track and analyze the results of your voice apps with custom analytics.</li>
<li>You get context first designs that enable you to create engaging contextually layered voice apps and conversations effortlessly.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-38.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-7">Pricing 💵</h4>
<p>Voiceflow has a free tier and two more paid tiers highlighted below.</p>
<ul>
<li><strong>Pro:</strong> $40 a month (paid annually) or $50 a month (paid monthly)</li>
<li><strong>Enterprise:</strong> You have to contact the sales team to get this information</li>
</ul>
<h2 id="heading-bumpa">Bumpa</h2>
<p><a target="_blank" href="https://getbumpa.com/">Bumpa</a> is that one stop shop for everything a developer or business owner needs to sell online and manage their business. With Bumpa, you can set up your website with your products in less than 5 minutes.</p>
<p>Bumpa allows you to record sales from your website, your physical shop, various marketplaces and even on social media. This no-code tool stands out because of the ease of setting up and also managing both your online and offline systems.</p>
<p><strong>With Bumpa, you can:</strong></p>
<ul>
<li>Create an online store.</li>
<li>Manage orders and products, track sales, share across Facebook, Instagram and Twitter.</li>
<li>Accept payment and send out notifications to your customers directly through the app.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-53.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-8">Pricing 💵</h4>
<p>Bumpa has a free plan for all its users. But if you need more features, you'll have to use upgrade to a premium plan which is $9 monthly.</p>
<h2 id="heading-zapier"><strong>Zapier</strong></h2>
<p>You can do a lot of the work you do every day automatically thanks to Zapier.</p>
<p>It helps you connect your apps and moves information between them automatically. This lets you focus on your most important work instead of doing repetitive tasks.</p>
<p><a target="_blank" href="https://zapier.com/">Zapier</a> gives you more time to build relationships, grow your team, test new strategies, and do work you enjoy. 😃 </p>
<p><strong>Here a couple of ways you can use Zapier as a developer:</strong></p>
<ul>
<li>Receive emails for new jobs from your favourite job board. </li>
<li>Auto-reply to Slack messages and mentions.</li>
<li>Turn emails into Trello task cards.</li>
<li>Streamline your recruitment process.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-39.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-9">Pricing 💵</h4>
<p>Zapier has a free tier and four other paid tiers highlighted below.</p>
<ul>
<li><strong>Started:</strong> $19.99 a month (paid annually) or $29.99 a month (paid monthly)</li>
<li><strong>Professional:</strong> $49 a month (paid annually) or $73.50 a month (paid monthly)</li>
<li><strong>Team:</strong> $299 a month (paid annually) or $448.50 a month (paid monthly)</li>
<li><strong>Company:</strong> $599 a month (paid annually) or $898.50 a month (paid monthly)</li>
</ul>
<h2 id="heading-shopify"><strong>Shopify</strong></h2>
<p>Want to start an e-commerce business? You should check out <a target="_blank" href="https://www.shopify.com.ng/">Shopify</a>.</p>
<p>Why, you may ask?</p>
<p>The truth is, Shopify helps you bring your business online by enabling you to create e-commerce websites backed by powerful tools that help you find customers, drive sales, and manage your day-to-day.</p>
<p>I like Shopify because it doesn't restrict you to selling only online products like most platforms. You can sell everywhere – in person with Point of Sale and online through your website, social media, and online marketplaces.</p>
<p><strong>Here a couple of ways you can use Shopify as a developer:</strong></p>
<ul>
<li>Create an e-commerce website for your clients or employer</li>
<li>Learn about Shopify and become a Shopify developer (They get paid a good amount of money).</li>
<li>Sell e-books, T-shirts, and more for people in the developer community. </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-41.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-10">Pricing 💵</h4>
<ul>
<li><strong>Basic Shopify:</strong> $29 monthly</li>
<li><strong>Shopify:</strong> $79 monthly</li>
<li><strong>Advanced Shopify:</strong> $299 monthly</li>
</ul>
<h2 id="heading-cardd"><strong>Cardd</strong></h2>
<p>Whether it's a personal profile, a landing page to capture emails, or something a bit more elaborate, <a target="_blank" href="https://carrd.co/">Cardd</a> has you covered.</p>
<p>Guess what? You can publish these sites to any custom domains you own with full SSL support. </p>
<p><strong>With Cardd, you can:</strong></p>
<ul>
<li>Create a simple, free, fully responsive one-page site for pretty much anything.</li>
<li>Build and publish more than three sites from a single Carrd account.</li>
<li>Embed your custom widgets from third-party services.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-48.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-11">Pricing 💵</h4>
<p>Carrd has a free tier and three other paid tiers listed below:</p>
<ul>
<li><strong>Pro Lite:</strong> $9 annually</li>
<li><strong>Pro Standard:</strong> $19 annually</li>
<li><strong>Pro Plus:</strong> $49 annually</li>
</ul>
<h2 id="heading-airtable"><strong>Airtable</strong></h2>
<p><a target="_blank" href="https://airtable.com/">Airtable</a> is a cloud collaboration service with the features of a database and a spreadsheet formatted in a way that lets you always know what’s going on.</p>
<p>The fields in an Airtable table are similar to cells in a spreadsheet but have types such as checkbox, phone number, drop-down list, images, and more.</p>
<p>Users can create a database, set up column types, add records, collaborate, sort records, and even publish views to external websites.</p>
<p>Whether you need to organize a calendar of project deliverables, compile a customer list, or organize any other type of information, Airtable makes it easy to create and collaborate.</p>
<p><strong>Here a couple of ways you can use Airtable as a developer:</strong></p>
<ul>
<li>Airtable lets you build spreadsheets. So, you can leverage each spreadsheet as a database.</li>
<li>Create views designed for different use cases.</li>
<li>Connect your workflow to hundreds of apps and services, or access your content programmatically.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-34.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-12">Pricing 💵</h4>
<p>Airtable has a free tier and three other paid tiers shared below:</p>
<ul>
<li><strong>Plus:</strong> $10 a month (paid annually) or $24 a month (paid monthly)</li>
<li><strong>Pro:</strong> $20 a month (paid annually) or $50 a month (paid monthly)</li>
<li><strong>Enterprise:</strong> You have to contact the sales team to get this information</li>
</ul>
<h2 id="heading-ifttt"><strong>IFTTT</strong></h2>
<p>With <a target="_blank" href="https://ifttt.com/">IFTTT</a>, you can connect your apps and devices in new and remarkable ways you didn't think were possible. It enables you to build a more connected world that works for <strong>you</strong>.</p>
<p>One integration with their API protocol connects your product to hundreds of apps, devices, and brands. They also have a fantastic documentation for <a target="_blank" href="https://platform.ifttt.com/docs">developers</a>. You should check it out.</p>
<p><strong>Here a couple of ways you can use IFTTT as a developer:</strong></p>
<ul>
<li>You’ll be able to gain insights into how people connect and use your products so you can further personalize their experiences.</li>
<li>Build and publish applets for others to use.</li>
<li>Find brands that complement yours. </li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/image-40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-pricing-13">Pricing 💵</h4>
<p>IFTTT has a free tier and four more paid tiers highlighted below:</p>
<ul>
<li><strong>Pro:</strong> $3.99  monthly</li>
<li><strong>Developer:</strong> $199 annually</li>
<li><strong>Team:</strong> You have to contact the sales team to get this information</li>
<li><strong>Enterprise:</strong> You have to contact the sales team to get this information</li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In an age where the demand for software far exceeds the supply of coders, no-code development tools are helping people design and build products and websites quickly.</p>
<blockquote>
<p>The best time to jump on to the no-code train is now. No-code is the future. If you become an expert now, you will be one of the most in-demand people in the future. - <strong>Arun Saigal, Cofounder and CEO Thunkable.</strong></p>
</blockquote>
<p>The quote above summarises my thoughts about no-code tools and why you should start using them now.</p>
<p>That's all, folks! I hope this was helpful. If yes, follow me on <a target="_blank" href="https://twitter.com/Didicodes">Twitter</a> to access more content like this.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test and Play with Web APIs the Easy Way with Postman ]]>
                </title>
                <description>
                    <![CDATA[ In a world where static websites and apps increasingly depend on separately maintained APIs, it can be hard to figure out how they work by just playing around in the browser.  So how can we use Postman to both test our existing APIs and understand ho... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-test-and-play-with-web-apis-the-easy-way-with-postman/</link>
                <guid isPermaLink="false">66b8e36e47e3b55b9fb6ee45</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ programing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ QA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Quality Assurance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ REST API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Colby Fayock ]]>
                </dc:creator>
                <pubDate>Tue, 30 Jun 2020 16:34:32 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/06/postman.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In a world where static websites and apps increasingly depend on separately maintained APIs, it can be hard to figure out how they work by just playing around in the browser. </p>
<p>So how can we use Postman to both test our existing APIs and understand how they work?</p>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-postman">What is Postman?</a></li>
<li><a class="post-section-overview" href="#heading-what-are-we-going-to-build-learn">What are we going to build / learn?</a></li>
<li><a class="post-section-overview" href="#heading-part-0-getting-set-up-with-postman">Part 0: Getting set up with Postman</a></li>
<li><a class="post-section-overview" href="#heading-part-1-an-introduction-to-postman">Part 1: An introduction to Postman</a></li>
<li><a class="post-section-overview" href="#heading-part-2-creating-a-new-postman-request-to-get-info-about-squirtle">Part 2: Creating a new Postman request to GET info about Squirtle</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/p/a02335e1-4f9a-453d-8916-db6b8419cf99/part-3-creating-a-collection-of-requests-in-postman-for-the-pok-api">Part 3: Creating a collection of requests in Postman for the PokéAPI</a></li>
<li><a class="post-section-overview" href="#heading-part-4-making-post-requests-with-postman-to-translate-sentences-to-sound-like-yoda">Part 4: Making POST requests with Postman to translate sentences to sound like Yoda</a></li>
<li><a class="post-section-overview" href="#heading-part-5-authenticating-requests-to-the-lord-of-the-rings-api-with-an-api-key">Part 5: Authenticating requests to the Lord of the Rings API with an API Key</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/KFuaybrXCdw" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-is-postman">What is Postman?</h2>
<p><a target="_blank" href="https://www.postman.com/">Postman</a> is a tool teams can use to reliably test APIs using easy to use configurations. It comes stocked with features you would expect when dealing with APIs, including authentication, setting headers, customizing the payload, and a bunch more that help reduce the friction of using an API.</p>
<p>And it’s not just for testing. The beauty is that this can be used for many aspects of working with APIs for many different members of the team. Maybe a Project Manager wants to verify that things work or might find it easier to make a change straight with the API, or a QA Engineer needs to make sure everything still works, or a developer wants to actively make changes while working on the API itself.</p>
<p>The best part about it – Postman provides collaboration features. The free tier includes exporting and importing collections of saved API requests as well as creating shared links. If you're part of a team, they have paid tiers that allow you to sync up your collections to make sure everyone has the most recent and up to date collection.</p>
<h2 id="heading-what-are-we-going-to-build-learn">What are we going to build / learn?</h2>
<p>We’re going to walk through two different example APIs to cover the concepts of Postman.</p>
<p>First, we’ll walk through some simple HTTP requests with a <a target="_blank" href="https://pokeapi.co/">public API for Pokémon</a>.</p>
<p>We’ll then use the Yoda Translator API for one part to demonstrate how to make specific HTTP requests.</p>
<p>Once we understand how the basics work, we’ll use the <a target="_blank" href="https://the-one-api.herokuapp.com/">Lord of the Rings API</a> to learn how authentication works with APIs. For this, you’ll need to register for a free account for an API key.</p>
<h2 id="heading-part-0-getting-set-up-with-postman">Part 0: Getting set up with Postman</h2>
<p>Before we get started, you’ll need <a target="_blank" href="https://www.postman.com/downloads/">Postman</a> in order to follow along with this walkthrough. The good news, is Postman is available for free on Mac, Windows, and Linux, so you should be able to find a version that works for you.</p>
<p>Get Postman: <a target="_blank" href="https://www.postman.com/downloads/">https://www.postman.com/downloads/</a></p>
<p>Once downloaded, go through the standard installation instructions, open it up, and we should be ready to go!</p>
<h2 id="heading-part-1-an-introduction-to-postman">Part 1: An introduction to Postman</h2>
<p>The first time you open up Postman you’ll immediately be shown a launchpad with a bunch of options to get started.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-launchpad.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>It might seem a bit overwhelming, but let’s break down some of the key concepts that we’ll need to know.</p>
<h3 id="heading-requests">Requests</h3>
<p>A request is kind of what it sounds like, it’s a specific API request. This will be a single type of request, whether it’s a GET or POST to a specific endpoint. You’ll want to create new requests for each type of endpoint which will allow you to move between them when testing.</p>
<h3 id="heading-collections">Collections</h3>
<p>A collection is a group of requests. This is handy for organizing your requests into different groups. This could be as simple as two totally different APIs (ie. Twitter vs Slack) or it could be two different groups of APIs for a single API (ie. Twitter Tweets API vs Twitter Accounts API).</p>
<h3 id="heading-authorization">Authorization</h3>
<p>Authorization is how requests are authenticated with an API, whether by a person making a request or by a computer making that request on your behalf. This commonly comes in the form of an API key which can be a static value assigned to your account or dynamically generated with tools like <a target="_blank" href="https://oauth.net/">OAuth</a>.</p>
<h3 id="heading-environments">Environments</h3>
<p>Environments will allow you to configure your endpoints to use specific variables that make it easier to use the same endpoints between different environments. For instance, you might have the same <code>/profile</code> endpoint on both your production and development environments, but they have different domains. Environments lets you manage a single request with a variable domain.</p>
<h3 id="heading-workspaces">Workspaces</h3>
<p>We won’t go too far into workspaces in this post, but it allows you to manage and organize different sets of collections. Imagine if you want to use Postman for both work and a personal project, you might have a Work workspace as well as a Personal workspace.</p>
<p>For the purposes of this article, we’ll be covering Requests, Collections, and Authorization.</p>
<h2 id="heading-part-2-creating-a-new-postman-request-to-get-info-about-squirtle">Part 2: Creating a new Postman request to GET info about Squirtle</h2>
<p>Now that we have a better understanding of the different terminology, let’s actually create a request.</p>
<p>At the top left of the UI you should see a but orange button that says <strong>New</strong>. Go ahead and click that and then select <strong>Request</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-create-new-request.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Before we get into the request itself, it requests a few things.</p>
<p>This first thing requires is a name. We’re going to start off by requesting information about the Pokémon Squirtle, so let’s name this “Pokémon - Squirtle”.</p>
<p>It also requires a collection, so click <strong>Create Collection</strong> and let’s name the collection “My Favorite Pokémon”.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-configure-new-request.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click the orange checkmark button next to the collection name then hit <strong>Save</strong>.</p>
<p>At this point we’ll have a new request, so let’s build that request.</p>
<p>There are two things we’ll first need to fill out for our first request:</p>
<ul>
<li><strong>Request type:</strong> GET, POST, PUT, etc - we’ll use GET</li>
<li><strong>Request URL:</strong> The endpoint for your API request - for our request we’ll use <a target="_blank" href="https://pokeapi.co/api/v2/pokemon/squirtle/">https://pokeapi.co/api/v2/pokemon/squirtle/</a></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-squirtle-pokemon-get.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And once you make sure those are correct, you can simply hit the blue <strong>Send</strong> button on the right and we’ve successfully made our first request!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-squirtle-pokemon-get-success.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We immediately get a few things we can see:</p>
<ul>
<li><strong>Body:</strong> at the bottom we should now see the response body of the API request. For our Squirtle API, we should have a JSON object with data like <code>abilities</code>, <code>base_experience</code>, and <code>forms</code>.</li>
<li><strong>Status:</strong> on the right, we should see the HTTP status code. “200 Ok” is a good sign and it means it was successful!</li>
<li><strong>Time:</strong> simply how long the request took to finish</li>
<li><strong>Size:</strong> the size in KB (in our example) of the response data</li>
</ul>
<p>You can also hover over Status, Time, and Size and get a more in depth look at each option.</p>
<p>So we made our first request!</p>
<p>Once thing to notice before we move on is that our request looks like it’s in a browser tab. If we’re done with that particular request, we can close the tab and click <strong>Save</strong> to make sure all of our changes are there for next time!</p>
<h2 id="heading-part-3-creating-a-collection-of-requests-in-postman-for-the-pokeapi">Part 3: Creating a collection of requests in Postman for the PokéAPI</h2>
<p>Now that we’ve created a request, let’s create a collection of them. Technically we already had to create a new collection for Part 2, but we’ll create a new one to learn how collections themselves work.</p>
<p>At the top left of the UI, click the orange <strong>New</strong> button again and select <strong>Collection</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-create-new-collection.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Similar to a request, it asks for a name so let’s call this “PokéAPI”. Optionally you can add a description, then click <strong>Create</strong> at the bottom.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-configure-new-collection.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On the left, you’ll now see your collection. You can select and expand the folder since we’ll be working with it.</p>
<p>Before we add a request, the PokéAPI has different types of requests, so it makes sense to organize it a little more thoroughly. So let’s click the three dots next to the PokéAPI collection and select <strong>Add Folder</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-add-folder-to-collection.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Similar to the others, this asks for a name. Folders are kind of like collections inside of a collection, so you get similar options. Let’s name this one “Pokémon” and click the orange <strong>Save</strong> button like before.</p>
<p>Now let’s add our requests! First, click the three dots next to the Pokémon folder, similar to how we added a folder to the collection, but this time select <strong>Add Request</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-add-request-to-collection.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Let’s name this request “Pokemon”. While it might be confusing that we have a Pokemon request inside of the Pokémon folder, Pokemon is just one of the endpoints of the Pokémon group.</p>
<p>Now, let’s use the same exact API that we used with our Squirtle request before:</p>
<ul>
<li><strong>Request Type:</strong> GET</li>
<li><strong>Request URL:</strong> <a target="_blank" href="https://pokeapi.co/api/v2/pokemon/squirtle/">https://pokeapi.co/api/v2/pokemon/squirtle/</a></li>
</ul>
<p>And similar to before, when we hit the blue <strong>Send</strong> button, we should see a successful request!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-successful-get-request-squirtle.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now let’s add another request. Follow the same process as before to create a new request under the PokéAPI Pokémon folder and let’s name this request “Abilities”.</p>
<p>If you scroll through the response from the first Squirtle endpoint, you see a lot of other API urls. At the top, we have <code>abilities</code> and we have two different ones — “torrent” and “rain-dish”.</p>
<p>Choose your favorite Squirtle ability and copy the <code>url</code> value into the new Abilities request we just created, I’m going to use <code>rain-dish</code>.</p>
<p>We can leave the Request Type as GET, hit the blue <strong>Send</strong> button, and we can again see a successful response!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-successful-request-squirtle-abilities.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here we get a lot of information about our Squirtle ability Rain Dish and some of the details come in different languages which is cool!</p>
<p>So now we have a new PokéAPI collection with a Pokémon folder representing the group of Pokémon API endpoints including Pokemon and abilities.</p>
<p>We’re going to stop Part 3 with those 2 requests, but feel free to continue on and add as many of the PokéAPI requests as you’d like!</p>
<h2 id="heading-part-4-making-post-requests-with-postman-to-translate-sentences-to-sound-like-yoda">Part 4: Making POST requests with Postman to translate sentences to sound like Yoda</h2>
<p>So far we’ve only made GET requests, but what if we wanted to make a POST request where we need to actually send some data?</p>
<p>For making a POST request, we’re going to use the Yoda Translator API from funtranslations.com. While this API only takes a single parameter, it’s still a good public endpoint we can use to understand the concept.</p>
<p>First, let’s create a new collection with a new request:</p>
<ul>
<li><strong>Collection:</strong> Fun Translations</li>
<li><strong>Request:</strong> Yoda</li>
</ul>
<p>This time, instead of a GET request, our request configuration will be:</p>
<ul>
<li><strong>Request Type:</strong> POST</li>
<li><strong>Request URL:</strong> <a target="_blank" href="https://api.funtranslations.com/translate/yoda">https://api.funtranslations.com/translate/yoda</a></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-new-request-yoda-api.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now this time, if we hit the blue <strong>Send</strong> button, we’ll notice we don’t get a successful 200 response, we get a 400!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-yoda-api-bad-request.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We never actually set up any data to be posted to the API and it requires that data, so let’s add it.</p>
<p>Right below the <strong>Request URL</strong>, click <strong>Body</strong>. Then instead of none, select <strong>raw</strong> as the body type. Finally, on the far right of the types, change <strong>Text</strong> to <strong>JSON</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-yoda-request-body-type.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Then, in the space below it, you can add the following:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"text"</span>: <span class="hljs-string">"Hello, I am learning how to test APIs with Postman!"</span>
}
</code></pre>
<p>And now click the blue <strong>Send</strong> button again and we get a successful response!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-successful-post-body-yoda-api.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We can apply this concept to pretty much any API. Postman doesn’t only permit you to post JSON, it allows you to use the other formats that we see listed in the Body Type section, meaning you have a lot of options depending on what the API you’re using requires.</p>
<h2 id="heading-part-5-authenticating-requests-to-the-lord-of-the-rings-api-with-an-api-key">Part 5: Authenticating requests to the Lord of the Rings API with an API Key</h2>
<p>For the rest of the walkthrough, we’re going to use the Lord of the Rings API.</p>
<p>First up, the Lord of the Rings API requires authentication in order to make requests using an API key. So to start, you’ll before we dive in, you’ll need to go <a target="_blank" href="https://the-one-api.herokuapp.com/sign-up">create a free account</a>.</p>
<p><a target="_blank" href="https://the-one-api.herokuapp.com/sign-up">https://the-one-api.herokuapp.com/sign-up</a></p>
<p>Once you sign up and log in, the first thing you’ll see is your API key! Either copy this key down or remember where you can find it for later. If you leave the page, you can always grab it by navigating to <strong>Welcome</strong> and then <strong>Account</strong> in the navigation of the API website.</p>
<p>To get started, let’s first create a new collection and request:</p>
<ul>
<li><strong>Collection:</strong> Lord of the Rings</li>
<li><strong>Folder:</strong> Movie</li>
<li><strong>Request:</strong> All Movies</li>
<li><strong>Request Type:</strong> GET</li>
<li><strong>Request URL:</strong> <a target="_blank" href="https://the-one-api.herokuapp.com/v1/movie">https://the-one-api.herokuapp.com/v1/movie</a></li>
</ul>
<p>Once you’re set with the above, click <strong>Send</strong>, and you’ll notice immediately it gives a response that says 401 and that it’s unauthenticated.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-unauthorized-request-lord-of-the-rings-api.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Because this API requires the API key, this is exactly what we expected. So let’s click on the <strong>Authorization</strong> tab. We can then select a <strong>Type</strong> of <strong>Bearer Token</strong>, and on the right, we can paste in our key that we just set up with the Lord of the Rings API.</p>
<p>And as soon as we hit <strong>Send</strong>, we now see a successful response!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-authorized-successful-lord-of-the-rings-api-request.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This worked really great, but what if we have a bunch of requests that use a single key. Do we have to manage that on each request?</p>
<p>Instead of managing it on each individual request, we can manage it on the collection. Let’s first build another request.</p>
<p>Under our Lord of the Rings collection and in the Movie folder, create a new request:</p>
<ul>
<li><strong>Request:</strong> Quote by Movie ID</li>
<li><strong>Request Type:</strong> GET</li>
<li><strong>Request URL:</strong> <a target="_blank" href="https://the-one-api.herokuapp.com/v1/movie/%7Bid%7D">https://the-one-api.herokuapp.com/v1/movie/{id}</a></li>
</ul>
<p>In this request, let’s use an ID from the response of the first request, I’m going to use <code>5cd95395de30eff6ebccde5b</code> which is the ID of The Two Towers, so the request URL will look like:</p>
<pre><code>https:<span class="hljs-comment">//the-one-api.herokuapp.com/v1/movie/5cd95395de30eff6ebccde5b</span>
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-inherit-authorization-from-parent.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, instead of setting our token in the request Authorization, we’re going to leave the type as <strong>Inherit auth from parent</strong>. Click on the three dots next to the collection and select <strong>Edit</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-edit-collection.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here, we’re going to do the same exact thing we did with the first request but on the Collection configuration. Select the <strong>Authorization</strong> tab, under type select <strong>Bearer Token</strong>, and in the <strong>Token</strong> field again paste your token.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-add-authorization-api-key-token-to-collection.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Finally, click <strong>Update</strong> and hit the blue <strong>Send</strong> button again and we can see a successful request!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-authorized-request-inherit-token-from-parent.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We can now go back to our All Movies request and update the Authorization to use a Type of Inherit auth from parent and it should still continue to work!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/postman-successful-request-lord-of-the-rings-api.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-else-can-we-do-with-postman">What else can we do with Postman?</h2>
<p>While I covered a lot of the basics, there’s quite a lot more you can do with Postman. Here are a few of my favorites.</p>
<h3 id="heading-environment-variables">Environment Variables</h3>
<p>If you’re working as a developer on a project, it’s likely that your team uses multiple environments, such as a development and production environment. Instead of creating and maintaining completely separate requests, you can add an environment variable and instead change that variable when switching between environments!</p>
<p>Variables apply to many scenarios, but that’s a common use. Check out Postman’s docs to learn how.</p>
<p><a target="_blank" href="https://learning.postman.com/docs/postman/variables-and-environments/variables/">https://learning.postman.com/docs/postman/variables-and-environments/variables/</a></p>
<h3 id="heading-import-and-export-collections-and-data">Import and Export Collections and Data</h3>
<p>A great thing about Postman is once you have your requests all organized, you can export them for others to use. This also means that you can import collections from other team members. This makes it much easier to make sure everyone’s using the same collection.</p>
<p>Bonus: you can even store these files in a Git repository, as they’re just JSON.</p>
<p>But keep in mind - if you’re using Authorization on the collection like we went over in this guide, you’ll want to make sure you don’t include that when exporting your collection.</p>
<p><a target="_blank" href="https://learning.postman.com/docs/postman/collections/importing-and-exporting-data/">https://learning.postman.com/docs/postman/collections/importing-and-exporting-data/</a></p>
<h3 id="heading-automated-testing">Automated testing</h3>
<p>Once you have a set of requests in a collection and even better, if you’re storing them in Github, you can begin to use those requests as part of a way to manage automated testing of your API.</p>
<p>While there are a few solutions for doing this, Postman includes a Collection runner built right into the app and <a target="_blank" href="https://learning.postman.com/docs/postman/collection-runs/command-line-integration-with-newman/">Newman</a> is a command line tool that lets you run tests right from the terminal.</p>
<p><a target="_blank" href="https://www.postman.com/use-cases/api-testing-automation/">https://www.postman.com/use-cases/api-testing-automation/</a></p>
<h2 id="heading-whats-your-favorite-way-to-test-and-play-with-apis">What’s your favorite way to test and play with APIs?</h2>
<p><a target="_blank" href="https://twitter.com/colbyfayock">Share with me on Twitter!</a></p>
<div id="colbyfayock-author-card">
  <p>
    <a href="https://twitter.com/colbyfayock">
      <img src="https://res.cloudinary.com/fay/image/upload/w_2000,h_400,c_fill,q_auto,f_auto/w_1020,c_fit,co_rgb:007079,g_north_west,x_635,y_70,l_text:Source%20Sans%20Pro_64_line_spacing_-10_bold:Colby%20Fayock/w_1020,c_fit,co_rgb:383f43,g_west,x_635,y_6,l_text:Source%20Sans%20Pro_44_line_spacing_0_normal:Follow%20me%20for%20more%20JavaScript%252c%20UX%252c%20and%20other%20interesting%20things!/w_1020,c_fit,co_rgb:007079,g_south_west,x_635,y_70,l_text:Source%20Sans%20Pro_40_line_spacing_-10_semibold:colbyfayock.com/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_68,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_145,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_222,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/w_300,c_fit,co_rgb:7c848a,g_north_west,x_1725,y_295,l_text:Source%20Sans%20Pro_40_line_spacing_-10_normal:colbyfayock/v1/social-footer-card" alt="Follow me for more Javascript, UX, and other interesting things!" width="2000" height="400" loading="lazy">
    </a>
  </p>
  <ul>
    <li>
      <a href="https://twitter.com/colbyfayock">? Follow Me On Twitter</a>
    </li>
    <li>
      <a href="https://youtube.com/colbyfayock">?️ Subscribe To My Youtube</a>
    </li>
    <li>
      <a href="https://www.colbyfayock.com/newsletter/">✉️ Sign Up For My Newsletter</a>
    </li>
  </ul>
</div>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
