<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aniket Bhattacharyea</title>
    <description>The latest articles on DEV Community by Aniket Bhattacharyea (@heraldofsolace).</description>
    <link>https://dev.to/heraldofsolace</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F27791%2F94532916-fa4c-4972-a381-f219844eff22.png</url>
      <title>DEV Community: Aniket Bhattacharyea</title>
      <link>https://dev.to/heraldofsolace</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/heraldofsolace"/>
    <language>en</language>
    <item>
      <title>How to Be the Best Tutorial Writer in the Room</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Wed, 26 Aug 2026 12:34:57 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/how-to-be-the-best-tutorial-writer-in-the-room-1hb7</link>
      <guid>https://dev.to/heraldofsolace/how-to-be-the-best-tutorial-writer-in-the-room-1hb7</guid>
      <description>&lt;p&gt;There's a special kind of pain reserved for people who follow a tutorial at 11 PM and hit a wall on step 4 because the author renamed a variable between step 3 and step 5 and never told you. You stare at your terminal. The error makes no sense. You check the code four times. Eventually you realize the tutorial was never run, start to finish, by a human being — it was assembled from memory, vibes, and whatever the author thought they did six months ago.&lt;/p&gt;

&lt;p&gt;I don't want to be that person, and after enough tutorials spanning across clients like Docker, Auth0, JetBrains, and others, I've settled on a short list of things that actually separate a tutorial that works from one that just looks like it does. None of this is exotic. It's mostly discipline, applied at the right moments, and a healthy suspicion of your own memory.&lt;/p&gt;

&lt;p&gt;I'm not going to talk about the same old boring advices like "Improve your English, use your own language." I myself am not a native English speaker, and I write very simple sentences -- no flowery language, no complicated prose -- and I can tell you that no client has ever questioned me about that. I'm going to give you some real advice, battle tested with real clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; All of the points below are my own advice, learned from my experience with &lt;a href="https://draft.dev" rel="noopener noreferrer"&gt;Draft.dev&lt;/a&gt;. These aren't absolute gospels, and you shouldn't take them as such.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Clearly Define the Article Requirement
&lt;/h2&gt;

&lt;p&gt;Before you write a single sentence, know what the article is actually supposed to deliver. Not the topic — the &lt;em&gt;shape&lt;/em&gt;. Is this a 500-word "here's a neat trick" post, or a 3,000-word soup-to-nuts build? Does it stand alone, or does it assume the reader already has your last three articles' worth of setup done? Is the deliverable a working demo app, or is the demo just scaffolding to explain one specific concept?&lt;/p&gt;

&lt;p&gt;I cannot stress enough the advantages of creating an outline before you start writing. I prefer to collect all the information I can from my clients, including details about their company, their product, and the topic, and create an outline with it. An outline gives you the basic shape of the article and tells you what to do, and what not to do.&lt;/p&gt;

&lt;p&gt;You can download a copy of the "content plan" template I use with my clients &lt;a href="https://5c50039a.sibforms.com/serve/MUIFADrkYEMLnNnH_XqL4flqF5UtouWdBbYHNLysLADPyIBfCc9nkYpHOC0ZDWJ6BPNHfgZUXJnE-5f6QMmMn8BZG6ckTPAVAyYQx9jqQq1VMgzapkch_3fPGd1XNKbtIooX-g1peVHBI0e39XC3RA_n-cyT3GbP6-ICIf_kFGqUOveW6UR5ulka5N-DxykCdmKps3c43B-Q10pl" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create a Compelling Scenario
&lt;/h2&gt;

&lt;p&gt;Once you know the requirement and before you touch the audience question, decide what the demo app is actually &lt;em&gt;about&lt;/em&gt;. This is where a lot of otherwise-solid tutorials go flat, because the author reaches for the most generic possible container for the feature they're demonstrating.&lt;/p&gt;

&lt;p&gt;Say you're writing a tutorial about authentication and authorization. The lazy version of the prompt to yourself is "let's create a basic app and add authn and authz." Technically accurate, and it produces exactly what it promises: a to-do list or a "hello, dashboard" page with a login screen bolted onto it, and a role check somewhere that simply prints your roles on an HTML page. Nobody remembers that tutorial a week later, because there was nothing to remember.&lt;/p&gt;

&lt;p&gt;Compare that to: "let's build a CMS where anyone can sign up, write and publish articles, but can't delete someone else's article." Same underlying feature -- authn and authz -- but now it's a scenario, not a checklist item, and the scenario does several things the generic version can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It forces you to define real roles before you write any code&lt;/strong&gt;, because "someone else's article" only means something once you've decided who owns what. That's authorization design, not authorization theater.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It gives you a built-in negative test case for free.&lt;/strong&gt; "User B tries to delete User A's article and gets blocked" is a genuinely satisfying moment to demonstrate -- readers get to &lt;em&gt;see&lt;/em&gt; the authorization actually stop something, instead of trusting your word that the middleware is wired up correctly. A generic "protected route" demo never gets to prove a negative like that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It reads well in a title and in a search result.&lt;/strong&gt; "How to Build a CMS with Role-Based Permissions" gets clicked. "How to Add Authn and Authz to a Basic App" reads like a boring text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It gives your screenshots something to be &lt;em&gt;of&lt;/em&gt;.&lt;/strong&gt; A login form looks like every other login form ever screenshotted. An article list with a greyed-out delete button next to someone else's post is an image that actually explains the feature at a glance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A compelling scenario is really the requirement and the audience decisions from the last two points, made concrete enough to build. If your scenario is interesting enough, half of your outline writes itself, because the scenario itself implies the steps: sign-up, write a post, publish it, try to break it as someone else. Boring scenarios don't do that work for you -- you end up having to invent steps to pad the tutorial out, and padding is always the first thing a reader notices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: A compelling scenario doesn't have to be elaborate. It just has to be specific enough that a stranger, reading only the title, already has a rough guess of what the finished thing does. Don't get carried away and try to make it too complex.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Clearly Define the Audience
&lt;/h2&gt;

&lt;p&gt;This ties to the previous point as this is part of the outline for me, but it's so important that this requires its own paragraph. This is the single biggest lever on how much detail you show, and it's the one people skip because they think they already know. "Developers" is not an audience. "Someone who's shipped a REST API before but has never touched OAuth" is an audience.&lt;/p&gt;

&lt;p&gt;Audience determines everything downstream: whether you explain what a JWT is or just link to a reference, whether you spell out &lt;code&gt;npm install&lt;/code&gt; or assume it, whether you show the full error message or just say "if you see an auth error, check your redirect URI." Undershoot the audience and you lose beginners on page one. Overshoot it and your expert reader skims past three paragraphs of &lt;code&gt;npm install&lt;/code&gt; ceremony looking for the one paragraph that matters, and maybe doesn't find your article again next time.&lt;/p&gt;

&lt;p&gt;I write a one-line audience note to myself before drafting — something like &lt;em&gt;"assumes React familiarity, zero assumption of Next.js server actions"&lt;/em&gt; — and I keep it visible while writing. Every time I'm about to explain (or skip explaining) something, I check it against that line instead of against my own head, because my own head thinks everything is obvious by the third read-through.&lt;/p&gt;

&lt;p&gt;Here's a concrete example of how audience shapes the article details for me. Imagine I am teaching how to create a Next.js app. If my audience is a beginner who has zero to minimal Next.js experience, I'll start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run the following command to create a Next.js app:

npx create-next-app@latest my-app --yes

Now move into the directory by running "cd my-app" and start the dev server by running "npm run dev"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if my audience is intermediate or expert in Next.js, I'll write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a Next.js app and start it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note: There is one situation where I'll spell out the details irrespective of the audience. That is, if I'm doing something that is not the default. For example, I'm using a different version of a tool, or passing a non-default option to the command.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build the App First, to Know It Works
&lt;/h2&gt;

&lt;p&gt;This should not need saying, and yet. Before you write a word of prose, build the actual thing, end to end, on your machine, from a clean slate if you can manage it. Not "I did something like this six months ago and it's basically the same." Build it now with the versions of the tools that exist.&lt;/p&gt;

&lt;p&gt;The reason this matters isn't pride — it's that software drifts under you while you're not looking. An SDK bumps a major version and renames a constructor argument. A framework moves from pages to app router. A CLI flag gets deprecated with a warning nobody reads. If your mental model of "how this works" is even a few months stale, you will confidently write down something that used to be true.&lt;/p&gt;

&lt;p&gt;This happened to me on my own side project, actually — not an article, but close enough to sting the same way. I stubbed an external client in my tests, the tests passed, and I moved on trusting that green checkmark. Then I ran the app for real and discovered that the library had changed its constructor from keyword arguments to positional ones between the version I remembered and the installed version. My stub accepted anything, so nothing caught it. The lesson transfers directly to writing: a test (or an article) that was never run against the real thing isn't verification; it's a nice story you're telling yourself.&lt;/p&gt;

&lt;p&gt;This is also useful when you're writing about a client's product. Many clients -- typically young startups -- don't pay as much attention to their documentation as they do to their marketing. Often, their docs will say "do X to get Y," but when you actually try it, you'll discover it doesn't work. Either the feature doesn't exist, but the Sales team got excited and claimed boldly that it's available, or that the docs were hallucinated by an AI, or maybe it worked at some point, but something changed, and nobody updated the docs.&lt;/p&gt;

&lt;p&gt;That's why build it first. Confirm it works. &lt;em&gt;Then&lt;/em&gt; start writing about it. As you build the app, try to develop a mental model of the steps you will show in the article, in what order. Building the app will tell you everything you need to do to get it working. You can write it down or memorize it. Either way, you now have a solid foundation of what works and what doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Build the App Once More as You Write
&lt;/h2&gt;

&lt;p&gt;Here's the part people skip because they just did step 4, so surely they remember what happened. You don't, not precisely enough. The gap between "I built this, and it worked" and "I wrote down, in order, the exact commands and code that make it work" is wider than it feels.&lt;/p&gt;

&lt;p&gt;As you write each step, actually perform that step again, right then, and paste in what genuinely happened — not what you remember happening. This catches the stuff that ruins tutorials: the config value you tweaked halfway through and forgot to write down, the folder you created out of order, the environment variable you set in one terminal tab and never mentioned. Writing and rebuilding in lockstep turns your article into a transcript of something real rather than a summary of something remembered.&lt;/p&gt;

&lt;p&gt;Building the app as you write the article will also let you capture screenshots and outputs immediately, making the article feel more alive. More on this later.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Build the App One More Time as You Go Through the Final Copy
&lt;/h2&gt;

&lt;p&gt;By the time you're proofreading, you're reading your own writing, and your own writing is exactly the thing you're least equipped to catch errors in — you know what you &lt;em&gt;meant&lt;/em&gt;, so your eyes helpfully autocorrect what you &lt;em&gt;wrote&lt;/em&gt;. A missing &lt;code&gt;cd&lt;/code&gt; into the right directory, a code block that's missing the import you added at the last minute, a step that got reordered when you moved a paragraph for flow and never noticed the code above it now depends on something that hasn't happened yet.&lt;/p&gt;

&lt;p&gt;The fix is unglamorous: go through the final copy one more time, but instead of just reading it, &lt;em&gt;execute it&lt;/em&gt;, literally, in a fresh terminal, following your own instructions exactly as written rather than as intended. Not "run something like this" — copy the actual commands out of your own article and run those. This is the pass that catches the copy-paste artifact from an earlier draft, the snippet that references a variable you renamed three sections ago, the step you accidentally deleted while editing for length.&lt;/p&gt;

&lt;p&gt;Three build passes sound like a lot until you consider that each one is catching a completely different class of mistake: the first confirms the thing is &lt;em&gt;possible&lt;/em&gt;, the second confirms your &lt;em&gt;narration&lt;/em&gt; of it is accurate, the third confirms your &lt;em&gt;edited, final&lt;/em&gt; narration is still accurate after you've been moving paragraphs around for an hour.&lt;/p&gt;

&lt;p&gt;The next few tips are "cosmetic." They don't improve the correctness of your article, but they make it better by a huge margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Show Full File Paths in Code Snippets
&lt;/h2&gt;

&lt;p&gt;A code block that starts with &lt;code&gt;export default function Page() {&lt;/code&gt; and nothing else creates more questions than it answers. Where does this go? Is it a new file? Does it replace something? Which folder, at what level, relative to what?&lt;/p&gt;

&lt;p&gt;Show the actual path, every time, even when it feels repetitive: &lt;code&gt;frontend/src/app/api/quote/route.ts&lt;/code&gt;, not &lt;code&gt;route.ts&lt;/code&gt;. Especially in a monorepo, or anything with more than one plausible &lt;code&gt;src/&lt;/code&gt;, guessing the path is exactly the kind of low-value puzzle that makes a reader feel dumb for a reason that has nothing to do with their skill and everything to do with your laziness. Full paths cost you only a few extra words per snippet and save the reader a headache and a moment of quiet resentment toward you specifically.&lt;/p&gt;

&lt;p&gt;Here you might argue that if the audience is an expert, surely they can figure out where to put the code if I say "the quote route" instead of &lt;code&gt;frontend/src/app/api/quote/route.ts&lt;/code&gt;, and that's a valid argument. I prefer to reduce confusion as much as possible, so I almost always mention the full path, regardless of the audience. Better safe than sorry.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Clearly Indicate Where to Put the Code
&lt;/h2&gt;

&lt;p&gt;Adjacent to the path problem, but distinct: a snippet with a correct path can still land in the wrong &lt;em&gt;place&lt;/em&gt; inside that file. "Add this" is not an instruction. "Add this snippet at the top of &lt;code&gt;frontend/src/lib/strapi.ts&lt;/code&gt;, right after the existing imports" is. "Replace the &lt;code&gt;return&lt;/code&gt; statement at the bottom of the &lt;code&gt;handler&lt;/code&gt; function with the following" is. Say whether it's a new file, an insertion, or a replacement, and say exactly where the insertion point is relative to something the reader can already see in their own editor.&lt;/p&gt;

&lt;p&gt;This matters more, not less, as snippets get shorter. A ten-line code block dropped in the middle of a paragraph with no context is the single most common way I've seen a technically correct tutorial become a frustrating one — not because the code was wrong, but because the reader had to guess what to do with it and guessed wrong.&lt;/p&gt;

&lt;p&gt;Don't refer to code by line number, like "Paste this code after line 15." Even if a reader has been following your article and has been copy pasting the exact codes from your article, they may end up having different line numbers. The most common reason is IDE auto-formatting that automatically formats the code as they paste. The reader themselves may decide to format the code in a different way. Always indicate lines via some sort of marker, like "Paste this after the closing  tag."&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Provide Screenshots, Outputs and a Repo
&lt;/h2&gt;

&lt;p&gt;Three different deliverables, all necessary in my opinion.&lt;/p&gt;

&lt;p&gt;The screenshot has two jobs. One, it is proof, aimed at the reader following along: "here's what it should look like when you get this right," so they can check their own result against something concrete instead of just trusting that the code compiled. It's also aimed at people skimming to decide whether the article is worth their time at all — a screenshot of a real, working thing does more convincing in half a second than three paragraphs of description.&lt;/p&gt;

&lt;p&gt;It's second purpose is to give the reader a visual cue. A screenshot of the web page with the button circled in red is easier and faster to understand than "Click the small + button in the top right corner of the page."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: Two things you must never forget with screenshots: Point out the interesting things (for example, circle the button you want the reader to click, or put an arrow next to it) and include an alt text and caption.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The code outputs have a similar responsibility as the screenshots. They provide a visual indicator. If the user gets the same output as you, they will know they are on the right path. To switch things up a bit (or to save yourself for pasting a huge output), you can also indicate what to look for. For example, "If the &lt;code&gt;status&lt;/code&gt; command shows &lt;code&gt;Running&lt;/code&gt;, you're good to go.&lt;/p&gt;

&lt;p&gt;The repo is the escape hatch, aimed at the reader who's stuck. Tutorials are read out of order, skimmed, half-followed, interrupted by lunch. A linked repo at the final commit state means "I did something slightly wrong three steps back" doesn't have to become "I give up" — it becomes "let me diff my folder against theirs." Also, a repo is a better proof of "It works" than a screenshot. Someone can simply check out the repo and test whether the app works or not before deciding to read your article. I link a repo on effectively everything now, and it's the single piece of feedback I get thanked for most often, more than any clever explanation in the prose itself.&lt;/p&gt;

&lt;p&gt;I link the repo twice. The first link is before starting the tutorial section, saying something like "All the code I am using in this article can be found in this repo." This gives the reader the escape hatch mentioned earlier before they actually start investing into the tutorial. The second link is after the tutorial is over, with something like "You can check out the full app in this repo." This works as a "tie everything together" moment. If they didn't check out the repo earlier, they can do so now, and compare their app with mine.&lt;/p&gt;

&lt;p&gt;A couple of principles I follow when creating a repo for my tutorials:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The repo should be the exact same as what my article produces. I basically use the repo I got from following step 6. This makes sure someone who is reading the article gets the exact same app as someone who simply checked out the repo. I don't do any "final touches" or improvements over what the article has. If I must, I make sure it's not an extensive functionality change. For example, I used basic CSS in the article, but that looks ugly, so I added some CSS styles in the final app. This is ok. But using SQLite in the article because it's easy, but changing it to PostGreSQL in the repo is too big of a change. Not cool.&lt;/li&gt;
&lt;li&gt;Sometimes I may provide a starter version of an app. In case the initial setup is too long, I provide a small prebuilt app that I want the reader to use as a starting point for the article. In that case, I provide the starter version and the final version in different branches of the same repo. Starter version in &lt;code&gt;starter&lt;/code&gt; and final version in &lt;code&gt;main&lt;/code&gt;, or starter version in &lt;code&gt;main&lt;/code&gt; and final version in &lt;code&gt;final&lt;/code&gt; -- choose whatever suits you.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  10. Provide a List of Prerequisites
&lt;/h2&gt;

&lt;p&gt;How many times have you started reading a tutorial and halfway through you realized, "Looks like I need an AWS account for this. No one made that clear!" You don't want to be the person who leaves their readers in the dark about what they need for the tutorial. That's why I add a prerequisites section before the tutorial actually starts. There, I list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The tools that are needed, e. g. Docker, Node.js&lt;/li&gt;
&lt;li&gt;The versions of the tools, where to install them, and which versions I used.&lt;/li&gt;
&lt;li&gt;If any third-party tools or services are needed, and where to sign up for them.&lt;/li&gt;
&lt;li&gt;If the tools behave sufficiently differently based on OS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's one example prerequisite section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;To follow along with this tutorial, you will need:
&lt;span class="p"&gt;
*&lt;/span&gt; An Auth0 account. You can sign up for free &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;here&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;link-to-auth0-registration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.
&lt;span class="p"&gt;*&lt;/span&gt; The latest version of Node.js. This article uses version 22.0.1. You can download Node from &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;here&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;link-to-node-website&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Be the Tutorial Nobody Has to Debug
&lt;/h2&gt;

&lt;p&gt;None of the ten points above are secret techniques picked up from some technical writing seminar. Every one of them is really the same instinct wearing a different hat: don't trust what you think you know, trust what actually happened five minutes ago on your own machine. Every "vibes" tutorial I've ever gotten burned by skipped some of these -- usually not out of laziness, but because it &lt;em&gt;felt&lt;/em&gt; done already, and the deadline was in three hours.&lt;/p&gt;

&lt;p&gt;That's the trap, and it's the same trap every time. A tutorial reads as finished to its author long before it's finished for the reader, because the author already knows what everything means. The reader doesn't. Every point above is just a way of closing that gap -- one more rebuild, one more real file path, one more screenshot, one more line telling them what account to go create before they get 60% of the way in and hit a wall you knew was coming.&lt;/p&gt;

&lt;p&gt;I've written these for clients and the ones that got read, bookmarked, and pasted into someone's internal wiki were never the ones with the cleverest explanation of &lt;em&gt;why&lt;/em&gt; something works. They were the ones that just worked, on the first try, for a total stranger, with no help from me sitting next to them. That's the whole job. Everything else is decoration on top of that one requirement.&lt;/p&gt;

&lt;p&gt;So if you want to be the best tutorial writer in the room, here's the short version: be the one whose tutorial nobody has to debug.&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>programming</category>
      <category>writing</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Privacy-Focused Social Media: What It Taught Me</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Fri, 07 Aug 2026 08:43:10 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/building-a-privacy-focused-social-media-what-it-taught-me-3ogi</link>
      <guid>https://dev.to/heraldofsolace/building-a-privacy-focused-social-media-what-it-taught-me-3ogi</guid>
      <description>&lt;p&gt;I've been working on a side project called Vidha for the past few months. It's a social platform with a somewhat unusual premise: instead of choosing between "public" and "private" like every other platform, you control exactly who sees what about you, and it's not just "show my profile picture to my friends and hide it from the public." You can show different versions of every piece of information you enter in Vidha. You can show a formal picture in suits to your office colleagues, the picture you took on your vacation to your close friends, and the picture with your fiancée to your family — all at the same time. Each attribute is scoped to an audience you define.&lt;/p&gt;

&lt;p&gt;I want to be upfront: this is purely a personal educational project. There's no company behind it, no funding, no roadmap I'm pitching to anyone. I built it to learn. Specifically, I wanted to understand what actually goes into building a social platform — not the surface-level stuff, but the data model decisions that haunt you at 2 am three weeks in.&lt;/p&gt;

&lt;p&gt;This article is about the things that surprised me, the decisions I made and would change, and the problems that turned out to be much harder than they looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;Before diving in, here's what I built this with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rails 8.1 with Ruby 3.4.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hotwire (Turbo + Stimulus), Tailwind CSS v4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Devise (with &lt;code&gt;:confirmable&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Activity Feeds&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GetStream.io (&lt;code&gt;stream-ruby&lt;/code&gt; v6)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Background Jobs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Solid Queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rich Text&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ActionText&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Railway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dev Environment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nix devenv&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I deliberately chose a "boring" stack. I'm a big fan of Rails because of how robust it is, and how easy it is to get started. The hard problem here is the domain — the visibility system, the feed logic, the moderation — not the infrastructure. Rails is good at getting out of the way, and that's what I needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Idea: Audiences, Not Privacy Modes
&lt;/h2&gt;

&lt;p&gt;The central concept in Vidha is &lt;strong&gt;groups&lt;/strong&gt;. Every user has named groups — "Public", "Followers", "Close Friends", whatever they want. Every profile attribute (display name, bio, location, profile picture) is associated with a group rather than directly with the profile. When someone views your profile, the app figures out which group they belong to and shows only the attributes you've configured for that group.&lt;/p&gt;

&lt;p&gt;The app only requires your email and a username to register. You're free to define your own attributes, such as &lt;strong&gt;Location&lt;/strong&gt;, &lt;strong&gt;Job&lt;/strong&gt;, etc., and assign a type, such as &lt;strong&gt;String&lt;/strong&gt;, &lt;strong&gt;Image&lt;/strong&gt;, &lt;strong&gt;Number&lt;/strong&gt;, or &lt;strong&gt;URL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffckjlryy6wyrc9qixeo3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffckjlryy6wyrc9qixeo3.png" alt="Creating a new attribute definition" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you define a new attribute, you can assign different values to it for different groups.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70ptd2qw7u9e16m9xaow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70ptd2qw7u9e16m9xaow.png" alt="Adding attribute values" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The visibility resolution logic looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;visible_attributes_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;group_for_viewer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;

  &lt;span class="n"&gt;attribute_definitions&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:attribute_values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;attribute_values: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;group_id: &lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pluck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"attribute_definitions.name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"attribute_values.value"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the group resolution itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;group_for_viewer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;public_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;is_public: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;

  &lt;span class="n"&gt;membership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GroupMembership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;member_id: &lt;/span&gt;&lt;span class="n"&gt;viewer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;owner_id: &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;membership&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three cases: an unauthenticated visitor gets the public group, an authenticated user with a membership gets their group, and an authenticated user with no membership also gets the public group. The fallback to public means your public attributes are always visible to logged-in users who don't follow you. That's a deliberate design decision — if you've put something in your public group, you want strangers to see it.&lt;/p&gt;

&lt;p&gt;There are a few "special" attributes (for now, only &lt;strong&gt;Display name&lt;/strong&gt; and &lt;strong&gt;Profile picture&lt;/strong&gt;) that the app uses automatically. Again, you're free to skip them, in which case you get a placeholder value. If you keep your display name undefined, it'll show up as "Anonymous". If you keep your profile picture set to undefined, it'll show a placeholder image.&lt;/p&gt;

&lt;p&gt;Conceptually, this is clean. In practice, every new feature affecting profiles had to thread through the same logic, which meant many opportunities to get it subtly wrong. There are many edge cases. For example, you don't have to define the value of an attribute for every group. In that case, it falls back to the public group. &lt;/p&gt;

&lt;p&gt;You can then attach groups to posts, and only members of those groups will see the post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1napwx6ko4qabwnl3o3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1napwx6ko4qabwnl3o3b.png" alt="Post interface" width="799" height="447"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Posts and Comments Are Tricky
&lt;/h2&gt;

&lt;p&gt;Oh, did I mention that posts support full rich-text, including image attachments? And so do comments. You can also mention a user by typing @, and you can reply to a comment. All of that brings new problems.&lt;/p&gt;

&lt;p&gt;ActionText looks simple until you actually deploy it. The first surprise was CORS. ActionText supports direct uploads — the browser uploads images straight to your storage backend (in my case, an S3-compatible bucket on Railway), bypassing Rails entirely. That means your S3 bucket needs CORS headers that allow PUT requests from your app's origin; otherwise, the upload fails in production while working perfectly in development with local disk storage. I spent an embarrassingly long time trying to set CORS policies to the bucket from Railway's web dashboard, but it turns out that you need to do that via the S3 CLI. The dashboard lacks the feature.&lt;/p&gt;

&lt;p&gt;The second surprise was unattached blobs. When a user picks an image in the editor, Active Storage creates and uploads the blob immediately — before the form is submitted. If they close the tab, navigate away, or just change their mind, that blob sits in your storage bucket forever, attached to nothing. The Rails guide shows how you can &lt;a href="https://guides.rubyonrails.org/active_storage_overview.html#purging-unattached-uploads" rel="noopener noreferrer"&gt;purge this&lt;/a&gt; using a custom Rake task, but it's up to you to perform this regularly. However, there might be an interesting edge case where your cleanup job runs while someone is in the middle of writing a post or comment and it deletes the files they had uploaded. I don't have a solution for this yet.&lt;/p&gt;

&lt;p&gt;The third was mentions. ActionText gives you rich HTML, but @username detection has to be layered on top manually. I scan the post body's plain text for the pattern, batch-look up real users, and write GetStream notifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify_mentions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comment_id: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;text&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;try&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:to_plain_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;
  &lt;span class="n"&gt;usernames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MENTION_PATTERN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniq&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usernames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;

  &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;username: &lt;/span&gt;&lt;span class="n"&gt;usernames&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;mentioned&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;comment_id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"comment:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;comment_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"post:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;add_to_feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mentioned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;actor:      &lt;/span&gt;&lt;span class="s2"&gt;"user:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;verb:       &lt;/span&gt;&lt;span class="s2"&gt;"mention"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;foreign_id: &lt;/span&gt;&lt;span class="s2"&gt;"mention:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:user:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;mentioned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;post_slug:  &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;post_title: &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rendering mentions as links in the stored HTML is a separate problem. ActionText's stored HTML is a sanitized fragment, so I wrote a helper that uses Nokogiri to walk the text nodes, split on the @username pattern, and replace matches with anchor tags — being careful to collect all text nodes before modifying the tree, because mutating the DOM mid-traversal skips nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;text_nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;traverse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;text_nodes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text?&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MENTION_SPLIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;text_nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MENTION_SPLIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;html&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MENTION_MATCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;
      &lt;span class="sx"&gt;%(&amp;lt;a href="/users/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;CGI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escapeHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;"&amp;gt;@&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;CGI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escapeHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;&amp;lt;/a&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="no"&gt;CGI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escapeHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;
  &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Nokogiri&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTML&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DocumentFragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Data Model Rabbit Hole
&lt;/h2&gt;

&lt;p&gt;I spent a genuinely embarrassing amount of time on the data model before writing a single controller. Every entity that seemed simple on day one kept revealing a layer of hidden complexity. The biggest example: &lt;code&gt;GroupMembership&lt;/code&gt;. My first instinct was to allow a follower to be in multiple groups simultaneously. You're my colleague and my friend — why can't you be in both the "Colleagues" and "Friends" groups?&lt;/p&gt;

&lt;p&gt;The answer is: because then I don't know which group's attributes to show you. The visibility resolution breaks the moment a viewer can belong to more than one group. So I added a hard constraint: &lt;strong&gt;a follower belongs to exactly one group per owner&lt;/strong&gt;. That one constraint simplified everything downstream. But it also meant that moving someone to a new group is a replace operation, not an add — and I had to make sure the feed subscriptions stayed in sync whenever that happened.&lt;/p&gt;

&lt;p&gt;Another fun one: every new user automatically gets a "Public" group and a "Followers" group, created in &lt;code&gt;after_create&lt;/code&gt; callbacks. Those two groups are protected — you can't delete them, and you can't have more than one of each. But I also had to make sure that a deleted user doesn't cascade-delete their group memberships in a way that violates foreign key constraints before the groups themselves are removed. The fix was a &lt;code&gt;before_destroy&lt;/code&gt; callback that strips the protected flags first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;after_create&lt;/span&gt;  &lt;span class="ss"&gt;:create_public_group&lt;/span&gt;
&lt;span class="n"&gt;after_create&lt;/span&gt;  &lt;span class="ss"&gt;:create_followers_group&lt;/span&gt;
&lt;span class="n"&gt;before_destroy&lt;/span&gt; &lt;span class="ss"&gt;:strip_protected_group_flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;prepend: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_public_group&lt;/span&gt;
  &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"Public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;is_public: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;strip_protected_group_flags&lt;/span&gt;
  &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;is_public: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;is_followers: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;prepend: true&lt;/code&gt; matters — without it, dependent destroys on associations run before this callback, and you'd get constraint violations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Activity Feeds Are Harder Than They Look
&lt;/h2&gt;

&lt;p&gt;I didn't want to roll my own fan-out-on-write feed system, so I integrated &lt;a href="https://getstream.io" rel="noopener noreferrer"&gt;GetStream&lt;/a&gt;. It handles the mechanics: when a post is published to a group feed, it fans out to every timeline that follows that feed. My job is just to keep the follow/unfollow graph correct. That turned out to be enough work. Here's what happens when someone follows another user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_to_followers_group_and_follow_feeds&lt;/span&gt;
  &lt;span class="n"&gt;followers_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;followed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;is_followers: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;public_group&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;followed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;is_public: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;target_group&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;followers_group&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt;

  &lt;span class="no"&gt;GroupMembership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_or_create_by!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;group: &lt;/span&gt;&lt;span class="n"&gt;target_group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;member: &lt;/span&gt;&lt;span class="n"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;timeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;StreamClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"timeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;follower_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;target_group&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The follower's timeline subscribes to &lt;strong&gt;two&lt;/strong&gt; feeds: the &lt;code&gt;followers&lt;/code&gt; group feed and the &lt;code&gt;public&lt;/code&gt; group feed. That's because a post can be shared to either group and the follower should see both.&lt;/p&gt;

&lt;p&gt;And on unfollow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remove_from_group_and_unfollow_feeds&lt;/span&gt;
  &lt;span class="n"&gt;membership&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GroupMembership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;member_id: &lt;/span&gt;&lt;span class="n"&gt;follower_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;owner_id: &lt;/span&gt;&lt;span class="n"&gt;followed_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;timeline&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;StreamClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"timeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;follower_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;public_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;followed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;is_public: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unfollow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unfollow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;public_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;

  &lt;span class="n"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's an edge case buried in there: if someone is in the public group (their only group), you'd call &lt;code&gt;unfollow&lt;/code&gt; on the same feed twice. Hence the &lt;code&gt;if&lt;/code&gt; check.&lt;/p&gt;

&lt;p&gt;The biggest mistake I made here: I stubbed the GetStream client in tests with a double. Every test passed. Then I actually ran the app and discovered that &lt;code&gt;stream-ruby&lt;/code&gt; v6 changed its constructor from keyword args to positional args. My stub accepted anything, so it never caught it. Lesson learned — if you're going to stub an external client, at least verify the calling convention against the actual gem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Private Accounts and the Follow Request
&lt;/h2&gt;

&lt;p&gt;When a user sets their account to private, new followers need approval. This is a common feature on social platforms and sounds simple. It is not. The core model is a &lt;code&gt;FollowRequest&lt;/code&gt; that remains in a pending state until it is approved or rejected. But the follow flow now has four distinct paths, and each one has to keep groups, feeds, and notifications in sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;follow(public account)    → create Follow immediately
follow(private account)   → create FollowRequest, notify owner
approve(follow_request)   → destroy FollowRequest, create Follow, assign group
cancel(pending request)   → destroy FollowRequest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I made the mistake of putting most of the side-effect logic in model callbacks — &lt;code&gt;after_create&lt;/code&gt; and &lt;code&gt;after_destroy&lt;/code&gt; on &lt;code&gt;Follow&lt;/code&gt;. It kept the controllers thin, which felt clean at the time. But callbacks are hard to test in isolation, and when something went wrong, the stack trace was a mess of implicit triggers.&lt;/p&gt;

&lt;p&gt;Also, there's a subtle interaction with Devise. I added an &lt;code&gt;is_invited&lt;/code&gt; flag to gate access during the closed beta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;active_for_authentication?&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;is_invited?&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inactive_message&lt;/span&gt;
  &lt;span class="n"&gt;is_invited?&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:inactive&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I changed the column default from &lt;code&gt;true&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;, every single test broke — 204 failures at once. The &lt;code&gt;sign_in&lt;/code&gt; helper in RSpec goes through Warden's full session validation, which calls &lt;code&gt;active_for_authentication?&lt;/code&gt;. All my test users had &lt;code&gt;is_invited: false&lt;/code&gt; by default, and Warden was silently blocking them. The fix was adding &lt;code&gt;is_invited: true&lt;/code&gt; to every &lt;code&gt;User.new(...)&lt;/code&gt; call in every request spec. Tedious, but at least the cause was obvious once I figured it out. Lesson learned - think about closed betas as early as possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Invisible Mode: Like u/[deleted] But Better
&lt;/h2&gt;

&lt;p&gt;Vidha also has an invisible mode for the privacy fanatics. Invisible mode is a toggle users can enable from their account settings. When it's on, the user effectively doesn't exist from anyone else's perspective: their profile returns a 404, they're excluded from search results and member typeahead suggestions, and attempting to follow them returns a 404 too. It's more private than a private account — closer to "ghost mode." The implementation is blunt but honest: every entry point that could expose the user has an explicit guard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# UsersController&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
  &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RecordNotFound&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invisible_mode?&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;follow&lt;/span&gt;
  &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:not_found&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invisible_mode?&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Search queries exclude invisible users at the DB level&lt;/span&gt;
&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"username ILIKE ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;invisible_mode: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raise ActiveRecord::RecordNotFound approach is intentional — it renders the same 404 page as any other missing resource, leaking no information about whether the account exists at all. However, you can still comment (shows up as "Anonymous"), and your posts can be viewed by someone with a direct link. This is to make sure you can still use the social media as intended. I suppose this might come in handy for activists or protesters who don't want to reveal their identity, yet want to post on the platform.&lt;/p&gt;

&lt;p&gt;One thing I'm undecided on: right now, people who already follow you can still see your timeline posts even in invisible mode (the feed subscription persists). Whether that's a bug or a feature is genuinely unclear to me. Also, this feature is pretty terrifying: Anything that can leak info about an invisible user is a big no-no, and I have to make sure every new feature respects invisible users.&lt;/p&gt;

&lt;p&gt;Currently the best approach in my mind is this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When an invisible user joins a community (or a visible user switches to invisible), it creates a pseudo-username, like "anon-abcde". This pseudo-username is scoped to the user and community. So the same user will get different pseudo-usernames in different communities.&lt;/li&gt;
&lt;li&gt;This pseudo-username will be visible to the mods, and they can see the list of posts and comments by this user and ban/suspend them.&lt;/li&gt;
&lt;li&gt;Since the same user will get different pseudo-usernames in different communities, it means if someone is a moderator in two different communities, they'll not be able to link the same user in those communities.&lt;/li&gt;
&lt;li&gt;This pseudo-username is persistent. So, if the user changes back to visible or leaves and rejoins the community, that won't generate a new one. This prevents someone from circumventing the ban by leaving and rejoining.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Communities: Like Subreddits, But Simpler
&lt;/h2&gt;

&lt;p&gt;Later in the build, I added communities — shared spaces with their own post feeds, member lists, and moderation. A community post publishes to a &lt;code&gt;"community"&lt;/code&gt; feed on GetStream instead of the author's personal group feeds. Membership works the same way: joining subscribes your timeline feed to the community's feed, while leaving unsubscribes.&lt;/p&gt;

&lt;p&gt;The trickiest part was access control. A post in a private community should be visible only to members. A personal post should only be visible to people in the right group. Both &lt;code&gt;PostsController&lt;/code&gt; and &lt;code&gt;CommentsController&lt;/code&gt; need this logic. Here's the concern I extracted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;PostVisibility&lt;/span&gt;
  &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Concern&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_view_post?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;community_id?&lt;/span&gt;
      &lt;span class="n"&gt;community&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;community&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;community&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_private?&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;community&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;creator_id&lt;/span&gt;
      &lt;span class="no"&gt;CommunityMembership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;community_id: &lt;/span&gt;&lt;span class="n"&gt;community&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_id&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:is_public?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;
      &lt;span class="no"&gt;GroupMembership&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;member_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;group_id: &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I initially had variations of this logic scattered across both controllers before I noticed I was copying code and extracted it into a separate file. I should have written it in one place from the beginning. Access control sprawl is a real thing — every new context (show, edit, destroy, comment) tempts you to just paste the check inline. I'm thinking about using an authorization gem like CanCanCan or Pundit, but I'm not sure how big a rewrite it'd be or whether it'd be worth it. Lesson learned: Think about authorization and access control from the beginning, and try to reuse the logic as much as possible.&lt;/p&gt;

&lt;p&gt;One more thing that I'm still trying to figure out: How should invisible accounts interact with community moderation? Letting invisible accounts run rampant in a community is a recipe for disaster, so moderators should have some way of telling "invisible dude A" from "invisible dude B." One approach is to let a moderator view a "community profile" of an invisible user: just the posts they have made in that particular community, plus the ban/suspend/remove buttons. But that still doesn't answer how to identify that particular user in the user list. This is something I'm thinking about very hard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frontend: Hotwire Over React
&lt;/h2&gt;

&lt;p&gt;I chose Hotwire (Turbo + Stimulus) and deliberately avoided React. The whole point was to stay inside the Rails idiom and keep the stack simple. For most things, this worked well. Likes are a good example — toggling a like or dislike replaces just the button component in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;respond_with_reaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;turbo_stream&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;turbo_stream: &lt;/span&gt;&lt;span class="n"&gt;turbo_stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;dom_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@likeable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:likes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="ss"&gt;partial: &lt;/span&gt;&lt;span class="s2"&gt;"likes/like_button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;locals: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;likeable: &lt;/span&gt;&lt;span class="vi"&gt;@likeable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;liked:    &lt;/span&gt;&lt;span class="n"&gt;current_kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"like"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;disliked: &lt;/span&gt;&lt;span class="n"&gt;current_kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"dislike"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_back&lt;/span&gt; &lt;span class="ss"&gt;fallback_location: &lt;/span&gt;&lt;span class="n"&gt;root_path&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No JavaScript state management, no API endpoint, no serializer. The server renders the updated partial and sends it down the wire. For the 90% case, this is genuinely fast to build.&lt;/p&gt;

&lt;p&gt;The 10% case is where Hotwire gets awkward: complex client-side state that doesn't map cleanly to server-rendered partials. The rich text editor with group targeting, for instance, involves coordinating ActionText, a custom group selector, and a preview — all in one form. Stimulus controllers are composable, but they don't share state easily. A few times I genuinely wished I had a React app.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell Myself at the Start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Model the domain on paper first.&lt;/strong&gt; The group-based visibility system is the core of the entire app. I arrived at the right data model through iteration, but a couple of hours on a whiteboard before writing any code would have saved me a week of refactoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't stub external services in tests and call it done.&lt;/strong&gt; Stubs lie. They accept anything and catch nothing. Either test against the real service (with a sandbox account), or write a thin adapter layer that the stub actually validates against (or read the documentation well).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access control needs a home from day one.&lt;/strong&gt; Don't spread visibility checks across controllers. Put them somewhere dedicated — a concern, a policy object, a query object — and route everything through it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Callbacks hide complexity, they don't eliminate it.&lt;/strong&gt; Model callbacks feel clean until you're debugging a 12-step trigger chain at midnight. For anything that touches external services or multiple models, a service object with explicit methods is worth the extra file.&lt;/p&gt;




&lt;p&gt;Building Vidha has been the most effective learning exercise I've done in a while. Every feature that looks simple from the outside — following someone, liking a post, showing a profile — has a graph of constraints underneath it. You don't see that graph until you try to build it yourself.&lt;/p&gt;

&lt;p&gt;If you're thinking about building something similar, I'd say: start with the hardest domain problem and work outward from there. The framework will take care of itself.&lt;/p&gt;

&lt;p&gt;And if you want to check out Vidha, go ahead and &lt;a href="https://vidha.online" rel="noopener noreferrer"&gt;sign up&lt;/a&gt;. Then drop a comment with your username so that I can activate your account. Expect bugs, lots of them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Vidha is a personal side project built for my own education. It is not a commercial product.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>socialmedia</category>
      <category>rails</category>
      <category>buildinpublic</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Best AI Code Review Tools of 2026</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Fri, 20 Feb 2026 13:25:32 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/the-best-ai-code-review-tools-of-2026-2mb3</link>
      <guid>https://dev.to/heraldofsolace/the-best-ai-code-review-tools-of-2026-2mb3</guid>
      <description>&lt;p&gt;Early AI code review tools had a math problem. For every real bug they caught, they flagged nine false positives. Teams got buried in comments about variable naming conventions and whitespace. They started ignoring the bot entirely. Productivity tanked.&lt;/p&gt;

&lt;p&gt;The tools that made it past 2025 didn't just throw better models at the problem. They rebuilt how code review works from scratch.&lt;/p&gt;

&lt;p&gt;This guide covers the AI code review tools worth using in 2026, what they're actually good at, and which specific problems they solve for your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem: Context Windows vs. Large Diffs
&lt;/h2&gt;

&lt;p&gt;AI reviewers break down when you feed them too much code at once. A 1,000-line diff overwhelms the context window. The model loses coherence, misses connections between changes, and falls back on pattern matching for style issues.&lt;/p&gt;

&lt;p&gt;The same reviewer that produces noise on large diffs produces useful feedback on small ones. The tool didn't get smarter. You gave it a problem it could actually solve.&lt;/p&gt;

&lt;p&gt;This is why the effective tools in 2026 either enforce small changes (Graphite), sacrifice depth for speed (GitHub Copilot), or index your entire codebase upfront (Greptile).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13qiqz7gotd26i1961wa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13qiqz7gotd26i1961wa.png" alt="AI code review tools" width="800" height="977"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool Comparison Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Platform Support&lt;/th&gt;
&lt;th&gt;Analysis Depth&lt;/th&gt;
&lt;th&gt;False Positive Rate&lt;/th&gt;
&lt;th&gt;Price/User/Mo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Graphite Agent&lt;/td&gt;
&lt;td&gt;Teams adopting stacked PRs&lt;/td&gt;
&lt;td&gt;GitHub only&lt;/td&gt;
&lt;td&gt;Deep (full codebase)&lt;/td&gt;
&lt;td&gt;~3% unhelpful&lt;/td&gt;
&lt;td&gt;\$40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;Existing Copilot users&lt;/td&gt;
&lt;td&gt;GitHub only&lt;/td&gt;
&lt;td&gt;Surface (diff-based)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;\$10-39 (bundled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeRabbit&lt;/td&gt;
&lt;td&gt;Multi-platform teams&lt;/td&gt;
&lt;td&gt;GitHub, GitLab, Bitbucket, Azure&lt;/td&gt;
&lt;td&gt;Surface (diff-based)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;\$24-30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Greptile&lt;/td&gt;
&lt;td&gt;Maximum bug detection&lt;/td&gt;
&lt;td&gt;GitHub, GitLab&lt;/td&gt;
&lt;td&gt;Deep (full codebase)&lt;/td&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;td&gt;\$30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BugBot&lt;/td&gt;
&lt;td&gt;Cursor-native teams&lt;/td&gt;
&lt;td&gt;GitHub only&lt;/td&gt;
&lt;td&gt;Medium (8-pass diff)&lt;/td&gt;
&lt;td&gt;Low-Medium&lt;/td&gt;
&lt;td&gt;\$40 + Cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Graphite Agent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://graphite.com/features/agent" rel="noopener noreferrer"&gt;Graphite Agent&lt;/a&gt; combines full-codebase understanding with &lt;a href="https://graphite.dev/guides/stacked-diffs" rel="noopener noreferrer"&gt;stacked PRs&lt;/a&gt;. Instead of one massive pull request, you break changes into small, dependent PRs that merge in sequence.&lt;/p&gt;

&lt;p&gt;Here's how stacked PRs work:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4trtetydfbmpxtriqig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4trtetydfbmpxtriqig.png" alt="Stacked PR" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shopify reported 33% more PRs merged per developer after adoption, with 75% of PRs now going through Graphite. &lt;a href="https://graphite.com/customer/asana" rel="noopener noreferrer"&gt;Asana&lt;/a&gt; saw engineers save 7 hours weekly, ship 21% more code, and cut median PR size by 11%.&lt;/p&gt;

&lt;p&gt;Agent maintains an &lt;a href="https://graphite.com/guides/effectiveness-and-limitations-of-ai-code-review" rel="noopener noreferrer"&gt;unhelpful comment rate under 3%&lt;/a&gt;. When it flags an issue, developers change the code 55% of the time. Human reviewers hit 49%.&lt;/p&gt;

&lt;p&gt;The tool provides one-click fixes, resolves CI failures inline, and includes a &lt;a href="https://graphite.dev/features/merge-queue" rel="noopener noreferrer"&gt;merge queue&lt;/a&gt; that coordinates landing changes in order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constraint:&lt;/strong&gt; GitHub-only, and your entire team needs to adopt stacked workflows. For teams that commit to this change, median PR merge time drops from 24 hours to 90 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; &lt;a href="https://graphite.dev/pricing" rel="noopener noreferrer"&gt;Team plan&lt;/a&gt; at \$40/user/month with unlimited reviews. Free tier for individuals. Enterprise pricing on request.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Copilot Code Review
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/copilot/using-github-copilot/code-review/using-copilot-code-review" rel="noopener noreferrer"&gt;GitHub Copilot Code Review&lt;/a&gt; hit general availability in April 2025 and reached 1 million users in a month. You assign Copilot as a reviewer like any teammate. It leaves inline comments with suggested fixes.&lt;/p&gt;

&lt;p&gt;The October 2025 update added context gathering. Copilot now reads source files, explores directory structure, and integrates &lt;a href="https://codeql.github.com/" rel="noopener noreferrer"&gt;CodeQL&lt;/a&gt; and ESLint for security scanning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it's good at:&lt;/strong&gt; Zero friction if you already pay for Copilot. Catches typos, null checks, and simple logic errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it misses:&lt;/strong&gt; Architectural problems and cross-file dependencies. It's diff-based, so it only sees what changed in the PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Bundled with Copilot subscriptions (\$10-39/month depending on tier). Code review features not available on free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  CodeRabbit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://coderabbit.ai/" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; is the most widely installed AI code review app on GitHub and GitLab. Over 2 million repositories connected, 13 million+ PRs processed. It runs automatically on new PRs, leaving line-by-line comments with severity rankings and one-click fixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The advantage:&lt;/strong&gt; Platform breadth. Supports GitHub, GitLab, Bitbucket, and Azure DevOps. Integrates 40+ linters and SAST scanners. Offers self-hosted deployment for enterprises with 500+ seats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The limitation:&lt;/strong&gt; Diff-based analysis. It sees what changed in the PR, not how changes interact with your codebase. Independent benchmarks gave it a 1/5 completeness score for catching systemic issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Pro plan at \$24-30/user/month. Free tier with basic PR summaries. Enterprise plans with self-hosting available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Greptile
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.greptile.com/" rel="noopener noreferrer"&gt;Greptile&lt;/a&gt; indexes your entire repository and builds a code graph. It uses multi-hop investigation to trace dependencies, check git history, and follow leads across files.&lt;/p&gt;

&lt;p&gt;Version 3 (late 2025) uses the Anthropic Claude Agent SDK for autonomous investigation. The tool shows you evidence from your codebase for every flagged issue.&lt;/p&gt;

&lt;p&gt;At \$30/developer/month with a \$180M valuation after its Benchmark-led Series A, Greptile offers the deepest context-aware analysis available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tradeoff:&lt;/strong&gt; Highest catch rate, but also highest false positive rate in independent evaluations. You get more real bugs and more noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; \$30/developer/month for unlimited reviews. Discounts for annual commitments. Open-source projects may qualify for free usage. Self-hosted and enterprise pricing on request.&lt;/p&gt;

&lt;h2&gt;
  
  
  BugBot
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cursor.com/bugbot" rel="noopener noreferrer"&gt;BugBot&lt;/a&gt; from Cursor launched in July 2025 and reviews 2 million+ PRs monthly. It runs 8 parallel review passes with randomized diff order on every PR, catching bugs that single-pass reviewers miss.&lt;/p&gt;

&lt;p&gt;The "Fix in Cursor" button jumps you from review comment to editor with the fix pre-loaded. Discord's engineering team reported BugBot finding real bugs on human-approved PRs. Over 70% of flagged issues get resolved before merge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constraint:&lt;/strong&gt; Tightly coupled to Cursor. You need a Cursor subscription, and it works best when your team already uses Cursor as their primary editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; \$40/user/month plus Cursor subscription. 14-day free trial. GitHub-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Smaller PRs Get Better Results
&lt;/h2&gt;

&lt;p&gt;Research shows 30-40% cycle time improvements for PRs under 500 lines, with diminishing returns above that threshold. Teams using stacked PRs ship 20% more code with 8% smaller median PR size, saving roughly 10 hours per week waiting to merge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzh98xu4zf6n023ldayh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzh98xu4zf6n023ldayh.png" alt="Bugbot" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same AI reviewer produces signal on a 150-line diff and noise on a 1,000-line one. The tool didn't change. The workflow gave it a solvable problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision Framework
&lt;/h2&gt;

&lt;p&gt;Pick based on what you're willing to change:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No workflow changes:&lt;/strong&gt; Start with GitHub Copilot if you already pay for it. Zero setup, catches obvious bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-platform support:&lt;/strong&gt; CodeRabbit is the only option that works across GitHub, GitLab, Bitbucket, and Azure DevOps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maximum bug detection:&lt;/strong&gt; Greptile's full-codebase indexing finds issues other tools miss. Accept higher noise as the tradeoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor workflow:&lt;/strong&gt; If your team lives in Cursor, BugBot extends your existing setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow transformation:&lt;/strong&gt; Graphite treats &lt;a href="https://graphite.dev/guides/code-review-best-practices" rel="noopener noreferrer"&gt;code review&lt;/a&gt; as a systems problem. The numbers from Shopify (33% more PRs per developer) and Asana (7 hours saved weekly) came from adopting stacked workflows, not just adding AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration and Security
&lt;/h2&gt;

&lt;p&gt;AI code review tools plug directly into GitHub, GitLab, and Bitbucket as automated reviewers. They integrate with CI/CD pipelines, offer IDE plugins for real-time feedback, and support webhook triggers for automatic reviews on code push.&lt;/p&gt;

&lt;p&gt;Security varies by provider. Look for encryption in transit and at rest, SOC 2 compliance, and clear data retention policies. Some tools offer self-hosted options for maximum control. Graphite has a &lt;a href="https://graphite.com/docs/privacy-and-security" rel="noopener noreferrer"&gt;privacy-first approach&lt;/a&gt; that guarantees code stays private and isn't used for model training.&lt;/p&gt;

&lt;p&gt;Cost ranges from \$10-50 per user monthly for standard plans. GitHub Copilot Code Review bundles with existing subscriptions (\$10-39/month). Enterprise plans with custom rules and dedicated support cost more. Self-hosted options may use infrastructure-based pricing instead of per-user costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Matters
&lt;/h2&gt;

&lt;p&gt;The AI code review tools that survived 2025 didn't just add smarter models. They rethought workflows. Graphite built a platform around stacked changes. GitHub Copilot traded depth for zero friction. CodeRabbit went for breadth across platforms. Greptile went all-in on context. BugBot integrated tightly with an editor.&lt;/p&gt;

&lt;p&gt;The right tool depends on what you're willing to change. If you want AI review with no disruption, GitHub Copilot works. If you need multi-platform support, CodeRabbit is your only option. If catching deep bugs matters more than noise, Greptile's full-codebase indexing finds things others miss. If your team lives in Cursor, BugBot fits naturally.&lt;/p&gt;

&lt;p&gt;If you're willing to change how your team works, not just add a bot, Graphite treats code review as a workflow problem. Stacked PRs, AI review, and merge queue work together in ways that separate tools can't replicate. The productivity gains came from the workflow change, not just the AI.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>code</category>
      <category>github</category>
    </item>
    <item>
      <title>Stacking up Graphite in the World of Code Review Tools</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Fri, 23 Jan 2026 13:07:13 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/stacking-up-graphite-in-the-world-of-code-review-tools-5fbn</link>
      <guid>https://dev.to/heraldofsolace/stacking-up-graphite-in-the-world-of-code-review-tools-5fbn</guid>
      <description>&lt;p&gt;&lt;strong&gt;A developer's guide to navigating the crowded market of AI code review assistants in 2026.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Review Bot" Explosion
&lt;/h2&gt;

&lt;p&gt;2025 is the year every engineering team tried to automate code review. If you opened a pull request this year, chances are an AI left a comment on it. Some of those comments caught real bugs. Many did not.&lt;/p&gt;

&lt;p&gt;The problem isn't that AI code review doesn't work. It's that the market is flooded with tools that look similar on the surface but solve fundamentally different problems. One tool might catch off-by-one errors in your IDE before you even push. Another scans your entire repository history to understand cross-file dependencies. A third lives in your GitHub comments, leaving feedback that ranges from brilliant to pedantic.&lt;/p&gt;

&lt;p&gt;For the average developer or tech lead, it's hard to tell the difference between a wrapper around an LLM and an actual platform. This article cuts through that noise.&lt;/p&gt;

&lt;p&gt;I'll compare five tools—Graphite, GitHub's native review, CodeRabbit, Greptile, and BugBot—using three criteria:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Depth&lt;/strong&gt;: Does it understand your whole codebase, or just the diff?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow&lt;/strong&gt;: Does it change how you review, or just what you read?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noise&lt;/strong&gt;: Does it help you ship faster, or spam your PR timeline?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start by categorizing these tools by what they actually do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Category 1: The "Workflow Platform"
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwg0xz22xzi72a6d6yf1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwg0xz22xzi72a6d6yf1.png" alt="Graphite interface" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool: Graphite Agent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://graphite.com" rel="noopener noreferrer"&gt;Graphite&lt;/a&gt; is an AI-augmented code review and pull request workflow platform designed to help teams ship higher-quality code faster by rethinking how reviews and merges work. It integrates AI feedback directly into pull requests, supports stacked pull request workflows, and provides automated merge orchestration.&lt;/p&gt;

&lt;p&gt;When you open a pull request with Graphite, the Graphite Agent provides AI-driven feedback directly in the PR page. It focuses on delivering high-signal insights rather than large volumes of superficial comments. The platform is built to help teams catch substantive bugs and issues early in the review cycle. &lt;/p&gt;

&lt;p&gt;A key workflow innovation is &lt;strong&gt;stacked pull requests&lt;/strong&gt;. With the Graphite CLI and tooling, developers can create a series of dependent PRs—each building on the last—so you can continue working on PR #3 while PR #1 and #2 are still under review. When an earlier PR merges, subsequent stacked changes are automatically rebased, helping reduce merge conflicts and keeping development unblocked.&lt;/p&gt;

&lt;p&gt;Graphite also includes a &lt;strong&gt;stack-aware merge queue&lt;/strong&gt;. Instead of merging PRs sequentially and running CI for each one in isolation, the merge queue can batch and test multiple PRs in parallel once they’re ready, speeding up the merge process and reducing wait times. &lt;/p&gt;

&lt;p&gt;In addition to AI review and stacking workflows, Graphite offers a modern PR review interface with unified inboxes, GitHub integrations, a CLI, and editor extensions (e.g., VS Code). These features aim to streamline the development and review experience and reduce context switching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Teams that find traditional GitHub review workflows slow or fragmented, and that would benefit from structured, smaller changesets, automated rebasing, and integrated AI feedback throughout the pull request lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free (Hobby)&lt;/strong&gt;: Essentials, including CLI for stacked PRs, VS Code extension, and limited access to Graphite Agent and AI reviews.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starter&lt;/strong&gt;: $20/user/month (billed annually), includes support for all GitHub organization repos and team insights.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team&lt;/strong&gt;: $40/user/month (billed annually), adds unlimited Graphite Agent access, unlimited AI reviews, review customizations, automations, and merge queue.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise&lt;/strong&gt;: Custom pricing with advanced controls, security features, and support. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Category 2: The "Native Giant"
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqougjqe12r58pvt3kgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqougjqe12r58pvt3kgd.png" alt="Enabling Copilot in VS Code" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool: GitHub
&lt;/h2&gt;

&lt;p&gt;GitHub’s native code review workflow is the baseline most teams start from. It is reliable, familiar, and requires no additional tooling beyond what teams are already using for source control and pull requests.&lt;/p&gt;

&lt;p&gt;GitHub has added AI-assisted review features through Copilot (including Copilot for Business and Copilot Enterprise). These capabilities can summarize pull requests, explain changes, and suggest improvements directly in comments. In practice, the quality of these suggestions varies: some feedback is genuinely helpful, while other comments are generic or low value. Importantly, Copilot acts as an assistive layer rather than a fully autonomous review agent.&lt;/p&gt;

&lt;p&gt;A core limitation is architectural. GitHub’s pull request model is fundamentally linear: one branch, one PR, one review lifecycle. There is no native support for stacked or dependent pull requests, which makes it harder to work on large changes incrementally without introducing workflow friction.&lt;/p&gt;

&lt;p&gt;For static analysis and security scanning, GitHub offers &lt;a href="https://codeql.github.com/" rel="noopener noreferrer"&gt;CodeQL&lt;/a&gt;. Public repositories get CodeQL scanning by default. For private repositories, full CodeQL integration—security alerts, dashboards, and UI support—requires GitHub Advanced Security, which introduces additional cost and setup. CodeQL analysis is deep and reliable, but it prioritizes correctness over speed and is not optimized for fast, conversational PR feedback like AI-first reviewers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Teams that value stability and minimal workflow disruption, and that prefer to stay within the existing GitHub ecosystem. If adopting new tools or changing review workflows is a hard sell, GitHub’s native features are often “good enough” for baseline review automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notable limitation&lt;/strong&gt;: Merge queue availability is restricted to higher-tier GitHub plans and is not universally accessible. Many teams still merge PRs sequentially and run CI per-PR rather than benefiting from batch testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Category 3: The "Comment Bots"
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5lwjlluuhem07ef0krfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5lwjlluuhem07ef0krfk.png" alt="CodeRabbit" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool: CodeRabbit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.coderabbit.ai" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; is a feature-rich AI code reviewer that operates entirely within pull request comments. It combines large language models with a broad set of linters and security scanners to generate comprehensive feedback on code changes. In addition to identifying issues, it can generate PR summaries, create sequence diagrams, suggest fixes, and propose test cases.&lt;/p&gt;

&lt;p&gt;The breadth of analysis is both its main strength and its primary trade-off. CodeRabbit surfaces a wide range of findings, but this often includes stylistic feedback and minor suggestions alongside substantive issues. As a result, teams frequently invest time in tuning configuration and rules to reduce noise and focus the output on what matters most for their codebase.&lt;/p&gt;

&lt;p&gt;CodeRabbit integrates with major source control platforms, including GitHub, GitLab, Bitbucket, and Azure DevOps. It also provides a VS Code extension that enables pre-PR review, allowing developers to catch issues before opening a pull request. This broad platform support makes it suitable for organizations operating across multiple repositories or hosting providers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Open source maintainers or teams that want extensive automated feedback directly in their existing PR workflow, without introducing a new review interface. Its multi-platform support is particularly valuable for organizations that are not GitHub-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;: Offers a free tier with basic features such as PR summarization. Paid plans are approximately $24–$30 per user per month (Pro), depending on billing, and unlock advanced analysis and customization options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Category 4: The "Deep Context" Engine
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8edxn7v7rkesmpuzj9i6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8edxn7v7rkesmpuzj9i6.png" alt="Greptile" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool: Greptile
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.greptile.com" rel="noopener noreferrer"&gt;Greptile&lt;/a&gt; takes a fundamentally different approach. Instead of analyzing just the PR diff, it builds a full graph of your codebase—understanding how functions connect, where code is used, and how similar patterns have evolved over time.&lt;/p&gt;

&lt;p&gt;This deep context means Greptile can catch issues that span multiple files or reference historical changes. It's like having a reviewer who has memorized your entire repository. The trade-off is complexity: Greptile requires more setup and resources than a simple diff-based tool.&lt;/p&gt;

&lt;p&gt;Greptile emphasizes enterprise features: self-hosted deployment, custom AI models, SOC 2 compliance, and support for both GitHub and GitLab. You can run it on-premises if your security team requires it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Large codebases where understanding cross-module dependencies is critical. If you're working on a system with millions of lines of code and complex architectural patterns, Greptile's full-repo analysis catches issues that simpler tools miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;: ~$30/user/month (cloud), enterprise negotiation for on-prem. 100% off for open-source and 50% off for startups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Category 5: The "Pre-Merge" Hunter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx68ycq975jfhx08xsuik.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx68ycq975jfhx08xsuik.png" alt="BugBot" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool: BugBot (by Cursor)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cursor.com/docs/bugbot" rel="noopener noreferrer"&gt;Bugbot&lt;/a&gt; is a narrowly focused AI code review tool designed to identify logic bugs and security issues with a low false-positive rate. Rather than acting as a full code review or workflow platform, it concentrates on surfacing concrete, actionable problems in code changes.&lt;/p&gt;

&lt;p&gt;Bugbot primarily operates on GitHub pull requests. When a PR is opened, it automatically reviews the diff and leaves comments highlighting potential bugs, edge cases, or security concerns. For users of the Cursor IDE, flagged issues can be opened directly in the editor, where suggested fixes can be applied or modified before committing updates.&lt;/p&gt;

&lt;p&gt;The tool is optimized for depth over breadth. It intentionally avoids stylistic feedback and general code quality suggestions, focusing instead on issues that are likely to cause incorrect behavior or security risk. Bugbot also supports custom review rules, allowing teams to encode project-specific constraints and conventions.&lt;/p&gt;

&lt;p&gt;The primary limitation is scope. Bugbot does not aim to replace a full code review platform: it does not generate PR summaries, manage approvals, or integrate with project management workflows. Its role is confined to automated detection of substantive bugs within pull requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt;: Individual contributors or teams who want automated, high-signal bug detection during PR review—especially those already using Cursor. It can be particularly useful in environments where logic or security defects carry a high operational cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing&lt;/strong&gt;: Bugbot offers a limited free tier for Cursor Teams and Individual plan users. Paid plans are approximately $40 per user per month, depending on usage limits and team features, and are available as add-ons to Cursor plans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Graphite&lt;/th&gt;
&lt;th&gt;GitHub&lt;/th&gt;
&lt;th&gt;CodeRabbit&lt;/th&gt;
&lt;th&gt;Greptile&lt;/th&gt;
&lt;th&gt;BugBot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GitHub App + CLI&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;GitHub App&lt;/td&gt;
&lt;td&gt;GitHub App + API&lt;/td&gt;
&lt;td&gt;GitHub App&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Focus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Workflow + AI&lt;/td&gt;
&lt;td&gt;Manual Review&lt;/td&gt;
&lt;td&gt;PR Comments&lt;/td&gt;
&lt;td&gt;Deep Context&lt;/td&gt;
&lt;td&gt;PR Comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analysis Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PR diff + context&lt;/td&gt;
&lt;td&gt;PR diff&lt;/td&gt;
&lt;td&gt;PR diff + linters&lt;/td&gt;
&lt;td&gt;Full repo graph&lt;/td&gt;
&lt;td&gt;Current PR + existing PR comments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Noise Level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very Low (~5%)&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Medium-High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stacked PRs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Native&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merge Queue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Enterprise only&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$20-40/user&lt;/td&gt;
&lt;td&gt;Included + Copilot&lt;/td&gt;
&lt;td&gt;$12-30/user&lt;/td&gt;
&lt;td&gt;~$30/user&lt;/td&gt;
&lt;td&gt;Limited usage included with Cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which Tool Fits Your Stack?
&lt;/h2&gt;

&lt;p&gt;There's no universal winner. The right choice depends on where your team is blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If your bottleneck is the review process itself&lt;/strong&gt;—PRs sit too long, developers get blocked, context-switching kills momentum—Graphite's workflow improvements matter more than any AI feature. The stacked PRs and merge queue can cut review time from days to hours, even before the AI comments help.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're fully invested in GitHub and Microsoft tools&lt;/strong&gt;, the native features are adequate. You won't get workflow improvements, but you also won't need to onboard a new platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you need cross-platform support&lt;/strong&gt; or want comprehensive coverage including linters and security scanners, CodeRabbit delivers. Be prepared to tune its configuration to reduce noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're working on a massive legacy codebase&lt;/strong&gt; where understanding historical context is critical, Greptile's full-repo analysis is worth the complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're already using Cursor IDE&lt;/strong&gt; and want to catch bugs before they are merged, BugBot is the most focused solution.&lt;/p&gt;

&lt;p&gt;The real question isn't "which AI reviewer is best?" It's "what's actually slowing down my team's shipping velocity?" Answer that first, then pick the tool that addresses it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What are you running in production?&lt;/strong&gt; Are you trusting AI to auto-approve yet, or strictly using it for summaries? I'm curious what everyone else is seeing. Drop a comment below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codereview</category>
      <category>coding</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Stack Pull Requests on GitHub (Without Losing Your Mind)</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Fri, 16 Jan 2026 12:25:13 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/how-to-stack-pull-requests-on-github-without-losing-your-mind-54p8</link>
      <guid>https://dev.to/heraldofsolace/how-to-stack-pull-requests-on-github-without-losing-your-mind-54p8</guid>
      <description>&lt;p&gt;You just finished building your feature. You open a pull request. Then you wait. And wait. And wait some more.&lt;/p&gt;

&lt;p&gt;Meanwhile, the next part of your project is ready to build, but it depends on the code you just submitted. Do you start working anyway and risk a rebasing nightmare? Or do you context switch to something completely different and lose your flow?&lt;/p&gt;

&lt;p&gt;This is the "waiting game" that kills productivity. You've probably tried both approaches. Starting the next feature while the first is still under review leads to merge conflicts when changes come back. Context switching to unrelated work destroys your momentum and makes you lose the mental model you built up.&lt;/p&gt;

&lt;p&gt;The typical escape hatch is cramming everything into one massive 1,000-line PR. Ship it all at once, avoid branch dependencies entirely. But reviewers hate this. Big PRs take forever to review. Bugs hide in the noise. And if something needs to change, you're back to square one.&lt;/p&gt;

&lt;p&gt;There's a better way. Stacked pull requests let you break work into small, reviewable chunks without the Git gymnastics. And the Graphite CLI (&lt;code&gt;gt&lt;/code&gt;) makes it stupid simple.&lt;/p&gt;

&lt;p&gt;This isn't theory. Companies like Airbnb and Meta have used stacking for years. Now it's available for the rest of us, without building custom tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why stacking matters
&lt;/h2&gt;

&lt;p&gt;When you stack PRs, you create a chain: PR 1 builds the foundation, PR 2 builds on PR 1, and PR 3 builds on PR 2. Each PR is small and focused. Reviewers can approve them independently. You keep coding without getting blocked.&lt;/p&gt;

&lt;p&gt;Think of it like chapters in a book. Each chapter makes sense on its own, but they build toward a larger story. Your reviewer reads chapter 1 (the API), approves it, then moves to chapter 2 (the UI). They're not overwhelmed by 50 pages at once.&lt;/p&gt;

&lt;p&gt;The benefits compound:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster reviews&lt;/strong&gt;: A 200-line PR gets reviewed in minutes. A 2,000-line PR sits for days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better feedback&lt;/strong&gt;: Reviewers spot issues when the context is fresh and focused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier debugging&lt;/strong&gt;: When something breaks, you know exactly which small change caused it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No blocking&lt;/strong&gt;: You keep building while waiting for reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem? Managing this in vanilla Git is painful. Rebasing changes down the stack, keeping base branches in sync, and avoiding merge conflicts requires constant mental overhead. You spend more time managing branches than writing code.&lt;/p&gt;

&lt;p&gt;Graphite automates all of it. The entire workflow becomes three commands: create, submit, restack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup in 60 seconds
&lt;/h2&gt;

&lt;p&gt;Install the Graphite CLI using either Homebrew or npm:&lt;/p&gt;

&lt;p&gt;brew installation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;withgraphite/tap/graphite
gt &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;npm installation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @withgraphite/graphite-cli@stable
gt &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign in to &lt;a href="https://app.graphite.com/activate" rel="noopener noreferrer"&gt;https://app.graphite.com/activate&lt;/a&gt; with your GitHub account. You might be prompted to complete the setup if you haven’t done so already. There, you can create a token, and Graphite will provide you with an authentication command to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt auth &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;your_cli_auth_token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this command, which will log you in.&lt;/p&gt;

&lt;p&gt;Initialize Graphite in your repo (make sure this repo exists in GitHub, and you chose it for syncing during Graphite setup):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Creates local config, doesn't modify your actual repo&lt;/span&gt;
gt init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Graphite works alongside Git. You can still use &lt;code&gt;git status&lt;/code&gt;, &lt;code&gt;git add&lt;/code&gt;, and other standard Git commands. The &lt;code&gt;gt&lt;/code&gt; commands just handle the branching logic for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the first layer: backend API
&lt;/h2&gt;

&lt;p&gt;Let's build a simple todo app. Start with the API endpoints.&lt;/p&gt;

&lt;p&gt;Make sure you’re on the main branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt checkout main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now write your code. Here's a minimal Express API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// In-memory storage for todos&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// GET endpoint to fetch all todos&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// POST endpoint to create a new todo&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server running on port 3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the following command, which will create a new branch named &lt;code&gt;feat/api&lt;/code&gt; and add a commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt create feat/api &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Add API endpoints"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API is done, but not merged yet. Time to stack the frontend on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stacking the frontend: the magic part
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. The API isn't approved or merged, but you're ready to build the UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- index.html --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Todo App&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My Todos&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"todo-input"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Add a todo"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"addTodo()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Add&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"todo-list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// Fetch and display all todos on page load&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todo-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Add a new todo via POST request&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todo-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/todos&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;loadTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Refresh the list&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;loadTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Initialize on page load&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this command, which creates a &lt;code&gt;feat/ui&lt;/code&gt; branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt create feat/ui &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Add UI"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Graphite automatically sets &lt;code&gt;feat/api&lt;/code&gt; as the parent. You're now working on code that depends on unmerged changes.&lt;/p&gt;

&lt;p&gt;You now have two branches, stacked on top of each other, both ready to review. You can check the structure with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gt log short

◉  feat/ui
◯  feat/api
◯  main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Syncing to GitHub: one command
&lt;/h2&gt;

&lt;p&gt;Push everything to GitHub and create PRs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt submit &lt;span class="nt"&gt;--stack&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Graphite does three things automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detects both branches in your stack&lt;/li&gt;
&lt;li&gt;Creates two separate PRs on GitHub&lt;/li&gt;
&lt;li&gt;Sets the base branch correctly so diffs show only the new changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On running the command, you’ll be taken to a webpage, where you can add a description for the PRs, and publish them.&lt;/p&gt;

&lt;p&gt;On GitHub, the PR for the UI is based on the &lt;code&gt;feat/api&lt;/code&gt; branch, not &lt;code&gt;main&lt;/code&gt;. This means reviewers see only the UI changes, not the API code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling review feedback: the restack
&lt;/h2&gt;

&lt;p&gt;Your reviewer asks you to rename a variable in the API. In a normal Git workflow, this breaks everything. You'd need to manually rebase the UI branch and pray you don't hit conflicts.&lt;/p&gt;

&lt;p&gt;With Graphite, it's automatic.&lt;/p&gt;

&lt;p&gt;Check out the API branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt checkout feat/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Update variable name in server.js&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;todoList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// was 'todos'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt modify &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will amend the last commit to add the new changes. If you prefer to create a separate commit, you can use this command instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt modify &lt;span class="nt"&gt;--commit&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--message&lt;/span&gt; &lt;span class="s2"&gt;"Responded to reviewer feedback"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Graphite detects the change and automatically restacks (rebases) &lt;code&gt;feat/ui&lt;/code&gt; on top of the updated &lt;code&gt;feat/api&lt;/code&gt; code. No conflict resolution needed. No manual rebasing. It just works.&lt;/p&gt;

&lt;p&gt;Check your stack again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt log short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both branches are in sync. The UI branch has the latest API changes.&lt;/p&gt;

&lt;p&gt;Resubmit the PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt submit &lt;span class="nt"&gt;--stack&lt;/span&gt; &lt;span class="nt"&gt;--update-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pro tips for power users
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Visualize your stack.&lt;/strong&gt; Use &lt;code&gt;gt log short&lt;/code&gt; anytime to see where you are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt log short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows your branch hierarchy at a glance. It's like &lt;code&gt;git log&lt;/code&gt;, but focused on your stack structure instead of commit history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync frequently.&lt;/strong&gt; Start each work session with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pulls the latest changes from main, detects any merged PRs, and prompts you to delete local branches that are no longer needed. It keeps your stack clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're not locked in.&lt;/strong&gt; Merge PRs directly on GitHub if you want. Graphite figures it out. You can mix and match workflows. Some team members can use Graphite while others stick with vanilla Git.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your teammates don't need Graphite.&lt;/strong&gt; They review PRs on GitHub like normal. Graphite only changes your local workflow, not theirs. The PRs look identical to standard GitHub PRs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigate with commands.&lt;/strong&gt; Use &lt;code&gt;gt up&lt;/code&gt; and &lt;code&gt;gt down&lt;/code&gt; to move between branches in your stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Move to the parent branch&lt;/span&gt;
gt down

&lt;span class="c"&gt;# Move to the child branch  &lt;/span&gt;
gt up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is faster than typing out branch names, especially when you're deep in a stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Split changes later.&lt;/strong&gt; If you realize mid-branch that you should split the work, use &lt;code&gt;gt split&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt &lt;span class="nb"&gt;split&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Graphite will guide you through splitting commits into separate branches. Useful when you realize a "quick fix" turned into a bigger change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amend instead of adding commits.&lt;/strong&gt; Instead of creating multiple commits on one PR, amend your work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gt modify &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps each branch as a single, clean commit. When you need to address review feedback, amend again. The history stays linear and readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Stacking lets you code at the speed of thought, not the speed of CI. Small PRs get reviewed faster. Fewer bugs slip through. You stay in flow instead of waiting around.&lt;/p&gt;

&lt;p&gt;GitHub's review process is powerful, but it wasn't built for this workflow. Graphite fills the gap. It's like adding power steering to your development process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://graphite.com/docs/install-the-cli" rel="noopener noreferrer"&gt;Graphite CLI Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stacking.dev" rel="noopener noreferrer"&gt;Stacking.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://graphite.com/docs" rel="noopener noreferrer"&gt;Graphite Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have you tried stacking before? Drop a comment and let me know if you prefer big PRs or small stacks.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>git</category>
      <category>github</category>
      <category>graphite</category>
    </item>
    <item>
      <title>The 6 Best AI Code Review Tools for Pull Requests in 2025</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Fri, 12 Dec 2025 16:11:53 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/the-6-best-ai-code-review-tools-for-pull-requests-in-2025-4n43</link>
      <guid>https://dev.to/heraldofsolace/the-6-best-ai-code-review-tools-for-pull-requests-in-2025-4n43</guid>
      <description>&lt;p&gt;&lt;em&gt;A comprehensive comparison of platforms, bots, and agents to speed up your code review cycle.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction: The "Review Gap" in the Age of AI&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AI code generation tools like GitHub Copilot and Cursor have fundamentally changed how developers write code. What once took hours now takes minutes. A single developer can output 2-3x more code than before. But here's the problem I keep seeing with teams I talk to: while AI has accelerated code creation, human review capacity has remained completely flat.&lt;/p&gt;

&lt;p&gt;This mismatch has created what I call the "Review Gap." Your team is writing more code than ever, but you're still reviewing it the old way, one PR at a time, with the same limited human attention. Pull requests pile up. Context switching increases. Deployment velocity suffers.&lt;/p&gt;

&lt;p&gt;The solution isn't to hire more reviewers or work longer hours. The solution is to apply AI to the review process itself. Modern AI code review tools have evolved far beyond simple linters. They provide context-aware analysis that can summarize changes, catch subtle bugs, verify architectural alignment, and even suggest fixes, all in seconds rather than hours.&lt;/p&gt;

&lt;p&gt;But not all AI review tools are created equal. I tested the leading options based on three critical criteria:&lt;/p&gt;

&lt;p&gt;Context awareness: Does it understand your entire repository, or just the lines that changed?&lt;/p&gt;

&lt;p&gt;False positive rate: Does it waste your time with nitpicks, or does it flag genuinely important issues?&lt;/p&gt;

&lt;p&gt;Workflow integration: Does it fit naturally into your development process, or is it just another noisy bot cluttering your PR timeline?&lt;/p&gt;

&lt;p&gt;Let's look at the six best tools available in 2025.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff33zr82ab5689w22by83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff33zr82ab5689w22by83.png" alt="Graphite Agent" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Graphite Agent (Best Overall Platform)
&lt;/h2&gt;

&lt;p&gt;Category: Comprehensive review platform&lt;/p&gt;

&lt;p&gt;Why it wins: Graphite solves the root cause of slow reviews (workflow) and applies AI on top.&lt;/p&gt;

&lt;p&gt;Most AI code review tools are bots that bolt onto your existing GitHub workflow. They leave comments, generate summaries, and hope for the best. &lt;a href="https://graphite.dev/" rel="noopener noreferrer"&gt;Graphite&lt;/a&gt; takes a fundamentally different approach. It's a platform that rethinks the entire code review workflow, starting with how you structure your changes.&lt;/p&gt;

&lt;p&gt;Graphite's killer feature is stacked pull requests. Instead of creating one massive PR with 2,000 lines of changes, you break your work into small, atomic PRs that build on each other. Each PR in the stack is focused and reviewable. This approach dramatically improves review speed and quality, but here's the AI advantage: smaller PRs give AI dramatically better results.&lt;/p&gt;

&lt;p&gt;When Graphite Agent reviews a 200-line PR with a clear scope, it can provide genuinely useful feedback. It catches type errors, identifies potential race conditions, spots security vulnerabilities, and suggests optimizations. When competitors try to review a 2,000-line monolithic PR, their AI struggles with context and produces generic advice or misses critical issues entirely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://graphite.dev/features/agent" rel="noopener noreferrer"&gt;Graphite Agent&lt;/a&gt; isn't just a comment bot. It's an interactive companion that lives on your PR page. You can ask it questions like "What happens if this API endpoint receives a null value?" or "Is this change thread-safe?" It generates test plans, explains complex logic, and provides instant summaries of what changed and why.&lt;/p&gt;

&lt;p&gt;The integration feels seamless. Graphite Agent appears directly in Graphite's PR inbox, which provides a cleaner, faster interface than GitHub's native UI. Reviews happen in under 90 seconds. The AI maintains a sub-5% negative feedback rate, meaning developers trust its suggestions because they're rarely wrong.&lt;/p&gt;

&lt;p&gt;Pricing is straightforward and team-friendly. Graphite Agent is included in paid plans. The Team plan runs around $40 per user per month with unlimited AI reviews, making it cost-effective for organizations that review hundreds of PRs weekly.&lt;/p&gt;

&lt;p&gt;Best for: Teams who want to fundamentally speed up their development velocity, not just add a bot to GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13vqglizjoyqwcu4uktr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13vqglizjoyqwcu4uktr.png" alt="GitHub Copilot" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. GitHub Copilot (Best for IDE Integration)
&lt;/h2&gt;

&lt;p&gt;Category: IDE and native GitHub integration&lt;/p&gt;

&lt;p&gt;Overview: The default choice for many teams. GitHub Copilot excels at "in-the-flow" assistance while writing code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; started as an autocomplete tool but has expanded into code review territory. If you're already using VS Code with Copilot, you get basic PR review features built in. Copilot can generate PR descriptions, summarize changes, and leave inline comments on pull requests through GitHub's native interface (especially with the Enterprise tier).&lt;/p&gt;

&lt;p&gt;The main advantage is deep integration with the Microsoft ecosystem. Copilot works seamlessly in VS Code, integrates with GitHub Enterprise, and meets enterprise compliance requirements out of the box. For organizations heavily invested in Microsoft tooling, this integration is valuable. You get a single vendor, unified billing, and consistent security policies across development tools.&lt;/p&gt;

&lt;p&gt;The "Copilot Workspace" feature allows developers to describe desired changes in natural language, and Copilot suggests code modifications. This works well for small refactoring tasks or implementing simple features. The AI has improved significantly over the past year and can handle increasingly complex requests.&lt;/p&gt;

&lt;p&gt;That said, Copilot's PR review experience has notable limitations compared to specialized agents. The analysis often stays surface-level, focused on style and obvious bugs rather than architectural concerns or subtle logic errors. Early adopters report that Copilot's PR comments can be noisy if not carefully configured. The AI doesn't have the same "agentic" feel as specialized tools. It provides suggestions but lacks the deeply interactive, conversational interface that helps developers dig deeper into complex issues on the PR timeline itself.&lt;/p&gt;

&lt;p&gt;Pricing varies by plan. GitHub Copilot Individual is $10 per month, while Copilot Business is $19 per user per month. Organizations paying for GitHub Enterprise receive the most advanced PR summarization and review features as part of their contract.&lt;/p&gt;

&lt;p&gt;Best for: Teams fully committed to the Microsoft ecosystem who want a safe, baseline AI experience without changing their workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nyoon9md5pg4c2g8fp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3nyoon9md5pg4c2g8fp8.png" alt="CodeRabbit" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. CodeRabbit (Best Standalone Bot)
&lt;/h2&gt;

&lt;p&gt;Category: Third-party PR bot&lt;/p&gt;

&lt;p&gt;Overview: A popular tool that connects to GitHub, GitLab, or Bitbucket and provides detailed AI reviews through PR comments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://coderabbit.ai/" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; has established itself as the leading third-party AI review bot. It offers a rich feature set that goes well beyond basic static analysis. When you open a PR, CodeRabbit automatically generates a detailed "walkthrough" summary explaining what changed and why. It uses a combination of large language models and traditional linters (it runs over 40 different code analyzers) to provide comprehensive feedback.&lt;/p&gt;

&lt;p&gt;The chat interface is a standout feature. You can have a conversation with CodeRabbit directly in PR comments, asking follow-up questions or requesting clarification on its suggestions. This makes the review process more interactive and helps developers understand the reasoning behind each recommendation.&lt;/p&gt;

&lt;p&gt;CodeRabbit is highly configurable. You can tune its "nitpickiness" level, define custom rules for your codebase, and train it to learn from your team's feedback over time. It integrates with multiple platforms (GitHub, GitLab, Bitbucket, Azure DevOps) and offers both cloud and self-hosted deployment options for teams with strict security requirements.&lt;/p&gt;

&lt;p&gt;The main drawback is noise. CodeRabbit leaves many comments on each PR. While comprehensive analysis can be valuable, it also clutters the GitHub timeline and can overwhelm developers, especially on larger changes. You'll need to invest time in configuration to dial down irrelevant feedback. Some teams report that CodeRabbit's enthusiasm for suggesting improvements, while well-intentioned, creates review fatigue.&lt;/p&gt;

&lt;p&gt;Pricing is competitive. CodeRabbit offers a free tier for open-source projects and personal use. Paid plans range from $12 per user per month(Lite) to $24-$30 per user per month (Pro). The Pro tier includes all features: advanced linters, chat capabilities, and detailed reporting.&lt;/p&gt;

&lt;p&gt;Best for: Teams who want to keep the native GitHub UI but need better automated feedback, and who don't mind investing time in configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93zauvxzfdy3a0ts9zcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93zauvxzfdy3a0ts9zcb.png" alt="Greptile" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Greptile (Best for Deep Context and RAG)
&lt;/h2&gt;

&lt;p&gt;Category: Specialized context engine&lt;/p&gt;

&lt;p&gt;Overview: Focuses heavily on understanding your entire codebase, not just the diff in each PR.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://greptile.com/" rel="noopener noreferrer"&gt;Greptile&lt;/a&gt; takes a unique approach to AI code review by building a comprehensive knowledge graph of your entire repository. It indexes every function, every dependency, every historical change, and uses this context to provide unusually deep analysis. When reviewing a PR, Greptile doesn't just look at the changed lines. It understands how those changes ripple through your entire codebase.&lt;/p&gt;

&lt;p&gt;This approach excels at answering complex questions that require cross-file or cross-module understanding. For example: "How does this API change affect the billing service?" or "What other components depend on this function?" Greptile can trace dependencies, identify potential breaking changes, and spot issues that would be nearly impossible for a human reviewer to catch without hours of investigation.&lt;/p&gt;

&lt;p&gt;The tool is particularly valuable for large, legacy codebases where understanding context is the hardest part of code review. New team members benefit enormously from Greptile's ability to explain how different parts of the system interact. Senior engineers appreciate having an AI that can flag subtle architectural issues that simple diff-based analysis would miss.&lt;/p&gt;

&lt;p&gt;Greptile emphasizes customization and enterprise features. You can define private AI models, create custom rules specific to your organization, and deploy Greptile on-premises for complete control over your data. The tool meets SOC 2 compliance standards and integrates with both GitHub and GitLab.&lt;/p&gt;

&lt;p&gt;The trade-off is complexity. Greptile's full-repository analysis takes time to set up and requires more computational resources than simpler diff-based tools. The platform is priced for enterprise use at around $30 per user per month for cloud deployment, with custom pricing for self-hosted options. For smaller teams or simpler codebases, this may be more powerful than necessary.&lt;/p&gt;

&lt;p&gt;Best for: Large, complex monorepos where understanding the impact of changes across the entire system is the hardest part of code review.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d531dwkt9fcptj0tuvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d531dwkt9fcptj0tuvs.png" alt="Ellipsis" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Ellipsis (Best for Automated Fixes)
&lt;/h2&gt;

&lt;p&gt;Category: Action-oriented agent&lt;/p&gt;

&lt;p&gt;Overview: An AI agent that can take reviewer comments and automatically implement the requested changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ellipsis.dev/" rel="noopener noreferrer"&gt;Ellipsis&lt;/a&gt; bridges the gap between review and implementation. Most AI review tools identify issues and leave comments. Ellipsis goes further: it can read a reviewer's comment ("Make this variable const" or "Add input validation here") and automatically generate a commit with the fix.&lt;/p&gt;

&lt;p&gt;This capability is genuinely useful for reducing the back-and-forth in code reviews. Instead of the original author context-switching back to their IDE, finding the file, making the change, and pushing a new commit, Ellipsis handles it automatically. For minor refactoring tasks, style fixes, or simple logic adjustments, this saves significant time.&lt;/p&gt;

&lt;p&gt;The tool works by maintaining a detailed understanding of your codebase and coding standards. When it receives a request to implement a change, it generates the code, runs tests to verify nothing breaks, and commits the result. You can review and approve the change before it's applied, maintaining control over what gets merged.&lt;/p&gt;

&lt;p&gt;Ellipsis integrates with GitHub and supports multiple programming languages. The AI is trained on millions of open-source repositories, giving it broad knowledge of common patterns and best practices. It's particularly effective for teams that follow consistent coding conventions, as the AI can learn and replicate those patterns.&lt;/p&gt;

&lt;p&gt;The limitations are predictable: Ellipsis handles simple, mechanical changes well but struggles with complex logic or architectural refactoring. It works best as a junior engineer who can take clear direction and implement straightforward fixes. For ambiguous or high-complexity changes, human implementation is still necessary.&lt;/p&gt;

&lt;p&gt;Pricing has recently shifted to a seat-based model. Ellipsis charges $20 per user per month for unlimited usage, moving away from previous per-commit pricing structures. This makes it predictable for teams to adopt.&lt;/p&gt;

&lt;p&gt;Best for: Teams who spend too much time on minor refactoring cycles and want to automate the implementation of simple reviewer feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1oug1e5ld8x5foh63fm9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1oug1e5ld8x5foh63fm9.png" alt="BugBot" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. BugBot by Cursor (Best for Security and Logic Errors)
&lt;/h2&gt;

&lt;p&gt;Category: Specialized defect detection&lt;/p&gt;

&lt;p&gt;Overview: A tool trained specifically to find logic bugs and security vulnerabilities, developed as part of the Cursor ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cursor.com/bugbot" rel="noopener noreferrer"&gt;BugBot&lt;/a&gt; takes a surgical approach to code review. Instead of trying to do everything, it focuses exclusively on finding critical bugs and security issues. It's designed to act as a "pre-merge safety net" that catches hard-to-spot problems before they reach production.&lt;/p&gt;

&lt;p&gt;The tool excels at identifying edge cases, race conditions, null pointer exceptions, and security vulnerabilities. It's particularly effective at reviewing AI-generated code, where subtle logic errors are more common than with human-written code. BugBot's analysis is highly precise. It maintains a low false-positive rate by focusing only on genuinely problematic code rather than style issues or minor optimizations.&lt;/p&gt;

&lt;p&gt;BugBot is deeply integrated with the &lt;a href="https://cursor.com/" rel="noopener noreferrer"&gt;Cursor IDE&lt;/a&gt;. When it flags an issue, you can jump directly to the problematic code in your editor and apply the suggested fix with one click. The tight IDE integration makes the review-fix-verify cycle extremely fast.&lt;/p&gt;

&lt;p&gt;The main limitation is scope. BugBot doesn't generate PR summaries, doesn't provide architectural feedback, and doesn't help with code documentation or readability. It has one job: find critical bugs. For teams that need comprehensive review assistance, BugBot would need to be paired with other tools.&lt;/p&gt;

&lt;p&gt;Adoption requires committing to the Cursor ecosystem. If your team uses VS Code, IntelliJ, or other editors, switching to Cursor represents a significant change. Some developers love Cursor's AI-first features, others prefer their existing tools.&lt;/p&gt;

&lt;p&gt;Pricing is currently tied to your Cursor subscription. It's generally included in Cursor's paid plans (Pro/Business) which start at $20 per user per month, rather than being sold as a standalone bot.&lt;/p&gt;

&lt;p&gt;Best for: High-compliance industries or mission-critical codebases where preventing logic errors and security vulnerabilities is paramount, and teams willing to adopt the Cursor IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Comparison Table: At a Glance&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Primary Focus&lt;/th&gt;
&lt;th&gt;Context Awareness&lt;/th&gt;
&lt;th&gt;Pricing Model&lt;/th&gt;
&lt;th&gt;Best Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Graphite Agent&lt;/td&gt;
&lt;td&gt;Complete platform (workflow + AI)&lt;/td&gt;
&lt;td&gt;PR diff + relevant context&lt;/td&gt;
&lt;td&gt;$40/user/month&lt;/td&gt;
&lt;td&gt;Teams wanting to fundamentally improve velocity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;IDE integration + basic PR review&lt;/td&gt;
&lt;td&gt;Limited to diff&lt;/td&gt;
&lt;td&gt;$19/user/month (Business)&lt;/td&gt;
&lt;td&gt;Microsoft ecosystem commitment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CodeRabbit&lt;/td&gt;
&lt;td&gt;Comprehensive PR bot&lt;/td&gt;
&lt;td&gt;PR diff + linters&lt;/td&gt;
&lt;td&gt;$12-30/user/month&lt;/td&gt;
&lt;td&gt;Teams keeping native GitHub UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Greptile&lt;/td&gt;
&lt;td&gt;Deep codebase analysis (RAG)&lt;/td&gt;
&lt;td&gt;Full repository graph&lt;/td&gt;
&lt;td&gt;$30/user/month&lt;/td&gt;
&lt;td&gt;Large monorepos, complex dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ellipsis&lt;/td&gt;
&lt;td&gt;Automated fix implementation&lt;/td&gt;
&lt;td&gt;PR diff + coding standards&lt;/td&gt;
&lt;td&gt;$20/user/month&lt;/td&gt;
&lt;td&gt;Reducing refactoring cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BugBot&lt;/td&gt;
&lt;td&gt;Critical bug detection&lt;/td&gt;
&lt;td&gt;PR diff focused on logic/security&lt;/td&gt;
&lt;td&gt;Included in Cursor Sub ($20+)&lt;/td&gt;
&lt;td&gt;High-compliance, mission-critical code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to Choose the Right Tool&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For pure speed and workflow improvement: Choose Graphite. The combination of stacked PRs and integrated AI provides the fastest path from code complete to merge. You're not just adding AI to a slow process, you're fixing the process itself.&lt;/p&gt;

&lt;p&gt;For massive monorepo querying: Choose Greptile. If your biggest challenge is understanding how changes ripple through a large, complex codebase, Greptile's full-repository analysis provides unmatched context.&lt;/p&gt;

&lt;p&gt;For simple summaries on GitHub: Choose CodeRabbit or GitHub Copilot. If you want to keep your existing workflow and just add AI feedback, either of these bots will provide useful suggestions without requiring major changes to how your team works.&lt;/p&gt;

&lt;p&gt;For mission-critical bug prevention: Choose BugBot (Cursor). If you're in a regulated industry or working on code where bugs have severe consequences, BugBot's laser focus on critical defects provides valuable protection.&lt;/p&gt;

&lt;p&gt;For reducing trivial back-and-forth: Choose Ellipsis. If your reviews are slow because of many small fix requests, having an AI that can implement those fixes automatically saves significant time.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AI code review tools are no longer optional. They're essential for handling the volume of code being generated today. The Review Gap is real, and it's growing. Teams that don't adopt AI assistance will find themselves increasingly unable to keep up with the pace of development.&lt;/p&gt;

&lt;p&gt;That said, not all AI tools address the underlying problem. Most are bots that add automation to a fundamentally slow, inefficient workflow. They help, but they're band-aids on a broken process.&lt;/p&gt;

&lt;p&gt;To truly fix code review, you need a platform that incentivizes better practices (smaller, more focused PRs) and reviews them with AI. That's why Graphite stands out. It's not just an AI reviewer, it's a complete rethinking of how code review should work in the age of AI-accelerated development.&lt;/p&gt;

&lt;p&gt;The best teams are shipping faster than ever while maintaining higher code quality. They're doing it by combining better workflow practices with AI assistance. If you're still relying on manual reviews of large, monolithic PRs, you're leaving velocity and quality on the table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://graphite.dev/" rel="noopener noreferrer"&gt;Try Graphite Agent today&lt;/a&gt; — it's included in every Graphite plan. Sign up and review your first stack in minutes.&lt;/p&gt;

</description>
      <category>codereview</category>
      <category>programming</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>How to use Cursor to Generate API Testcases in Requestly</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Thu, 27 Nov 2025 14:02:16 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/how-to-use-cursor-to-generate-api-testcases-in-requestly-4pn</link>
      <guid>https://dev.to/heraldofsolace/how-to-use-cursor-to-generate-api-testcases-in-requestly-4pn</guid>
      <description>&lt;p&gt;API testing is one of those tasks every developer knows is essential, but few enjoy. Manually writing test cases for every endpoint is repetitive, error-prone, and consumes valuable time that could be spent building features. Edge cases are often skipped, test coverage suffers, and teams frequently find themselves maintaining brittle scripts.&lt;/p&gt;

&lt;p&gt;That’s where automation changes the game. By pairing &lt;a href="https://cursor.com" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, an AI-powered coding assistant, with &lt;a href="https://requestly.com" rel="noopener noreferrer"&gt;Requestly's&lt;/a&gt; local-first API testing and mocking platform, you can offload the grunt work of writing tests to AI while keeping execution secure and reproducible on your own system. In this article, we’ll walk through how to set up Cursor with Requestly, generate test cases automatically, and run them end-to-end so that you can focus less on boilerplate and more on shipping features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Automate API Test Case Generation?
&lt;/h2&gt;

&lt;p&gt;Traditionally, writing API test cases involves manually scripting tests using the request and response schema. This process is slow, error-prone, and often results in incomplete coverage because developers don’t have time to test every possible scenario.&lt;/p&gt;

&lt;p&gt;The challenges include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual scripting overhead:&lt;/strong&gt; Every endpoint and method must be coded by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incomplete coverage:&lt;/strong&gt; Edge cases and negative tests are frequently skipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High setup cost:&lt;/strong&gt; Establishing a reusable test framework requires a significant amount of time.&lt;/p&gt;

&lt;p&gt;This is where Cursor provides a significant advantage. Because it’s an AI-powered coding assistant, Cursor can quickly generate test cases based on endpoint definitions, documentation, or even just example payloads. It understands context and can suggest multiple variations, including edge and error scenarios, without requiring hours of manual coding.&lt;/p&gt;

&lt;p&gt;With Requestly's &lt;a href="https://docs.requestly.com/general/team/local-workspace" rel="noopener noreferrer"&gt;Local Workspaces&lt;/a&gt;, integrating Cursor becomes a breeze. Thanks to the local workspaces, all your requests are stored in the local filesystem as JSON files. For example, the following request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cr42l7174gpbg8yvopw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cr42l7174gpbg8yvopw.png" alt="A sample test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;becomes a JSON file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{base_url}}/user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"preRequest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"postResponse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rq.test(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Request is successful (2XX)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, () =&amp;gt; {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; rq.response.to.be.ok&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;});"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"queryParams"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contentType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"currentAuthType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INHERIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"authConfigStore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note that the post-response script is stored in the &lt;code&gt;scripts.postResponse&lt;/code&gt; key as a string.&lt;/p&gt;

&lt;p&gt;Unlike other Cloud-based tools like Postman, the local nature of Requestly means you can feed the files directly to Cursor, and Cursor can generate test cases and edit the files. These files can be checked into version control. With this setup, teams can automate the generation of API tests, and can collaborate and review the test files, thereby saving a huge amount of time.&lt;/p&gt;
&lt;h2&gt;
  
  
  API Testing with Requestly and Cursor
&lt;/h2&gt;

&lt;p&gt;As an example of a practical API to test, we'll be using the &lt;a href="https://docs.github.com/en/rest" rel="noopener noreferrer"&gt;GitHub REST API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To follow along with this tutorial, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt; installed on your computer.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token" rel="noopener noreferrer"&gt;GitHub Personal Access Token (PAT)&lt;/a&gt;. Choose a repository where you have write access and give it read-write access for issues.&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://requestly.com/downloads/" rel="noopener noreferrer"&gt;Requestly desktop app&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cursor.com/download" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Setting up Requestly
&lt;/h3&gt;

&lt;p&gt;I have already created a &lt;a href="https://github.com/heraldofsolace/requestly-demo" rel="noopener noreferrer"&gt;repo&lt;/a&gt; that contains a Requestly collection. Clone the repo to your machine.&lt;/p&gt;

&lt;p&gt;Start Requestly and click on the &lt;strong&gt;Workspaces&lt;/strong&gt; dropdown in the top-left corner and select &lt;strong&gt;+ Add&lt;/strong&gt;. Select the directory where you cloned the app, and write &lt;code&gt;requestly-demo&lt;/code&gt; as the name of the workspace. This will load the workspace of the same name that already exists in the directory.&lt;/p&gt;

&lt;p&gt;Once the workspace is ready, navigate to the &lt;strong&gt;APIs&lt;/strong&gt; tab, where you'll find the prepared &lt;strong&gt;GitHub API&lt;/strong&gt; collection. In the &lt;strong&gt;Environments&lt;/strong&gt; tab, you'll find an &lt;a href="https://docs.requestly.com/general/api-client/environments-and-variables/environment-variables" rel="noopener noreferrer"&gt;Environment&lt;/a&gt; named "Dev". Update the variables with your credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;token&lt;/code&gt;: Your PAT&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;owner&lt;/code&gt;: Your GitHub username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repo&lt;/code&gt;: The name of the repo that you chose&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since the environments store the PAT in plaintext, It's recommended to add the &lt;code&gt;requestly-demo/environments&lt;/code&gt; directory to .gitignore. Or, you could use a pre-commit hook to remove the PAT from the environment JSON upon commiting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Setting up Cursor Rules
&lt;/h3&gt;

&lt;p&gt;By default, Cursor can generate test cases for requests, since Requestly uses the commonly used &lt;a href="https://www.chaijs.com/" rel="noopener noreferrer"&gt;Chai.js&lt;/a&gt; syntax. However, Requestly adds a few syntactic sugars and abstractions on top of the Chai.js library. We can inform Cursor about these features and provide it with some instructions by creating a &lt;a href="https://cursor.com/docs/context/rules" rel="noopener noreferrer"&gt;project rule&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.cursor&lt;/code&gt; directory in the root of the project and create a file named &lt;code&gt;test-rules.mdc&lt;/code&gt; inside it with the following content. These instructions tell Cursor everything it needs to know for generating tests.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;





&lt;p&gt;As you can see, we have iterated on some of the testing features provided by Requestly and have also provided some custom instructions. The &lt;code&gt;globs: requestly-demo/**/*.json&lt;/code&gt; line tells Cursor to automatically load this rule whenever you're working with one of the Requestly files.&lt;/p&gt;

&lt;p&gt;Since we're using the GitHub API, which is a well-documented public API, Cursor is already aware of the request and response structures. However, if you're testing your own API, it's a good idea to let Cursor know about your schemas by referencing your OpenAPI specification file. You can reference files in the rules by using &lt;code&gt;@filename&lt;/code&gt;. For example, you can add a line like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Use the OpenAPI specification for request and response structure: @schema.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generating Tests with Cursor
&lt;/h3&gt;

&lt;p&gt;Now that the Cursor rules are in place, let's test it out. Open the &lt;code&gt;requestly-demo/GitHub REST API/a862b625-82ce-4ad2-9c41-4cda55e1bb5e.json&lt;/code&gt; file, which creates an issue in your repo.&lt;/p&gt;

&lt;p&gt;Write "Generate test cases for this request. Show the test cases in this window before editing the file. Wait for my confirmation before editing the file" and hit enter.&lt;/p&gt;

&lt;p&gt;Cursor will generate a few test cases and show you in the chat window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipudjcvuvzxalkni8vzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipudjcvuvzxalkni8vzb.png" alt="The generated tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a good idea to check that the test cases satisfy your expectations before writing them to the file. You can also ask it to add new tests, or change/remove any test you want. Once you're happy, ask it to update the file. Cursor will stringify the tests and write it in the &lt;code&gt;scripts.postResponse&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flue0ngqgy9t3vca6four.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flue0ngqgy9t3vca6four.png" alt="The tests are saved to the file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Accept the changes so that the file is updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the Tests
&lt;/h3&gt;

&lt;p&gt;Go back to the Requestly Window and open the &lt;strong&gt;Create issue&lt;/strong&gt; request. Navigate to the &lt;strong&gt;Scripts&amp;gt; Post-response&lt;/strong&gt; tab, and you should see the tests Cursor just generated!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvu9r71mzdvq9t2wed8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvu9r71mzdvq9t2wed8z.png" alt="The tests in Requestly"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: If the tests don't appear in Requestly, you may need to reload the workspace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Click on &lt;strong&gt;Send&lt;/strong&gt; to run the tests. You should see the tests pass. Now you’ve got tests running without writing a single line by hand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iterate and Improve
&lt;/h3&gt;

&lt;p&gt;As with any AI tool, Cursor can make mistakes and may generate a faulty test. Additionally, your API can evolve over time, and your tests may need to be updated accordingly. Simply provide feedback to Cursor and ask it to update the tests. For example, in the tests Cursor generated for me, it included checks for various fields, such as &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, and so on. I can ask it to only check for the &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepmsrr2s0c8egzjp1pge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepmsrr2s0c8egzjp1pge.png" alt="Updated instructions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The updated tests now only check for &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yz2blfx6rgiieqiuxop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yz2blfx6rgiieqiuxop.png" alt="The updated tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, if the AI generates a faulty output (for example, a malformed JSON), you can prompt it to fix the mistake, and repeat this process until you get what you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling and Bulk Automation
&lt;/h3&gt;

&lt;p&gt;You saw how Cursor can help you generate and refine test cases. However, if you have a large number of endpoints, it's not feasible to repeat this process for each endpoint one by one. In this situation, you can use the &lt;a href="https://cursor.com/docs/cli/overview" rel="noopener noreferrer"&gt;Cursor CLI&lt;/a&gt; to generate tests in bulk. This process has the added benefit that you can integrate with a file watcher, like &lt;a href="https://emcrisostomo.github.io/fswatch/" rel="noopener noreferrer"&gt;fswatch&lt;/a&gt;, or a Git pre-commit hook to automatically generate tests whenever the request JSON is changed. Let's see this in action.&lt;/p&gt;

&lt;p&gt;After installing Cursor CLI, run the follwing command in the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cursor-agent &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"add tests to all the requestly files in requestly-demo/GitHub REST API/. Read instructions from .cursor/rules/test-rules.mdc"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor will now go ahead and add tests to all the requests.&lt;/p&gt;

&lt;p&gt;This process has a disadvantage that you won't be able to verify the tests before Cursor writes them to the file. So, be sure to check that the tests run properly before commiting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for AI-Generated API Testing
&lt;/h2&gt;

&lt;p&gt;AI-assisted testing works best when you set it up for success. Cursor and Requestly make the workflow faster and safer, but the quality of your tests still depends on how you guide and maintain the process. Here are some best practices to keep your testing reliable over time:&lt;/p&gt;

&lt;h3&gt;
  
  
  Write Clear Prompts for Cursor
&lt;/h3&gt;

&lt;p&gt;Cursor will only be as precise as the instructions you provide. Don’t just say “write a test”. Instead, specify the fields you care about, the types of responses you expect, and whether you want edge or negative cases included. For example:&lt;/p&gt;

&lt;p&gt;"Generate tests for this request. Check for a 200 status, verify title and body fields exist, and add one negative test for unauthorized access."&lt;/p&gt;

&lt;p&gt;The more context you give, the less cleanup you’ll need later. You can also add repetitive prompts to the project rules so that they're applied every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always Review and Validate AI Output
&lt;/h3&gt;

&lt;p&gt;Even with good prompts, AI may generate redundant, flaky, or irrelevant tests. Treat Cursor’s output as a draft: review it, remove what you don’t need, and refine. Requestly makes this easy since tests are just JSON with embedded scripts - you can see exactly what changed before committing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Requestly Mocks and Intercepts for Safer Testing
&lt;/h3&gt;

&lt;p&gt;Not every test should hit the real API. For failure cases, like 500 errors or rate limits, use Requestly’s &lt;a href="https://docs.requestly.com/general/api-mocking/api-mocking" rel="noopener noreferrer"&gt;mocks&lt;/a&gt; and &lt;a href="https://docs.requestly.com/general/http-interceptor/overview" rel="noopener noreferrer"&gt;intercepts&lt;/a&gt; to simulate responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep Secrets Local
&lt;/h3&gt;

&lt;p&gt;Never paste API tokens, passwords, or other secrets into Cursor prompts. Store them in Requestly environments and variables instead. This way, you can safely run AI-generated tests without exposing credentials. Use development credentials in the development environment and put the Requestly environments folder in gitignore to prevent accidentally committing them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Version Control Your Tests
&lt;/h3&gt;

&lt;p&gt;Because Requestly stores collections and tests as JSON files, you can commit them directly to Git. This means your team can review, track history, and collaborate on test changes just as they do with application code. Each AI-generated test case can be peer-reviewed before merging — ensuring quality and accountability as your test suite scales.&lt;/p&gt;

&lt;p&gt;With this setup, teams can save a huge chunk of testing time, especially when managing multi-endpoint APIs with frequent updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iterate Instead of One-Shot Generation
&lt;/h3&gt;

&lt;p&gt;Don’t expect perfect tests on the first try. Use Cursor iteratively: generate, review, adjust prompts, regenerate. Over time, you’ll build a solid suite of reusable tests tuned to your API’s quirks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combine AI Speed with Human Insight
&lt;/h3&gt;

&lt;p&gt;AI is great at generating lots of cases quickly, but you know your API's edge cases, failure modes, and business logic better than any model. Utilize AI for coverage and scaffolding, then supplement with human-crafted tests for the scenarios that truly matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining Cursor and Requestly, you can cut down on repetitive test writing while improving API test coverage. Cursor helps you quickly generate comprehensive test cases, while Requestly ensures those tests run securely in a local-first environment.&lt;/p&gt;

&lt;p&gt;The result: faster iteration, stronger reliability, and safer execution. If you’re looking to modernize your API testing workflow, give this combination &lt;a href="https://requestly.com/downloads/" rel="noopener noreferrer"&gt;a try&lt;/a&gt; - you’ll spend less time writing boilerplate tests and more time building what matters.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>api</category>
      <category>apitesting</category>
    </item>
    <item>
      <title>How to ace API Testing with Requestly</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Tue, 23 Sep 2025 06:45:59 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/how-to-ace-api-testing-with-requestly-hg3</link>
      <guid>https://dev.to/heraldofsolace/how-to-ace-api-testing-with-requestly-hg3</guid>
      <description>&lt;p&gt;Imagine this scenario: You’re debugging an API call. The endpoint only works with production tokens, but your API testing tool insists on syncing everything to the cloud. Suddenly, your production credentials are sitting on someone else’s servers, risking potential exposure.&lt;/p&gt;

&lt;p&gt;Most tools default to cloud-first, which can be limiting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your secrets live in the cloud.&lt;/li&gt;
&lt;li&gt;Most features require an online account.&lt;/li&gt;
&lt;li&gt;Tests aren’t versioned with your code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Requestly, you get a local-first experience. Your API tests run on your machine, alongside your code, version-controlled just like the rest of your workflow. Your secrets and credentials stay on your device, safe and secure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local-first API testing with Requestly
&lt;/h2&gt;

&lt;p&gt;With cloud-first API testing tools, your sensitive information lives in the cloud. One misconfigured account, one permission too broad, and you're risking &lt;a href="https://treblle.com/blog/apis-exposed-postman-data-breach-lessons" rel="noopener noreferrer"&gt;exposing your sensitive information&lt;/a&gt; to the world. Requestly emphasizes local-first API testing that lets developers test APIs without depending on remote setups. You run everything right on your own machine, so you can intercept requests, mock responses, or reproduce tricky edge cases in seconds, all while keeping tokens and sensitive data safe. Because it’s all local, you get fast, reliable feedback that makes debugging smoother and development less painful.&lt;/p&gt;

&lt;p&gt;This local-first approach means your tests can be stored next to your code, under Git. By keeping your Requestly assets under Git, you gain the benefits of collaborative development: teammates can review changes through pull requests, track history with commits, and roll back to previous versions if needed. This also fits naturally into existing workflows - your API test rules evolve alongside the code they validate.&lt;/p&gt;

&lt;p&gt;In the following few sections of this article, you'll see Requestly in action. As an example of a practical API to test, we'll be using the &lt;a href="https://docs.github.com/en/rest" rel="noopener noreferrer"&gt;GitHub REST API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Testing with Requestly
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, you'll need the following dependencies on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Latest version of &lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; and npm.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token" rel="noopener noreferrer"&gt;GitHub Personal Access Token (PAT)&lt;/a&gt;. Choose a repository where you have write access and give it read-write access for issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up Requestly
&lt;/h3&gt;

&lt;p&gt;To make things easy, I have already created a &lt;a href="https://github.com/heraldofsolace/requestly-demo" rel="noopener noreferrer"&gt;repo&lt;/a&gt; that contains the basic setup needed for this article. Clone the repo to your machine.&lt;/p&gt;

&lt;p&gt;Install the dependencies:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You can either install Requestly as a browser extension or as a desktop app. You can read about the feature differences between these two versions &lt;a href="https://docs.requestly.com/general/others/how-is-bowser-extension-different-from-a-desktop-app" rel="noopener noreferrer"&gt;here&lt;/a&gt;. For this tutorial, I'll use the desktop app, which you can download from &lt;a href="https://requestly.com/downloads/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. So go ahead and install it.&lt;/p&gt;

&lt;p&gt;Requestly works on the concept of &lt;a href="https://docs.requestly.com/general/team/workspaces" rel="noopener noreferrer"&gt;&lt;strong&gt;Workspaces&lt;/strong&gt;&lt;/a&gt;. The demo app already has a local workspace stored, which you need to load. Click on the &lt;strong&gt;Workspaces&lt;/strong&gt; dropdown in the top-left corner and select &lt;strong&gt;+ Add&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Choose the directory where you just cloned the app, and use &lt;code&gt;requestly-demo&lt;/code&gt; as the name of the workspace. This will load the workspace of the same name that is already in the directory.&lt;/p&gt;

&lt;p&gt;Once the workspace is ready, navigate to the &lt;strong&gt;APIs&lt;/strong&gt; tab, where you'll find the &lt;strong&gt;GitHub API&lt;/strong&gt; collection. I have already prepared the requests so that we can focus on writing tests in this article. However, if you want to learn more about how to make requests, you can visit the &lt;a href="https://docs.requestly.com/general/api-client/send-api-request" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkc3l39up3181vtafecbv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkc3l39up3181vtafecbv.png" alt="List of prepared requests" width="728" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Apart from REST APIs, Requestly also supports making &lt;a href="https://docs.requestly.com/general/api-client/graphql-request" rel="noopener noreferrer"&gt;GraphQL requests&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is also an &lt;a href="https://docs.requestly.com/general/api-client/environments-and-variables/environment-variables" rel="noopener noreferrer"&gt;Environment&lt;/a&gt; named "Dev" where you'll need to put your credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;token&lt;/code&gt;: Your PAT&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;owner&lt;/code&gt;: Your GitHub username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repo&lt;/code&gt;: The name of the repo that you chose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll start with simple tests, then advance to schema validation and testing workflows. We'll see how to debug and handle errors, and finally, we'll mock a third-party API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Our First Test
&lt;/h3&gt;

&lt;p&gt;If authentication breaks, your entire API is toast! So, we'll start by testing the &lt;strong&gt;Login&lt;/strong&gt; endpoint. This will show you an example of how to handle authentication. If you click on the collection name and navigate to the &lt;strong&gt;Authorization&lt;/strong&gt; tab, you'll see that the authorization type has been set to &lt;strong&gt;Bearer Token&lt;/strong&gt;. For the token value, we're using &lt;code&gt;{{token}}&lt;/code&gt;, which retrieves the value from the environment variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9fokeyynz4d2tf4d7xg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy9fokeyynz4d2tf4d7xg.png" alt="Authorization setup" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every endpoint in this collection inherits this authentication method, which means they'll automatically pass the token when you make the request.&lt;/p&gt;

&lt;p&gt;Open the &lt;strong&gt;Login&lt;/strong&gt; request and navigate to the &lt;strong&gt;Scripts&lt;/strong&gt; tab. Requestly allows you to run &lt;a href="https://docs.requestly.com/general/api-client/scripts" rel="noopener noreferrer"&gt;scripts&lt;/a&gt; before the request is sent (Pre-request scripts), or after the response is received (Post-response scripts). For the purpose of writing API tests, we'll use the Post-response scripts.&lt;/p&gt;

&lt;p&gt;Requestly uses &lt;a href="https://www.chaijs.com/api/bdd/" rel="noopener noreferrer"&gt;Chai.js&lt;/a&gt; for assertions. It exposes the &lt;code&gt;expect&lt;/code&gt; method of Chai through &lt;code&gt;rq.expect()&lt;/code&gt;, which you can use to write assertions. Requestly also lets you access the request and response objects through &lt;code&gt;rq.request&lt;/code&gt; and &lt;code&gt;rq.response&lt;/code&gt; respectively, which can be used in the tests.&lt;/p&gt;

&lt;p&gt;Our first test will make sure that the server returns &lt;code&gt;200 Ok&lt;/code&gt; response. Let's start by creating a test. We want to check that &lt;code&gt;rq.response.status&lt;/code&gt; is equal to &lt;code&gt;OK&lt;/code&gt;. So, our test will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login is successful&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you click on &lt;strong&gt;Send&lt;/strong&gt;, Requestly will make the request and run the tests. You can see the result of the test in the &lt;strong&gt;Tests&lt;/strong&gt; tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9yfbws79hokthn5dtxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9yfbws79hokthn5dtxv.png" alt="The tests tab" width="663" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Requestly also allows you to check the status code using its name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find all the status codes in the &lt;a href="https://docs.requestly.com/general/api-client/tests" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Authentication
&lt;/h3&gt;

&lt;p&gt;Now we know that the login request is successful, but how do we know that the login itself works correctly? The login endpoint also returns the logged-in user's data, which we can validate against the ground truth.&lt;/p&gt;

&lt;p&gt;Here we can check that the &lt;code&gt;login&lt;/code&gt; property matches our username that we put into the &lt;code&gt;owner&lt;/code&gt; environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login returns valid data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send the requests, and if everything goes correctly, you should see the tests pass.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0lfz0lx2ar7idhenf3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0lfz0lx2ar7idhenf3r.png" alt="The tests result tab" width="658" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema Validation
&lt;/h3&gt;

&lt;p&gt;APIs act as contracts between services, and if the structure (fields, types, nesting, required attributes) changes, consumers may break, even if the data looks valid. This is why it's vital to test that the response object has the correct schema.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;List repos&lt;/strong&gt; request, we're making a request to the &lt;code&gt;/user/repos&lt;/code&gt; endpoint, which fetches all the repos of the logged-in user. Let's assume that we are integrating this API with a frontend that expects the body to be an array of repos, and each repo must have &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt;, and &lt;code&gt;owner&lt;/code&gt; fields. The &lt;code&gt;owner&lt;/code&gt; field must have &lt;code&gt;login&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; fields, and the &lt;code&gt;name&lt;/code&gt; field must match the regex: &lt;code&gt;/^\.?[a-z0-9-_\.]+$/i&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Requestly, you can use helpers from Chai to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fetched data has correct body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Check that body is an array&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;an&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;empty&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check required fields&lt;/span&gt;
    &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;include&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;// Match regex&lt;/span&gt;
    &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\.?[&lt;/span&gt;&lt;span class="sr"&gt;a-z0-9-_&lt;/span&gt;&lt;span class="se"&gt;\.]&lt;/span&gt;&lt;span class="sr"&gt;+$/i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test works, but it's very verbose. If you have a more complicated JSON schema, you'll have to spend a lot of time writing out each property one by one. This is also unmaintainable. If the schema changes, changing it in the test will feel like finding a needle in a haystack. You can use &lt;code&gt;.to.have.jsonSchema&lt;/code&gt; instead, which lets you pass a JSON schema.&lt;/p&gt;

&lt;p&gt;For example, you can write the above test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Match JSON schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonSchema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;owner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.?[a-zA-Z0-9-_&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.]+$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;private&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much easier to write, understand, and maintain. Of course, let's make sure the tests work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc121zzn5t3s7uz48caow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc121zzn5t3s7uz48caow.png" alt="The tests run successfully" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Complete Workflows
&lt;/h3&gt;

&lt;p&gt;Often, your API calls are not as simple as calling one single endpoint. You might need to call one endpoint, process the response, and use the data from the response to call another endpoint. With Requestly, you can use Environment Variables to store arbitrary data and use them in subsequent requests. We have already used one environment variable in the &lt;strong&gt;Login&lt;/strong&gt; endpoint. Let's now take a look at a more complete "workflow" example.&lt;/p&gt;

&lt;p&gt;Let's assume that we want to test the pipeline of creating, fetching, and deleting issues. We want to create an issue, fetch the same issue, and then delete it.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Create issue&lt;/strong&gt; endpoint, we're making a request to &lt;code&gt;{{base_url}}/repos/{{owner}}/{{repo}}/issues&lt;/code&gt; endpoint. The &lt;code&gt;{{...}}&lt;/code&gt; denotes an environment variable that Requestly will substitute. We're creating an issue with the following body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test issue from Requestly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This is a test issue created via API"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's write a test to ensure the issue is created successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Issue was created successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test issue from Requestly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;issue_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we're checking that the title of the issue matches what we sent in the body. The last line is where we set the &lt;code&gt;issue_id&lt;/code&gt; environment variable, which we'll use in our subsequent request.&lt;/p&gt;

&lt;p&gt;Open the &lt;strong&gt;Fetch issue&lt;/strong&gt; request. You'll notice we're using the &lt;code&gt;issue_id&lt;/code&gt; in the request URL: &lt;code&gt;{{base_url}}/repos/{{owner}}/{{repo}}/issues/{{issue_id}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's write a test to verify the correct issue is fetched. We'll verify that it has &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;body&lt;/code&gt; fields, and matches what we created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Issues has correct schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonSchema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fetches correct issue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test issue from Requestly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a test issue created via API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's close the issue to complete the workflow. In the &lt;strong&gt;Close issue&lt;/strong&gt; request, you'll see we're again using the &lt;code&gt;issue_no&lt;/code&gt; variable. This time, let's make sure &lt;code&gt;state&lt;/code&gt; is &lt;code&gt;closed&lt;/code&gt; in the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Issue is closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use this technique of chaining requests in many real-life scenarios. For example, if your API uses username and password to log in and returns a JWT, you can store it in an environment variable and use it in subsequent tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling and Debugging
&lt;/h3&gt;

&lt;p&gt;So far, we've been dealing with tests that work correctly. But in practice, tests may fail. And it's not always easy to debug. Suppose the login test is suddenly failing in CI. Is it because someone pushed buggy code, or is it due to something in the test itself, such as an expired token? Requestly offers debugging features to help you understand why your tests are failing.&lt;/p&gt;

&lt;p&gt;When a test fails, Requestly prints an error message next to the test. You can try this out by sending the &lt;strong&gt;Invalid token&lt;/strong&gt; request. This request attempts to log in with an invalid token, but expects the result to be 'ok'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yjmhqq9p28mcqf3p0qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yjmhqq9p28mcqf3p0qz.png" alt="Test fails" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use &lt;code&gt;console.log&lt;/code&gt; to log values. You can use &lt;code&gt;rq.request&lt;/code&gt; and &lt;code&gt;rq.response&lt;/code&gt; objects to access the request and response values to help you debug the actual cause of a failing test. For example, let's log the request headers and response status code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reuest is successful&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you press &lt;code&gt;Ctrl+Alt+I&lt;/code&gt;, the Devtools console will open. If you send the request, the logs will show up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhqzb3kekmxc25xbpuad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhqzb3kekmxc25xbpuad.png" alt="The developer console" width="528" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These logs can help you debug your test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mocking &amp;amp; Intercepting for API Testing
&lt;/h2&gt;

&lt;p&gt;We’ve seen how to handle real errors like bad tokens or missing endpoints. But what about failures that don’t happen on their own? For example, a flaky third-party API or an edge case response you can’t easily trigger in production. That’s where mocking comes in.&lt;/p&gt;

&lt;p&gt;Real apps often depend on third-party APIs, such as payment processors, weather data, and analytics. Testing these integrations is tricky because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can’t control when the third-party API fails.&lt;/li&gt;
&lt;li&gt;You can’t control the data it sends back.&lt;/li&gt;
&lt;li&gt;Sometimes, every test call costs real money.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;a href="https://docs.requestly.com/general/api-mocking/api-mocking" rel="noopener noreferrer"&gt;API mocking&lt;/a&gt; helps. With Requestly, you can intercept an outgoing request and return a fake response. That way you can simulate failures, edge cases, or alternate data without touching the real API.&lt;/p&gt;

&lt;p&gt;Our demo repo has a &lt;code&gt;/httpbin&lt;/code&gt; endpoint that calls &lt;code&gt;http://httpbin.org/get&lt;/code&gt;. Normally, it just passes through the response. But what if HttpBin goes down? We want to see if our app handles errors gracefully.&lt;/p&gt;

&lt;p&gt;To mock an API call, you need to use Requestly's &lt;a href="https://docs.requestly.com/general/http-interceptor/overview" rel="noopener noreferrer"&gt;HTTP interceptor&lt;/a&gt;, which turns Requestly into a proxy server to intercept HTTP calls from your browsers (Chrome, Firefox, etc.), terminal app, or even the whole system. Then you can use &lt;a href="https://docs.requestly.com/general/http-rules/overview" rel="noopener noreferrer"&gt;HTTP rules&lt;/a&gt; to modify the request, response, headers, introduce delays, redirect, and much more.&lt;/p&gt;

&lt;p&gt;As of writing this article, this feature is not available in a local workspace. So, you'll need either a team workspace or switch to the private workspace. In any case, you'll need an account. Click on the icon in the top-right corner, and sign up or sign in.&lt;/p&gt;

&lt;p&gt;Now in the workspace dropdown menu, you'll see that the &lt;strong&gt;Private workspace&lt;/strong&gt; option is enabled. Choose this option to switch to the private workspace.&lt;/p&gt;

&lt;p&gt;Go to the &lt;strong&gt;Network tab&lt;/strong&gt;, click on &lt;strong&gt;Connect apps&lt;/strong&gt;, switch to the &lt;strong&gt;Terminal processes&lt;/strong&gt; tab, click on &lt;strong&gt;Setup instructions&lt;/strong&gt;, and copy the command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5akwale22cv553neqatr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5akwale22cv553neqatr.png" alt="The terminal proxy setup instructions" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste the command in the terminal and run it. On successful execution, you should see the message &lt;strong&gt;Requestly interception enabled&lt;/strong&gt;. Now start the server with &lt;code&gt;npm start&lt;/code&gt;. Now, any request made from the API server will pass through Requestly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Running the command provided by Requestly sets up a few environment variables, one of them being &lt;code&gt;https_proxy&lt;/code&gt;, which sets up Requestly as a proxy server. The demo app uses Axios to make requests to HttpBin. Axios automatically picks up the &lt;code&gt;https_proxy&lt;/code&gt; environment variable, so in this case, we don't have to change any code. However, suppose you use a different library (such as node-fetch) that doesn't automatically pick up the &lt;code&gt;https_proxy&lt;/code&gt; environment variable. In that case, you'll need to modify the code to use the proxy server manually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In another terminal, invoke the &lt;code&gt;/httpbin&lt;/code&gt; endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl localhost:3000/httpbin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Requestly, you should see the HttpBin request show up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1w7f1xp5eq6oi3k0mp7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1w7f1xp5eq6oi3k0mp7t.png" alt="The HttpBin request" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: As of writing the article, requests made with Axios show up with the wrong URL due to a &lt;a href="https://github.com/requestly/requestly/issues/301" rel="noopener noreferrer"&gt;bug&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Right-click on it and select &lt;strong&gt;Modify response body&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This will open a window where you can define an HTTP rule that will modify the response body of the intercepted request.&lt;/p&gt;

&lt;p&gt;First, give the rule a name, for example: "HttpBin error". In the &lt;strong&gt;If request&lt;/strong&gt; field, select &lt;strong&gt;URL&lt;/strong&gt; and &lt;strong&gt;Contains&lt;/strong&gt; and write &lt;code&gt;httpbin.org&lt;/code&gt; in the input field. Select &lt;code&gt;500&lt;/code&gt; from the status codes dropdown, enter an empty body, and check &lt;strong&gt;Serve this response body without making a call to the server&lt;/strong&gt;. Then, save the rule.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3rvcxr937ga15twk5r0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3rvcxr937ga15twk5r0.png" alt="Modify response body details" width="800" height="708"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, anytime our API server makes a request to HttpBin, Requestly will return a 500 response without actually hitting HttpBin.&lt;/p&gt;

&lt;p&gt;Let's now write the test. Go to &lt;strong&gt;APIs&lt;/strong&gt; tab, and create a new HTTP request. Provide a name such as &lt;strong&gt;Get third party error&lt;/strong&gt;, and put &lt;code&gt;http://localhost:3000&lt;/code&gt; in the URL field, and write the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Should return error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can test whether my app correctly handles failures or not without having to wait for the third-party API to fail!&lt;/p&gt;

&lt;p&gt;With these tests in place - from happy paths to deliberate failures - we've covered the core scenarios of real-world API testing. Now it's your turn to use Requestly for testing your APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Effective API testing goes beyond checking if an endpoint returns data - it ensures reliability, catches edge cases, and streamlines development. By going local-first with Requestly, you can quickly test and mock APIs without juggling cloud accounts or heavy setups. Start small with a few tests, then layer on more complexity as your needs grow. The best way to see the value is to &lt;a href="https://requestly.com/downloads/" rel="noopener noreferrer"&gt;try it in your own workflow&lt;/a&gt; and feel how much smoother debugging and iteration can become.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>apitesting</category>
    </item>
    <item>
      <title>(Almost) Everything You Need To Know About Pointers in C</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Thu, 09 Sep 2021 07:09:05 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/almost-everything-you-need-to-know-about-pointers-in-c-53na</link>
      <guid>https://dev.to/heraldofsolace/almost-everything-you-need-to-know-about-pointers-in-c-53na</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;When I was first starting out with C, pointers were something that confused me the most. I was scared of pointers and could never understand how to use them. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No, I didn't say that! In fact, pointers have always been intuitive to me. But most of the students starting to learn C are put off by the idea of pointers. It is one of those areas of C which are not explained properly to students. resulting in many misconceptions about them.&lt;/p&gt;

&lt;p&gt;In this huge post, I have compiled almost everything that is fundamental to pointers. Of course, it is a huge topic, and it's not possible to cover the entirety of it in one post, but once you know these fundamentals, you'll be able to use them more efficiently, and hopefully will be able to tackle pointers in a program.&lt;/p&gt;

&lt;p&gt;Let's start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beginning With Pointer Sorcery
&lt;/h2&gt;

&lt;p&gt;One fine afternoon, you are lying on your couch and thinking about the year 2038 problem and the end of the universe, and suddenly your friend calls you and asks "Hey, I want to come over and contemplate our existence, but I do not know where your house is!"&lt;/p&gt;

&lt;p&gt;You say, "No problem buddy. I'll give you a copy of my home."&lt;/p&gt;

&lt;p&gt;Of course, you'd never say that. Instead, you will give him your address so that he can come over. Now, you could make him a copy of your home if you're generous enough, but it takes time and defeats the purpose of your friend coming over. He wants to come to your house, not a copy.&lt;/p&gt;

&lt;p&gt;Now think in terms of programming. At the time when C was created, memory was scarce, and being efficient was not only needed but vital. For this reason, you'd have to be really careful while dealing with memory. You'd really not like to make unnecessary copies of something.&lt;/p&gt;

&lt;p&gt;Another case you can consider is of having "side effect" of a function. Consider this simple program.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
void f(int a) { a = 10; }
int main() {
    int a = 5;
    printf("%d\n", a);
    f(a);
    printf("%d\n", a);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;which just prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Even though you are calling the function &lt;code&gt;f&lt;/code&gt; with the variable &lt;code&gt;a&lt;/code&gt; as a parameter, and &lt;code&gt;f&lt;/code&gt; is changing the value of &lt;code&gt;a&lt;/code&gt;. the change doesn't show up in the original value of &lt;code&gt;a&lt;/code&gt;, because when you are calling the function &lt;code&gt;f&lt;/code&gt;, you are passing a copy of &lt;code&gt;a&lt;/code&gt;, not &lt;code&gt;a&lt;/code&gt; itself. In other terms, you are giving your friend a copy of your house.&lt;/p&gt;

&lt;p&gt;This is desired in most cases. You don't really want your functions to accidentally change any variable where it's not supposed to. But sometimes, you actually want the function to change a variable. You have already seen such a function that can change the actual parameter.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scanf("%d", &amp;amp;n);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;How does &lt;code&gt;scanf&lt;/code&gt; change the value of &lt;code&gt;n&lt;/code&gt;? The answer is through pointers.&lt;/p&gt;

&lt;p&gt;Also, take a look at this classic example of swap- &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void swap(int a, int b) {
    int t = a;
    a = b;
    b = t;
}
int main() {
    int a = 5, b =10;
    swap(a, b);
    printf("a = %d, b = %d\n", a, b);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It works, except it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;swap&lt;/code&gt; does swap the variables, but since you are making a copy of &lt;code&gt;a&lt;/code&gt;, and &lt;code&gt;b&lt;/code&gt;, the change doesn't show up outside the function. But we want the function to be able to change the actual variables. So we need to have some kind of way to pass the actual &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; . But in C, there is no way you can pass "actual" variables. (which is not the case in C++).&lt;/p&gt;

&lt;p&gt;One way you might end up doing is to make &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; global &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = 5, b = 10;
void swap() {
    int t = a;
    a = b;
    b = t;
}
int main() {
    swap();
    printf("a = %d, b = %d\n", a, b);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And it now works, because &lt;code&gt;swap&lt;/code&gt; now can access &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;, but having global variables is a real &lt;a href="https://wiki.c2.com/?GlobalVariablesAreBad" rel="noopener noreferrer"&gt;bad idea&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The way? Give &lt;code&gt;swap&lt;/code&gt; the addresses of &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. If it has addresses of &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; , it can change them directly.&lt;/p&gt;

&lt;p&gt;Pointers are nothing but variables that hold the address of another variable.&lt;/p&gt;

&lt;p&gt;Now, where does this address come from? We know how &lt;a href="https://computer.howstuffworks.com/bytes.htm" rel="noopener noreferrer"&gt;bits and bytes work&lt;/a&gt;. The RAM of the computer can be thought of as a mess, a really long one, with lots of rooms one after another, and each byte is a room. How does the computer know which room to put data in? It gives a number to each room, and that number is the address.&lt;/p&gt;

&lt;p&gt;When I write&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char a;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I tell the compiler "Buddy, reserve one room in the mess, and call it &lt;code&gt;a&lt;/code&gt;" . Why one room? Because the size of &lt;code&gt;char&lt;/code&gt; is 1 byte. (Note that C's definition of a byte is basically the &lt;code&gt;sizeof char&lt;/code&gt; , which in some rare cases might not be actually 1 byte in the machine, however, it is always 1 byte in C)&lt;/p&gt;

&lt;p&gt;If I write&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int b;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I tell the compiler to reserve the number of rooms necessary for &lt;code&gt;int&lt;/code&gt; and call it &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side rant: People coming from Turbo C, and being told size of &lt;code&gt;int&lt;/code&gt; is 2 bytes, it's not necessarily so, and probably not so in any modern computer. The C standard guarantees at least 2 bytes for &lt;code&gt;int&lt;/code&gt; and on my machine &lt;code&gt;sizeof(int)&lt;/code&gt; is 4, so we will stick to that for the rest of this post.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that our &lt;code&gt;b&lt;/code&gt; has 4 rooms, it will stay in the rooms starting from the first one. So that when we say "address of b", we actually mean "address of the starting or ending byte of b". (See &lt;a href="https://chortle.ccsu.edu/AssemblyTutorial/Chapter-15/ass15_3.html" rel="noopener noreferrer"&gt;big endian and little endian&lt;/a&gt;. For this tutorial, let's assume it's the ending byte because it is so on my machine)&lt;/p&gt;

&lt;p&gt;In order to get the address of &lt;code&gt;b&lt;/code&gt; and store it, we need to use a pointer variable. Just like any other variable, a pointer also has a type, defined by the type of the thing it points to. The syntax is &lt;code&gt;type_of_the_thing_it_points_to *name&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char *pa;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that the asterisk need not be adjoined to the variable name. Any of these is valid - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char* pa;
char *pa;
char * pa;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will prefer the 2nd syntax. We will see in a short while why.&lt;/p&gt;

&lt;p&gt;Let's first see how to assign a value to a pointer. In order to make a pointer point to a variable, we have to store the address of the variable in the pointer. The syntax for getting the address of a variable is &lt;code&gt;&amp;amp;variable_name&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char a;
char *pa = &amp;amp;a; // pa now contains the address of a

printf("%p", pa); // %p is the format specifier to print a pointer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you run this program, you will see something like &lt;code&gt;0x7ffc2fc4ff27&lt;/code&gt;. That is the value of the pointer, which is the address of the variable &lt;code&gt;a&lt;/code&gt; (this is in hexadecimal). This value is not fixed. If you run the program again, the value will likely change, because &lt;code&gt;a&lt;/code&gt; will be stored somewhere else.&lt;/p&gt;

&lt;p&gt;One thing you might have noticed. Although we are declaring it as &lt;code&gt;*pa&lt;/code&gt;, the * is not used when printing the pointer. In fact, * is not a part of the name of the pointer. The name of the pointer is just &lt;code&gt;pa&lt;/code&gt;. The * is instead used to get the value of whatever thing the pointer is pointing to (known as dereferencing).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char a = 'a';
char *pa = &amp;amp;a;

printf("%p\n", pa); // prints the value of pa
printf("%c", *pa); // prints the value of a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So, quickly revise - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;pa&lt;/code&gt; is the value of the pointer, which is the address of &lt;code&gt;a&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*pa&lt;/code&gt; is the value of the thing &lt;code&gt;pa&lt;/code&gt; is pointing to, in this case &lt;code&gt;a&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One more time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;pointer_name&lt;/code&gt; is the value of the pointer itself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*pointer_name&lt;/code&gt; is the value of the thing the pointer points to.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, this should be clear.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char a = 'a', b = 'b';
char * pa = &amp;amp;a; // pa points to a
*pa = 'c'; // change the value of whatever pa is pointing to, in this case a
printf("%c", a); //prints c
pa = &amp;amp;b; // change the pointer itself. pa now points to b
*pa = 'd' // change the value of whatever pa is pointing to, in this case b
printf("%c", a); //prints c, because a is unchanged as pa is no more pointing to a
printf("%c", b); //prints d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can rewrite the swap function as follows - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And call it with the addresses &lt;code&gt;swap(&amp;amp;a, &amp;amp;b)&lt;/code&gt;. This works and the change shows up outside the function too. Because once you have the address of a variable, you know where it lives in memory so you can freely change it.&lt;/p&gt;

&lt;p&gt;You might have a valid question. Since all pointers are just addresses, which are basically numbers, why is the type of the thing it points to necessary? Why do we distinguish between &lt;code&gt;char*&lt;/code&gt; and &lt;code&gt;int*&lt;/code&gt; although both of them are just some numbers?&lt;/p&gt;

&lt;p&gt;The answer is clear. When you dereference a pointer, the compiler needs to know what data type is the object. Remember that address of a variable is just the address of the ending byte of the variable. In order to read the variable, the compiler needs to know its type so that it knows how many bytes to read.&lt;/p&gt;

&lt;p&gt;Consider this program&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int main() {
    int a = 1101214537;
    char *pa = &amp;amp;a;
    printf("%c", *pa);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It prints (ignore the compiler warning)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What happened here?&lt;/p&gt;

&lt;p&gt;If you represent &lt;code&gt;1101214537&lt;/code&gt; in binary it is &lt;code&gt;01000001 10100011 00110011 01001001&lt;/code&gt; . So &lt;code&gt;&amp;amp;a&lt;/code&gt; which is the address of &lt;code&gt;a&lt;/code&gt; points to the byte in memory that contains the last byte of the number, which is &lt;code&gt;01001001&lt;/code&gt;. When I dereference &lt;code&gt;pa&lt;/code&gt;, the compiler sees that it points to &lt;code&gt;char&lt;/code&gt; so it reads only one byte at that address, giving the value &lt;code&gt;01001001&lt;/code&gt; which &lt;code&gt;73&lt;/code&gt;, the ASCII for &lt;code&gt;I&lt;/code&gt;. This is why the type is absolutely and you should not mix and match types unless you are absolutely sure of what you are doing. (We'll see a few examples)&lt;/p&gt;

&lt;p&gt;Remember we told that we will prefer &lt;code&gt;int *pa&lt;/code&gt; rather than &lt;code&gt;int* pa&lt;/code&gt; although they are the same? The reason is to safeguard against the following common misconception. Can you find the difference?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a, b; // a and b both are int
int* pa, pb; // whoopsie! pb is not a pointer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you are a beginner, you will assume that since &lt;code&gt;int a, b&lt;/code&gt; makes both &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; as &lt;code&gt;int&lt;/code&gt; , then &lt;code&gt;int* pa, pb&lt;/code&gt; will make both &lt;code&gt;pa&lt;/code&gt; and &lt;code&gt;pb&lt;/code&gt; as &lt;code&gt;int*&lt;/code&gt;. But it doesn't. The reason is &lt;code&gt;*&lt;/code&gt; "binds" to the variable name, not the type name. If instead, you'd have written&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int *pa, pb;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;you'd rightly conclude &lt;code&gt;pa&lt;/code&gt; is a pointer to int, and &lt;code&gt;pb&lt;/code&gt; is just &lt;code&gt;int&lt;/code&gt;. Hence I prefer to write the &lt;code&gt;*&lt;/code&gt; with the variable name, however, there are compelling reasons for the other style as well, and if you are careful enough, you can use the other style as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  NULL and void pointer
&lt;/h3&gt;

&lt;p&gt;These two are a special types of pointers in C. The Null pointer is used to denote that the pointer doesn't point to a valid memory location.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int *pa = NULL;
char *pb = NULL;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We use Null pointer in various ways, for example, to denote failure, or mark the end of a list of unknown size, etc. Dereferencing a Null pointer is undefined behavior and your program will likely crash.&lt;/p&gt;

&lt;p&gt;Note that the Null pointer is not the same as pointer to memory address 0, although it's very likely to be so. There are exceptions, for example in small embedded devices where address 0 might be a valid location.&lt;/p&gt;

&lt;p&gt;Void pointer is one more interesting pointer in C. Basically void pointer "throws away" the type of a pointer. It is a general-purpose pointer that can hold any type of pointer and can be cast to any type of pointer. The following are all valid - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a;
char b;
float c;
void *p = &amp;amp;a;
p = &amp;amp;b;
p = &amp;amp;c;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But you can't dereference a &lt;code&gt;void *&lt;/code&gt; because it doesn't have a type. Trying to dereference a &lt;code&gt;void *&lt;/code&gt; will give you an error. However, you can cast it to anything you want and then dereference it, although it's not a very good idea and it violates the type aliasing rules.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = 65;
void *p = &amp;amp;a;
char *c = (char *) p;
printf("%c\n", *c);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here we're removing the type of &lt;code&gt;&amp;amp;a&lt;/code&gt; through &lt;code&gt;p&lt;/code&gt; and casting it to a &lt;code&gt;char *&lt;/code&gt;. Essentially &lt;code&gt;a&lt;/code&gt; is getting read as a &lt;code&gt;char&lt;/code&gt; and this prints &lt;code&gt;A&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Be careful during casting. You should use void pointers only if you are absolutely sure of what you're doing.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = 65;
void *p = &amp;amp;a;
int (*f)(int) = (int (*)(int)) p; // cast as a function pointer (discussed later)
f(2); // Segmentation fault
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Sometimes you'll see &lt;code&gt;char *&lt;/code&gt; used as a generic pointer as well. This is because &lt;code&gt;void *&lt;/code&gt; was not present in old versions of C, and some practice remains, or maybe the code needs to do pointer arithmetic on that pointer.&lt;/p&gt;

&lt;p&gt;Generally &lt;code&gt;void *&lt;/code&gt;&lt;code&gt;i&lt;/code&gt;s used in places where you expect to work with pointers to multiple types. As an example, consider the famous &lt;code&gt;memcpy&lt;/code&gt; function which copies a block of memory. Here is the signature of &lt;code&gt;memcpy&lt;/code&gt; - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void * memcpy ( void * destination, const void * source, size_t num );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you see, it accepts &lt;code&gt;void *&lt;/code&gt;, which means it works with any type of pointers. As for an example (copied from &lt;a href="https://www.cplusplus.com/reference/cstring/memcpy/" rel="noopener noreferrer"&gt;cplusplus&lt;/a&gt;) - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* memcpy example */
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;string.h&amp;gt;

struct {
  char name[40];
  int age;
} person, person_copy;

int main ()
{
  char myname[] = "Pierre de Fermat";

  /* using memcpy to copy string: */
  memcpy ( person.name, myname, strlen(myname)+1 );
  person.age = 46;

  /* using memcpy to copy structure: */
  memcpy ( &amp;amp;person_copy, &amp;amp;person, sizeof(person) );

  printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In line 15, we invoked &lt;code&gt;memcpy&lt;/code&gt; with &lt;code&gt;char *&lt;/code&gt; and in line 19, we invoked &lt;code&gt;memcpy&lt;/code&gt; with a pointer to structure, and they both work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pointer arithmetic
&lt;/h3&gt;

&lt;p&gt;Since pointers are just like other variables, you'd expect that we should be able to do arithmetic with them. We can, but there's a catch. First of all, we are only allowed these 2 operations - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Addition (and hence subtraction) of an integer constant to a pointer.&lt;/li&gt;
&lt;li&gt;Subtraction of two pointers of the same type.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see them one by one&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a;
int *pa = &amp;amp;a;

printf("pa = %p\n", pa);
printf("pa + 1 = %p\n", pa + 1);
printf("pa - 1 = %p\n", pa - 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pa = 0x7ffdd7eeee64
pa + 1 = 0x7ffdd7eeee68
pa - 1 = 0x7ffdd7eeee60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Strangely, it seems &lt;code&gt;pa+1&lt;/code&gt; increments the pointer by 4, and not by 1. The reason lies in the datatype of the thing it points to, in this case, &lt;code&gt;int&lt;/code&gt;. Remember that a pointer must always point to something. When you increment the pointer by 1, it points to the next thing.&lt;/p&gt;

&lt;p&gt;In this case, &lt;code&gt;pa&lt;/code&gt; points to an &lt;code&gt;int&lt;/code&gt;. Where is the next &lt;code&gt;int&lt;/code&gt;? After 4 bytes of course, because the size of int is 4 bytes.&lt;/p&gt;

&lt;p&gt;Similarly &lt;code&gt;pa-1&lt;/code&gt; points to the previous &lt;code&gt;int&lt;/code&gt; which lies 4 bytes before.&lt;/p&gt;

&lt;p&gt;By the same logic, &lt;code&gt;pa+2&lt;/code&gt; points to the &lt;code&gt;int&lt;/code&gt; 2 places after &lt;code&gt;a&lt;/code&gt; that is &lt;code&gt;4 * 2 = 8&lt;/code&gt; bytes after &lt;code&gt;a&lt;/code&gt;, and &lt;code&gt;pa+n&lt;/code&gt; points to the integer &lt;code&gt;n&lt;/code&gt; places after &lt;code&gt;a&lt;/code&gt; which is &lt;code&gt;4n&lt;/code&gt; bytes after &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An observant reader might have noticed that things are looking almost like an array, and he/she is not wrong completely. In a few minutes, we shall explore the idea of array using pointers. Before let's talk about the subtraction of pointers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a;
int *pa = &amp;amp;a;
int *pb = pa + 2;
printf("pa = %p\n", pa);
printf("pb = %p\n", pb);
printf("pb - pa = %ld\n", pb - pa);
printf("pa - pb = %ld\n", pa - pb);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pa = 0x7ffec09d685c
pb = 0x7ffec09d6864
pb - pa = 2
pa - pb = -2  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Similar to the previous case, although the difference between &lt;code&gt;pa&lt;/code&gt; and &lt;code&gt;pb&lt;/code&gt; is of 8 bytes as numbers, as pointers the difference is 2. The negative sign of &lt;code&gt;pa-pb&lt;/code&gt; implies that &lt;code&gt;pb&lt;/code&gt; points after &lt;code&gt;pa&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To quickly summarise - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If I have &lt;code&gt;some_data_type *p&lt;/code&gt;, then &lt;code&gt;pa + n&lt;/code&gt; increments the pointer by &lt;code&gt;n * sizeof(some_data_type)&lt;/code&gt; bytes.&lt;/li&gt;
&lt;li&gt;If I have &lt;code&gt;some_data_type *p, *q&lt;/code&gt; then &lt;code&gt;p - q&lt;/code&gt; is equal to the difference in bytes divided by &lt;code&gt;sizeof(some_data_type)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's consider what happens if we mix indirection and prefix or postfix increment/decrement operators. Can you guess what each of these does? I have omitted the data types so that you can't guess ;-). Assume &lt;code&gt;p&lt;/code&gt; points to &lt;code&gt;int&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = *p++;
x = ++*p;
x = *++p;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In order to answer, you have to remember the precedence - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Postfix &lt;code&gt;++&lt;/code&gt; and &lt;code&gt;--&lt;/code&gt; have higher precedence than &lt;code&gt;*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Prefix &lt;code&gt;++&lt;/code&gt; and &lt;code&gt;--&lt;/code&gt; have the same precedence as &lt;code&gt;*&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since the &lt;code&gt;*&lt;/code&gt; operator is itself a prefix, you'll never have a problem with prefix increment or decrement. You can tell just by the order of the operator. For the postfix operator, remember that postfix works first, then indirection.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;*p++&lt;/code&gt; is same as &lt;code&gt;*(p++)&lt;/code&gt;. So, the value of &lt;code&gt;p&lt;/code&gt; will be used in the expression, then &lt;code&gt;p&lt;/code&gt; will be incremented. So &lt;code&gt;x&lt;/code&gt; gets the value of &lt;code&gt;*p&lt;/code&gt; and &lt;code&gt;p&lt;/code&gt; becomes &lt;code&gt;p+1&lt;/code&gt;, so that the type of &lt;code&gt;x&lt;/code&gt; ought to be &lt;code&gt;int&lt;/code&gt; too.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = 5;
int *p = &amp;amp;a;
int x;
printf("Before:\n");
printf("a = %d\n", a);
printf("p = %p\n", p);
x = *p++;
printf("After\n");
printf("a = %d\n", a);
printf("p = %p\n", p);
printf("x = %d\n", x);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
a = 5
p = 0x7ffe82ae9eb0
After
a = 5
p = 0x7ffe82ae9eb4
x = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;++*p&lt;/code&gt; will probably not arise confusion. This is the same as &lt;code&gt;++ (*p)&lt;/code&gt;. So, first &lt;code&gt;p&lt;/code&gt; is dereferenced, and then &lt;code&gt;++&lt;/code&gt; is applied. So whatever &lt;code&gt;p&lt;/code&gt; was pointing to gets incremented by 1 and then it is assigned to &lt;code&gt;x&lt;/code&gt;, and &lt;code&gt;p&lt;/code&gt; is unchanged. So the type of &lt;code&gt;x&lt;/code&gt; is again &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = 5;
int *p = &amp;amp;a;
int x;
printf("Before:\n");
printf("a = %d\n", a);
printf("p = %p\n", p);
x = ++*p;
printf("After\n");
printf("a = %d\n", a);
printf("p = %p\n", p);
printf("x = %d\n", x);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
a = 5
p = 0x7fff1484e210
After
a = 6
p = 0x7fff1484e210
x = 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And finally &lt;code&gt;* ++p&lt;/code&gt; is same as &lt;code&gt;* (++p)&lt;/code&gt;. So, first &lt;code&gt;p&lt;/code&gt; gets incremented by 1, and then it is dereferenced. So &lt;code&gt;x&lt;/code&gt; gets the value of whatever is this incremented pointer pointing to.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = 5;
int *p = &amp;amp;a;
int x;
printf("Before:\n");
printf("a = %d\n", a);
printf("p = %p\n", p);
x = *++p;
printf("After\n");
printf("a = %d\n", a);
printf("p = %p\n", p);
printf("x = %d\n", x);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:
a = 5
p = 0x7ffd4bad9c90
After
a = 5
p = 0x7ffd4bad9c94
x = 32765
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can also compare pointers using relational operators like &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt; etc. but there's a catch. You can only compare two pointers using &lt;code&gt;&amp;lt;=, &amp;lt;, &amp;gt;=, &amp;gt;&lt;/code&gt; if they both are pointers of the same type, and of the same array or same aggregate object. Otherwise, it is undefined behavior.&lt;/p&gt;

&lt;p&gt;Quoting C11 - &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When two pointers are compared, the result depends on the relative locations in the address space of the objects pointed to. If two pointers to object types both point to the same object, or both point one past the last element of the same array object, they compare equal. If the objects pointed to are members of the same aggregate object, pointers to structure members declared later compare greater than pointers to members declared earlier in the structure, and pointers to array elements with larger subscript values compare greater than pointers to elements of the same array with lower subscript values. All pointers to members of the same union object compare equal. If the expression &lt;code&gt;P&lt;/code&gt; points to an element of an array object and the expression &lt;code&gt;Q&lt;/code&gt; points to the last element of the same array object, the pointer expression &lt;code&gt;Q+1&lt;/code&gt; compares greater than &lt;code&gt;P&lt;/code&gt;. *&lt;strong&gt;&lt;em&gt;In all other cases, the behavior is undefined.&lt;/em&gt;&lt;/strong&gt;*&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take a look - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;typedef struct some_struct {
    int p;
    int q;
} some_struct;

some_struct a = {1, 2}; 
int *p = &amp;amp;a.p;
int *q = &amp;amp;a.q;
if(p &amp;gt; q) puts("Hi\n");
else puts("Bye\n");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints &lt;code&gt;Bye&lt;/code&gt;. Well first of all this comparison is valid since &lt;code&gt;p&lt;/code&gt; and &lt;code&gt;q&lt;/code&gt; are both pointers to &lt;code&gt;int&lt;/code&gt; and also they both point to elements of the same &lt;code&gt;struct&lt;/code&gt;. Since &lt;code&gt;q&lt;/code&gt; was declared later in &lt;code&gt;some_struct&lt;/code&gt; , &lt;code&gt;q&lt;/code&gt; compares greater to &lt;code&gt;p&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For equality, the restriction is a bit slack. You can compare any two pointers as long as they have the same type, or one of them is a null pointer or void pointer. And they compare equal if they point to the same object, or if both are null (doesn't matter if types don't match), or if both are pointing to members of the same union.&lt;/p&gt;

&lt;p&gt;Let's demonstrate the last point.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;typedef union some_union {
    int p;
    int q;
} some_union;

some_union a;
int *p = &amp;amp;a.p;
int *q = &amp;amp;a.q;

if(p == q) {
    puts("Equal\n");
} else {
    puts("Not equal\n");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints &lt;code&gt;Equal&lt;/code&gt; although &lt;code&gt;p&lt;/code&gt; and &lt;code&gt;q&lt;/code&gt; point to different things, they are within the same union.&lt;/p&gt;

&lt;p&gt;Since pointers are just numbers, can you put any integer in them? The answer is yes, but be careful of what you put. In fact, be careful when you dereference it. If you try to dereference an invalid address, your program will likely segfault and crash.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int *x = (int *) 1;
printf("%d\n", *x);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This instantly segfaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Admitted to Hogwarts School Of Pointer Magic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pointers and Arrays
&lt;/h3&gt;

&lt;p&gt;Let's now move to some advanced sorcery - array and pointers.&lt;/p&gt;

&lt;p&gt;We know that an array stores its elements contiguously in memory. Which means the elements are stored in order one after another. So if we have &lt;code&gt;int arr[10]&lt;/code&gt;, we know &lt;code&gt;arr[1]&lt;/code&gt; lies right after &lt;code&gt;arr[0]&lt;/code&gt;, &lt;code&gt;arr[2]&lt;/code&gt; lies right after &lt;code&gt;arr[1]&lt;/code&gt; and so on. So if I have a pointer to &lt;code&gt;arr[0]&lt;/code&gt; and I increment it by 1, it should point to &lt;code&gt;arr[1]&lt;/code&gt;. If I increment it by 1 again, it should point to &lt;code&gt;arr[2]&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;In fact, there are so many similarities between arrays and pointers, that we can talk about the equivalence of arrays and pointers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Word of caution! This does not mean arrays and pointers are the same and you can use one in place of another. This misconception is quite common and ends up being harmful. Arrays and pointers are very different things, but pointer arithmetic and array indexing are equivalent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For starters, the name of an array "decays" into a pointer to the first element. What do I mean by that? Consider this code - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int arr[10];
int *pa = &amp;amp;(arr[0]); // pointer to the first element
int *pb = arr; // What

printf("pa = %p\n", pa);
printf("pb = %p\n", pb);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pa = 0x7ffef7706bb0
pb = 0x7ffef7706bb0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But aren't we mixing up datatypes in the case of &lt;code&gt;pb&lt;/code&gt;? &lt;code&gt;pb&lt;/code&gt; is a pointer to &lt;code&gt;int&lt;/code&gt; and &lt;code&gt;arr&lt;/code&gt; is an array of int!&lt;/p&gt;

&lt;p&gt;Turns out that &lt;code&gt;arr&lt;/code&gt; is &lt;em&gt;converted to&lt;/em&gt; a pointer to the first element. So that &lt;code&gt;arr&lt;/code&gt; and &lt;code&gt;&amp;amp;(arr[0])&lt;/code&gt; is equivalent.&lt;/p&gt;

&lt;p&gt;Quick note: indexing operator &lt;code&gt;[]&lt;/code&gt; has higher precendence than &lt;code&gt;*&lt;/code&gt; so that &lt;code&gt;* arr[0]&lt;/code&gt; is same as &lt;code&gt;* (arr[0])&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's do even more funny stuff - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int arr[3] = {1, 2, 3};
int *pa = arr;


printf("arr[1] = %d\n", arr[1]); // 2nd element using array indexing
printf("*(pa + 1) = %d\n", *(pa + 1)); // 2nd element using pointer arithmetic
printf("pa[1] = %d\n", pa[1]); // What
printf("*(arr + 1) = %d\n", *(arr + 1)); // Whatt
printf("1[arr] = %d\n", 1[arr]); // Whattt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The first &lt;code&gt;printf&lt;/code&gt; is ok. &lt;code&gt;arr[1]&lt;/code&gt; means the 2nd element of &lt;code&gt;arr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We just reasoned about the 2nd line. &lt;code&gt;pa&lt;/code&gt; points to the first element of &lt;code&gt;arr&lt;/code&gt;. So &lt;code&gt;pa+1&lt;/code&gt; will point to the next &lt;code&gt;int&lt;/code&gt; in memory, which is &lt;code&gt;arr[1]&lt;/code&gt; because array elements are stored contiguously.&lt;/p&gt;

&lt;p&gt;But in the 3rd and 4th lines, aren't we mixing up array and pointer syntax? Well, turns out that &lt;code&gt;arr[i]&lt;/code&gt; is just the same as &lt;code&gt;*(arr + i)&lt;/code&gt; and this is (almost) what happens internally when you write &lt;code&gt;arr[i]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similarly &lt;code&gt;*(pa + i)&lt;/code&gt; is the same as &lt;code&gt;pa[i]&lt;/code&gt;. Pointer arithmetic works both on arrays and pointers. Similarly, array indexing works on both pointers and arrays.&lt;/p&gt;

&lt;p&gt;And for the last part, &lt;code&gt;arr[1]&lt;/code&gt; is the same as &lt;code&gt;*(arr + 1)&lt;/code&gt; which is the same as &lt;code&gt;*(1 + arr)&lt;/code&gt; which should be the same as &lt;code&gt;1[arr]&lt;/code&gt;. This is one of those weird quirks of C.&lt;/p&gt;

&lt;p&gt;Does this mean you can mix and match pointers and arrays? The answer is a big fat no. The reason is although &lt;code&gt;arr[i]&lt;/code&gt; and &lt;code&gt;pa[i]&lt;/code&gt; give you the same result, i. e. the 2nd element of &lt;code&gt;arr&lt;/code&gt;, the way they reach there is quite different.&lt;/p&gt;

&lt;p&gt;Consider the code&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include&amp;lt;stdio.h&amp;gt;
int main() {
    int arr[3] = {1, 2, 3};
    int *pa = arr;
    int a = arr[1];
    int b = pa[1];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's look at the assembly code generated by the compiler. I used &lt;a href="//__GHOST_URL__/p/7a2aaf27-dbd2-4822-8dd4-3bbb6da203ac/godbolt.org"&gt;Compiler Explorer&lt;/a&gt;. Don't worry if you can't read assembly. We'll go together.&lt;/p&gt;

&lt;p&gt;We are interested in lines 5 and 6. Here's the related assembly for &lt;code&gt;int arr[3] = {1, 2, 3}&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov     DWORD PTR [rbp-28], 1
mov     DWORD PTR [rbp-24], 2
mov     DWORD PTR [rbp-20], 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In case you are seeing assembly for the first time, &lt;code&gt;rbp&lt;/code&gt; is the base pointer register that holds the memory address of the base of the current stack frame. Don't worry about what that means. For now think of &lt;code&gt;rbp&lt;/code&gt; as a pointer variable, which points to some location in memory.&lt;/p&gt;

&lt;p&gt;Here the contents of &lt;code&gt;arr&lt;/code&gt; is being put in memory. For example, consider the first line. The &lt;code&gt;mov&lt;/code&gt; instruction puts the value 1 somewhere in memory. The &lt;code&gt;DWORD PTR&lt;/code&gt; tells that it is of size 32 bit or 4 bytes as it is an &lt;code&gt;int&lt;/code&gt;. The syntax &lt;code&gt;[rbp - 28]&lt;/code&gt; means the content of the memory location at the address &lt;code&gt;rbp-28&lt;/code&gt;. Remember that &lt;code&gt;rbp&lt;/code&gt; is like a pointer. So it is the same as doing &lt;code&gt;* (rbp - 28)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Putting everything together, we see that the first line puts the value 1 in the memory address pointed by &lt;code&gt;rbp-28&lt;/code&gt;. The next value should be stored right after it, i. e. after 4 bytes. Which should be pointed by &lt;code&gt;rbp-24&lt;/code&gt; and indeed that is where 2 is stored. And finally, 3 is stored in the memory address pointed by &lt;code&gt;rbp-20&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, we see that the address of the first element is &lt;code&gt;rbp-28&lt;/code&gt;. So we'd expect this should be reflected in the line &lt;code&gt;int *pa = arr;&lt;/code&gt;. And indeed it is - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lea     rax, [rbp-28]
mov     QWORD PTR [rbp-8], rax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;lea&lt;/code&gt; means &lt;code&gt;load effective address&lt;/code&gt; which calculates &lt;code&gt;rbp-28&lt;/code&gt; and stores the &lt;strong&gt;address&lt;/strong&gt; in &lt;code&gt;rax&lt;/code&gt; rather than fetching the content of the memory address &amp;nbsp;&lt;code&gt;rbp-28&lt;/code&gt; and storing the &lt;em&gt;content&lt;/em&gt;. In other words, it just copies the address of the first element in &lt;code&gt;rax&lt;/code&gt; register and then in the memory location &lt;code&gt;rbp-8&lt;/code&gt; which is our &lt;code&gt;pa&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let's look at &lt;code&gt;int a = arr[1]&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov     eax, DWORD PTR [rbp-24]
mov     DWORD PTR [rbp-12], eax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So here first the content of &lt;code&gt;rbp-24&lt;/code&gt; is loaded into &lt;code&gt;eax&lt;/code&gt; and then stored in &lt;code&gt;rbp-12&lt;/code&gt; which is our &lt;code&gt;a&lt;/code&gt;. The interesting thing to notice is that the compiler knows the first element of &lt;code&gt;arr&lt;/code&gt; is at &lt;code&gt;rbp-28&lt;/code&gt; so when you write &lt;code&gt;arr[1]&lt;/code&gt; it directly offsets the base address by &lt;code&gt;and gets&lt;/code&gt;rbp-24`. This happens in compile time.&lt;/p&gt;

&lt;p&gt;Now let's look at &lt;code&gt;int b = pa[1];&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov     rax, QWORD PTR [rbp-8]
mov     eax, DWORD PTR [rax+4]
mov     DWORD PTR [rbp-16], eax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here we see first the value stored at &lt;code&gt;rbp-8&lt;/code&gt; is moved to &lt;code&gt;rax&lt;/code&gt;. Remember this was our &lt;code&gt;pa&lt;/code&gt; variable? So first the value stored at &lt;code&gt;pa&lt;/code&gt; is read. Then it is offset by 1, so we get &lt;code&gt;rax + 4&lt;/code&gt; and we read the value at &lt;code&gt;rax+4&lt;/code&gt; and store it to &lt;code&gt;eax&lt;/code&gt;. Finally, we store the value from &lt;code&gt;eax&lt;/code&gt; to &lt;code&gt;rbp-16&lt;/code&gt; which is the &lt;code&gt;b&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;The noticeable difference is that it takes one extra instruction in case of pointer. Because array address is fixed, when you write &lt;code&gt;arr&lt;/code&gt;, the compiler knows what you're talking about. But a pointer value can be changed. So when you write &lt;code&gt;pa&lt;/code&gt;, the value of &lt;code&gt;pa&lt;/code&gt; needs to be read first and then it can be used.&lt;/p&gt;

&lt;p&gt;Now suppose something like this. You have two files. One contains a global array like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int arr[3] = { 1, 2, 3 };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And in another file, you get carried away by the equivalence of array and pointer and write&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extern int *arr;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In other words, you have declared &lt;code&gt;arr&lt;/code&gt; as a pointer but defined as an array. What will happen if you write &lt;code&gt;int a = arr[1]&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The answer is - something catastrophic. Let's see why.&lt;/p&gt;

&lt;p&gt;Let's assume the array elements are stored just like before - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov     DWORD PTR [rbp-28], 1
mov     DWORD PTR [rbp-24], 2
mov     DWORD PTR [rbp-20], 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But in our second file, we are doing &lt;code&gt;arr[1]&lt;/code&gt;. So it will do something like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov     rax, QWORD PTR [rbp-28]
mov     eax, DWORD PTR [rax+4]
mov     DWORD PTR [rbp-16], eax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Can you see the problem? We are reading the content at &lt;code&gt;rbp-28&lt;/code&gt;, but the content is 1, the first element of the array. So, essentially we are reading the content of memory address &lt;code&gt;1+4=5&lt;/code&gt; which is an invalid location!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bottom line: Don't mix and match.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another difference is that a pointer name is a variable, but an array name is not. So you can do &lt;code&gt;pa++&lt;/code&gt; and &lt;code&gt;pa=arr&lt;/code&gt; but you cannot do &lt;code&gt;arr=pa&lt;/code&gt; and &lt;code&gt;arr++&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But, there is a case where arrays and pointers are the same. That is in function parameters - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void f(int *pa, int arr[]) {
    int a = pa[1];
    int b = arr[1];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What is the difference between &lt;code&gt;arr&lt;/code&gt; and &lt;code&gt;pa&lt;/code&gt;? There is no difference&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = pa[1]
mov     rax, QWORD PTR [rbp-24]
mov     eax, DWORD PTR [rax+4]
mov     DWORD PTR [rbp-4], eax

int b = arr[1]
mov     rax, QWORD PTR [rbp-32]
mov     eax, DWORD PTR [rax+4]
mov     DWORD PTR [rbp-8], eax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The compiler treats &lt;code&gt;arr&lt;/code&gt; and &lt;code&gt;pa&lt;/code&gt; both as pointers, and that's about the only case you can be certain that using pointer in place of array works.&lt;/p&gt;

&lt;p&gt;Technically, this is an illustration of an array-like syntax being used to declare pointers, rather than an example of pointers and arrays being the same.&lt;/p&gt;

&lt;p&gt;Since pointers are like any other variable, you can have a pointer to pointers too.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int **pa;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here &lt;code&gt;pa&lt;/code&gt; is a pointer to pointer to &lt;code&gt;int&lt;/code&gt;. So, &lt;code&gt;*pa&lt;/code&gt; will give you a pointer to &lt;code&gt;int&lt;/code&gt;, and finally &lt;code&gt;**pa&lt;/code&gt; will give you an &lt;code&gt;int&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a;
int *pa = &amp;amp;a; // pointer to int
int **ppa = &amp;amp;pa; // pointer to pointer to int
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can have pointers to array too. But before that, remember &lt;code&gt;[]&lt;/code&gt; has higher precedence than &lt;code&gt;*&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int (*pa)[3]; // pointer to array of 3 elements
int *pa[3]; // 3 element array of pointer to int
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What's the difference between pointer to an array and normal pointer? Consider&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int arr[3] = {1, 2, 3};
int (*pa)[3] = &amp;amp;arr;
int *pb = arr;

printf("arr = %p\n", arr);
printf("pa = %p\n", pa);
printf("pb = %p\n", pb);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arr = 0x7fff2632cc84
pa = 0x7fff2632cc84
pb = 0x7fff2632cc84
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So, essentially they all point to the same location. And we already know &lt;code&gt;arr&lt;/code&gt; is the same as a pointer to the first element. Now we see that &lt;code&gt;&amp;amp;arr&lt;/code&gt; also contains the location of the first element.&lt;/p&gt;

&lt;p&gt;Although &lt;code&gt;pa&lt;/code&gt; and &lt;code&gt;pb&lt;/code&gt; point to the same location, what they point to is very different. &lt;code&gt;pb&lt;/code&gt; is a pointer to [int] so it points to a [int] which is the first element of &lt;code&gt;arr&lt;/code&gt; whereas &lt;code&gt;pa&lt;/code&gt; is a pointer to [array of 3 elements] so it points to an [array of 3 elements] i. e. the whole &lt;code&gt;arr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is evident when you try to do arithmetic - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printf("pa + 1 = %p\n", pa + 1);
printf("pb + 1 = %p\n", pb + 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pa + 1 = 0x7fff2632cc90
pb + 1 = 0x7fff2632cc88
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pb&lt;/code&gt; is a pointer to &lt;code&gt;int&lt;/code&gt;. So &lt;code&gt;pb+1&lt;/code&gt; points to the next &lt;code&gt;int&lt;/code&gt; 4 bytes after. Whereas &lt;code&gt;pa&lt;/code&gt; is a pointer to array of 3 &lt;code&gt;int&lt;/code&gt;. So &lt;code&gt;pa+1&lt;/code&gt; will point to the next array of 3 &lt;code&gt;int&lt;/code&gt; which is &lt;code&gt;3 * 4 = 12&lt;/code&gt; bytes after, and indeed, &lt;code&gt;pa+1&lt;/code&gt; is 12 bytes after &lt;code&gt;pa&lt;/code&gt;. ( 0x7fff2632cc90 - 0x7fff2632cc84 = 12, these are in hexadecimal in case you're confused).&lt;/p&gt;

&lt;p&gt;You can use a pointer to array just like a normal variable. Just remember the precedence - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int arr[3] = {1, 2, 3};
int (*pa)[3] = &amp;amp;arr;

int a = *pa[1]; // Wrong
int b = (*pa)[1]; // Correct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The easiest way to remember is "Declaration follows usage." So the usage of a pointer will look like the way it was defined. Since we defined &lt;code&gt;pa&lt;/code&gt; as &lt;code&gt;(*pa)[]&lt;/code&gt;, its usage will also look the same.&lt;/p&gt;

&lt;p&gt;One common mistake that students do, with the fact that arrays decay down to pointers in function parameters is working with multidimensional arrays.&lt;/p&gt;

&lt;p&gt;If you have something like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int arr[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
f(arr);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;you might think since array names decay to pointers in function parameter, an array of array should decay to a pointer to pointer. So you might write the declaration of &lt;code&gt;f&lt;/code&gt; as&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void f(int **m) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Unfortunately, this is wrong and will give a warning (but will compile)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main.c:22:8: warning: passing argument 1 of ‘f’ from incompatible pointer type [-Wincompatible-pointer-types]                                                   
main.c:11:5: note: expected ‘int **’ but argument is of type ‘int (*)[4]’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What happened here? It's easy.&lt;/p&gt;

&lt;p&gt;If an array of [int] decays down to a pointer to [int], what should an array of [array of int] decay down to? Of course a pointer to [array of int]. Remember that the size is also part of arrays type. So, in our case, &lt;code&gt;arr&lt;/code&gt; is an array of [4 element array of int]. So, it decays down to pointer to [4 element array of int].&lt;/p&gt;

&lt;p&gt;So you should write&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void f(int (*m)[4]) {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or, you can just take an array of array&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void f(int m[][4]){
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that only the size of the rightmost column is required in the formal parameters list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pointers and Structures and Unions
&lt;/h3&gt;

&lt;p&gt;Now we move on to &lt;code&gt;struct&lt;/code&gt; and &lt;code&gt;union&lt;/code&gt;. We can have pointers to them too.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct some_struct {
    int p;
    int q;
}

struct some_struct a;
struct some_struct *pa = &amp;amp;a;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or if you prefer a &lt;code&gt;typedef&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;typedef struct some_struct {
    int p;
    int q;
} some_struct;

some_struct a;
some_struct *pa = &amp;amp;a;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;An interesting situation occurs when you want to access members of &lt;code&gt;struct&lt;/code&gt; using pointer. Suppose you want to access the member &lt;code&gt;p&lt;/code&gt; through &lt;code&gt;pa&lt;/code&gt;. You might do&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int k = *pa.p;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Except, this doesn't do what you expect. The operator &lt;code&gt;.&lt;/code&gt; has higher precedence than &lt;code&gt;*&lt;/code&gt; so &lt;code&gt;*pa.p&lt;/code&gt; is same as &lt;code&gt;*(pa.p)&lt;/code&gt;. So instead of dereferencing &lt;code&gt;pa&lt;/code&gt; and then accessing the member &lt;code&gt;p&lt;/code&gt;, you end up accessing the member &lt;code&gt;p&lt;/code&gt; and then dereferencing it. But &lt;code&gt;pa&lt;/code&gt; doesn't have a member &lt;code&gt;p&lt;/code&gt;. So, it gives a compiler error.&lt;/p&gt;

&lt;p&gt;Instead, you want to write this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int k = (*pa).p;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which works the way you want. But writing this is tedious, and turns out that we write this so much that they have a special operator &lt;code&gt;-&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int k = pa -&amp;gt; p;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pa -&amp;gt; p&lt;/code&gt; is same as &lt;code&gt;(*pa).p&lt;/code&gt; but looks neat and clean.&lt;/p&gt;

&lt;p&gt;The case of unions is a little bit involved. Quoting cppreference - &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A pointer to a union can be cast to a pointer to each of its members (if a union has bit field members, the pointer to a union can be cast to the pointer to the bit field's underlying type). Likewise, a pointer to any member of a union can be cast to a pointer to the enclosing union.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What it means is that, if you have a pointer to a union, you can cast it to any of its members, and vice versa. Take a look&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;typedef union some_union {
        int p;
        char q;
    } some_union;

some_union a = {1}; // Initialize a with p = 1
some_union *pa = &amp;amp;a;
printf("%d\n", pa -&amp;gt; p); // Access p through pointer to a

int * pb = (int *) pa; // cast pa to point to p directly
printf("%d\n", *pb);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, I could cast the pointer to &lt;code&gt;a&lt;/code&gt; to an &lt;code&gt;int*&lt;/code&gt; and it automatically pointed to the member &lt;code&gt;p&lt;/code&gt;. Similarly, if I had cast it to &lt;code&gt;char*&lt;/code&gt; it would point to &lt;code&gt;q&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Conversely, if I had a pointer to &lt;code&gt;p&lt;/code&gt;, I could cast it to a pointer to &lt;code&gt;some_union&lt;/code&gt; and it would point to &lt;code&gt;a&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int *pc = &amp;amp;(a.p);
some_union *pd = (some_union *)pc;
a.q = 'a';
printf("%c\n", pd -&amp;gt; q);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints &lt;code&gt;a&lt;/code&gt; as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ministry of Pointer Magic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pointers and Function
&lt;/h3&gt;

&lt;p&gt;It is possible to have pointers to functions too. Remember that the &lt;strong&gt;return type, the number of parameters it takes, and the type of each parameter&lt;/strong&gt; - these 3 are parts of the type of a function. Hence you must provide these during pointer declaration. Also worth noting &lt;code&gt;()&lt;/code&gt; has higher precedence than &lt;code&gt;*&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int *f(); // a function that returns a pointer to int
int (*f)(); // a pointer to a function that takes no argument and returns an int
int (*f)(int); // a pointer to a function that takes an int and returns an int
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A pointer to function can be used just like other pointers - &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int f(int a) {
    return a+1;
}

int main()
{
    int (*fp)(int) = &amp;amp;f;
    printf("%d\n", (*fp)(1));
    // printf("%d\n", *fp(1)); Wrong~ Won't cpmpile
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This prints &lt;code&gt;2&lt;/code&gt; as you'd expect.&lt;/p&gt;

&lt;p&gt;Remember I talked about declaration follows usage? Well, turns out that in the case of pointer to functions, that rule can be ignored. For example, this works&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printf("%d\n", (**fp)(1));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And so does this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printf("%d\n", (*********fp)(1));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And weirdly enough, this too&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;printf("%d\n", fp(1));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So, in the case of functions, &lt;strong&gt;not only you can dereference as many times as you want, you can drop the dereferencing altogether and just use the pointer as if it were a function itself&lt;/strong&gt;. Another one of those C quirks.&lt;/p&gt;

&lt;p&gt;Finally, you can get wild with pointers and arrays and function like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;char *(*(*foo)[5])(int); // foo is a pointer to array of 5 elements of pointer to a function that takes an int and returns a pointer to char
int *(*foo)(int *, int (*[4])()); // foo is a pointer to function (that takes a pointer to int and a 4 element array of pointer to functions that return int) and returns a pointer to int
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You get the idea. Yes, it can get pretty messy, but once you know the syntax, and you have &lt;a href="//__GHOST_URL__/p/7a2aaf27-dbd2-4822-8dd4-3bbb6da203ac/cdecl.org"&gt;cdecl&lt;/a&gt;, you can easily breeze through them (or read my &lt;a href="https://dev.to__GHOST_URL__/declaration-in-c-p1/"&gt;article&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;As for how you can use a function pointer. here's an example of a simple calculator&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

float add(float a, float b) { return a+b; }
float sub(float a, float b) { return a-b; }
float mul(float a, float b) { return a+b; }
float divide(float a, float b) { return a/b; }


int main()
{
    float (*arr[4])(float, float) = { add, sub, mul, divide };
    int n;
    float a, b;
    printf("Enter two numbers: ");
    scanf("%f%f", &amp;amp;a, &amp;amp;b);
    printf("Enter 1 for addition, 2 for subtraction, 3 for multiplication, 4 for division: ");
    scanf("%d", &amp;amp;n);
    printf("%f", arr[n-1](a, b));

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We are storing all 4 operations in an array and when the user enters a number, we call the corresponding operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qualified types
&lt;/h3&gt;

&lt;p&gt;Each type in C can be qualified by using qualifiers. In particular we have 3 - &lt;code&gt;const&lt;/code&gt;, &lt;code&gt;volatile&lt;/code&gt;, and &lt;code&gt;restrict&lt;/code&gt;. Here we will look at &lt;code&gt;const&lt;/code&gt; and &lt;code&gt;restrict&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;const&lt;/code&gt; to a type effectively marks it read-only, so that attempting to change the value will result in a compiler error.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const int p = 1;
p = 2; // error: assignment of read-only variable ‘p’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Turns out, &lt;code&gt;int const&lt;/code&gt; and &lt;code&gt;const int&lt;/code&gt; both are valid. Now if I throw pointers into the party, I get some fun stuff&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const int *p;
int * const p;
const int * const p;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Can you guess which one is what?&lt;/p&gt;

&lt;p&gt;To untangle this, we will remember &lt;code&gt;some_data_type *p&lt;/code&gt; declares &lt;code&gt;p&lt;/code&gt; to be a pointer to &lt;code&gt;some_data_type&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Hence, &lt;code&gt;const int *p&lt;/code&gt; can be thought of as &lt;code&gt;(const int) *p&lt;/code&gt;. So that &lt;code&gt;p&lt;/code&gt; is a pointer to a &lt;code&gt;const int&lt;/code&gt;. It means, whatever &lt;code&gt;p&lt;/code&gt; is pointing to is a &lt;code&gt;const int&lt;/code&gt; and you cannot change that. However, &lt;code&gt;p&lt;/code&gt; itself is not &lt;code&gt;const&lt;/code&gt; and can be changed.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const int a = 1;
const int b = 2;
const int *p = &amp;amp;a;

p = &amp;amp;b; // works. You can change p
// *p = 3; error: assignment of read-only location ‘*p’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For the second one compare it with &lt;code&gt;int const p&lt;/code&gt; which declares &lt;code&gt;p&lt;/code&gt; as a read only &lt;code&gt;int&lt;/code&gt;. So, &lt;code&gt;int * const p&lt;/code&gt; should declare &lt;code&gt;p&lt;/code&gt; as read-only &lt;code&gt;int*&lt;/code&gt;. This means the pointer itself is &lt;code&gt;const&lt;/code&gt; and you can't change &lt;code&gt;p&lt;/code&gt; , but you can change what &lt;code&gt;p&lt;/code&gt; is pointing to.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int a = 1, b = 2;
int * const p = &amp;amp;a;

*p = 2; // Works. you can change *p
// p = &amp;amp;b; error: assignment of read-only location ‘p’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And finally, &lt;code&gt;const int * const p&lt;/code&gt; declares that both &lt;code&gt;p&lt;/code&gt; and &lt;code&gt;*p&lt;/code&gt; are read-only. So you can neither change &lt;code&gt;p&lt;/code&gt; nor &lt;code&gt;*p&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const int a = 1, b = 2;
const int * const p = &amp;amp;a;
// *p = 2; error: assignment of read-only location ‘*p’
// p = &amp;amp;b; error: assignment of read-only location ‘p’
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let's look at the &lt;code&gt;restrict&lt;/code&gt; keyword. The &lt;code&gt;restrict&lt;/code&gt; keyword, when applied to a pointer &lt;code&gt;p&lt;/code&gt;, tells the compiler that as long as &lt;code&gt;p&lt;/code&gt; is in scope, only &lt;code&gt;p&lt;/code&gt; and pointers directly or indirectly derived from it (e. g. &lt;code&gt;p+1&lt;/code&gt; ) will access the thing it's pointing to.&lt;/p&gt;

&lt;p&gt;Confused? Let's see an example.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void f(int *p, int *q, int *v) {
    *p += *v;
    *q += *v;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is the assembly generated after enabling optimization -&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov     eax, DWORD PTR [rdx]
add     DWORD PTR [rdi], eax
mov     eax, DWORD PTR [rdx]
add     DWORD PTR [rsi], eax
ret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The problem in this function is, &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;q&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; might point to the same location. So that when you do &lt;code&gt;*p += *v&lt;/code&gt;, it might happen that &lt;code&gt;*v&lt;/code&gt; also gets changed because &lt;code&gt;p&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; were pointing to the same location. &lt;/p&gt;

&lt;p&gt;This is why &lt;code&gt;*v&lt;/code&gt; is first loaded into &lt;code&gt;eax&lt;/code&gt; by &lt;code&gt;mov &amp;nbsp; &amp;nbsp; eax, DWORD PTR [rdx]&lt;/code&gt;. Then it is added to &lt;code&gt;*p&lt;/code&gt;. Again, we have to load &lt;code&gt;*v&lt;/code&gt; because at this point, we are not sure if &lt;code&gt;*v&lt;/code&gt; has changed or not.&lt;/p&gt;

&lt;p&gt;Now if I update the function as follows -&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void g(int *restrict p, int *restrict q, int *restrict v) {
    *p += *v;
    *q += *v;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;the compiler is free to assume that &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;q&lt;/code&gt; and &lt;code&gt;v&lt;/code&gt; all point to different locations, and can load &lt;code&gt;*v&lt;/code&gt; only once, and indeed it does&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov     eax, DWORD PTR [rdx]
add     DWORD PTR [rdi], eax
add     DWORD PTR [rsi], eax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that it is up to the programmer to guarantee that the pointers do not overlap. In case they do, it is undefined behavior.&lt;/p&gt;

&lt;p&gt;You can read more about &lt;code&gt;restrict&lt;/code&gt;&lt;a href="https://en.cppreference.com/w/c/language/restrict" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's probably enough for one post. To quickly recap, you have learned - &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What pointers are.&lt;/li&gt;
&lt;li&gt;How to declare and dereference pointers.&lt;/li&gt;
&lt;li&gt;Pointer arithmetic.&lt;/li&gt;
&lt;li&gt;Pointer comparison.&lt;/li&gt;
&lt;li&gt;Pointer to array and array of pointers.&lt;/li&gt;
&lt;li&gt;Pointers and arrays are not the same.&lt;/li&gt;
&lt;li&gt;Pointer arithmetic and array indexing are equivalent.&lt;/li&gt;
&lt;li&gt;Array in function parameter decay to a pointer.&lt;/li&gt;
&lt;li&gt;Pointers to a multidimensional array.&lt;/li&gt;
&lt;li&gt;Pointers to structures and unions.&lt;/li&gt;
&lt;li&gt;Pointer to functions.&lt;/li&gt;
&lt;li&gt;Pointer to wild exotic types.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;const&lt;/code&gt; and &lt;code&gt;restrict&lt;/code&gt; with pointers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that most of these hold true in C++ also, although you have minor changes, and some new stuff like smart pointers.&lt;/p&gt;

&lt;p&gt;Hopefully, you learned something new in this post. Subscribe to &lt;a href="https://blog.abhattacharyea.dev" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; for more.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>c</category>
      <category>pointers</category>
    </item>
    <item>
      <title>Replace your Existing Unix Utilities with These Modern Alternatives</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Fri, 30 Jul 2021 17:34:34 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/replace-your-existing-unix-utilities-with-these-modern-alternatives-2bfo</link>
      <guid>https://dev.to/heraldofsolace/replace-your-existing-unix-utilities-with-these-modern-alternatives-2bfo</guid>
      <description>&lt;p&gt;In your day-to-day Unix or Linux usage you use different tools like &lt;code&gt;cd&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, or &lt;code&gt;find&lt;/code&gt;. These are ubiquitous utilities found in every Nix system. However, they were created a long time ago, when computers lacked computational power and speed. These tools were tailored towards systems with very little resources. Nowadays, our systems are far better and our requirements are far bigger. In this article, I will show you some utilities aimed towards providing modern replacements for these useful Unix utilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sharkdp/bat" rel="noopener noreferrer"&gt;&lt;code&gt;bat&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bat&lt;/code&gt; is a modern replacement for cat written in Rust. Unlike &lt;code&gt;cat&lt;/code&gt;, this tool supports syntax highlighting for many programming languages out of the box.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvkj5sya46iphvlwzumv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvkj5sya46iphvlwzumv.png" alt="bat displaying Markdown file" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnpqt0rsoot98ziz8kr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnpqt0rsoot98ziz8kr5.png" alt="bat displaying Ruby file" width="742" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another interesting feature of &lt;code&gt;bat&lt;/code&gt; is native Git integration. If you run &lt;code&gt;bat&lt;/code&gt; from a git repository, you will get a "git gutter" indicating modification with respect to the index.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx6octofh5e7e8q4wwvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx6octofh5e7e8q4wwvs.png" alt="Git gutter with bat" width="797" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;bat&lt;/code&gt; automatically pipes its own output into a pager (e. g. &lt;code&gt;less&lt;/code&gt;) when the output is bigger than one page. Which means you don't have to manually pipe the output into &lt;code&gt;less&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you wish to replace &lt;code&gt;cat&lt;/code&gt; with &lt;code&gt;bat&lt;/code&gt;, you can use the following alias -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alias cat='bat --style header --style rules --style snip --style changes --style header'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://github.com/ogham/exa" rel="noopener noreferrer"&gt;&lt;code&gt;exa&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;exa&lt;/code&gt; is a modern alternative for &lt;code&gt;ls&lt;/code&gt;, also written in Rust. Apart from some minor differences, &lt;code&gt;exa&lt;/code&gt; has all the features of &lt;code&gt;ls&lt;/code&gt;, along with Git integration and colors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cdq1e9q0ih7q8kkb9j5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cdq1e9q0ih7q8kkb9j5.png" alt="exa uses colors to distinguish files and folders" width="606" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;--git&lt;/code&gt; option, you can quickly see the Git status of files inside a Git repo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58f4ay3qz9xiof6pr8qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58f4ay3qz9xiof6pr8qq.png" alt="exa shows the Git status" width="799" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;--tree&lt;/code&gt; option to get a tree view of the files. Using the &lt;code&gt;--level&lt;/code&gt; option, you can specify the depth of the tree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2kch361oe3jz4w4mtqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2kch361oe3jz4w4mtqh.png" alt="exa tree view" width="511" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;--icons&lt;/code&gt; option to display little icons next to the filenames, provided your font contains those icons. You can install any Nerd Font and you should be good to go.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqeghvv1k99m99w2ey3y1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqeghvv1k99m99w2ey3y1.png" alt="exa displaying icons" width="788" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/bootandy/dust" rel="noopener noreferrer"&gt;&lt;code&gt;dust&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dust&lt;/code&gt; is an alternative for &lt;code&gt;du&lt;/code&gt;, and it should not be surprising that it is written in Rust! When you run dust, it shows a nice tree structure of files and folders along with a visualization of sizes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjphywg17m5b923p5v9ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjphywg17m5b923p5v9ih.png" alt="Output of dust" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/muesli/duf" rel="noopener noreferrer"&gt;&lt;code&gt;duf&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;duf&lt;/code&gt; is a replacement for the &lt;code&gt;df&lt;/code&gt; utility. Guess which language it is written in? Nope, not Rust. It's written in Go.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;duf&lt;/code&gt; makes the output easy to understand and visualize. It has color highlights and percentage bars so that you can easily understand your disk usage. Also, it separates local devices, network devices, and special devices in separate regions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9tr1o7tgdixdrfucrij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9tr1o7tgdixdrfucrij.png" alt="duf output" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use &lt;code&gt;duf --all&lt;/code&gt; to list all devices including pseudo, duplicate and inaccessible devices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30680441sma6pxtee1rp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30680441sma6pxtee1rp.png" alt="duf --all output" width="647" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use &lt;code&gt;duf --json&lt;/code&gt; to output a convenient JSON file.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sharkdp/fd" rel="noopener noreferrer"&gt;&lt;code&gt;fd&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;fd&lt;/code&gt; is an alternative for the find utility. And we're back again to the Rust world!&lt;/p&gt;

&lt;p&gt;When you run fd command with a search pattern, it will search the current directory for the pattern and show you the file list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfn5zv2bd5c7cv0musqp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfn5zv2bd5c7cv0musqp.png" alt="fd output" width="471" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use regular expression to refine your search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapocx532f0m5jf03olcg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapocx532f0m5jf03olcg.png" alt="Regex with fd" width="219" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can search for a specific extension by using the &lt;code&gt;-e&lt;/code&gt; flag. You can combine this with the search pattern.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q2smnyfwlfk9h5dtmj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6q2smnyfwlfk9h5dtmj3.png" alt="fd with -e flag" width="555" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like find, you can run a command on every found file using the &lt;code&gt;-X&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1znlrmluuchxvjc4yhut.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1znlrmluuchxvjc4yhut.png" alt="exa with -X flag" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/BurntSushi/ripgrep" rel="noopener noreferrer"&gt;&lt;code&gt;ripgrep&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ripgrep&lt;/code&gt; is a super fast &lt;code&gt;grep&lt;/code&gt; alternative written in Rust. Apart from being super fast, it also respects your &lt;code&gt;.gitignore.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ripgrep&lt;/code&gt; is invoked through the &lt;code&gt;rg&lt;/code&gt; command. The basic usage is similar to &lt;code&gt;grep&lt;/code&gt;. You provide a pattern and a file to find the pattern in. &lt;code&gt;rg&lt;/code&gt; by default also shows the line numbers and highlights the search term.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1h6yf6s9uvd31byp7zql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1h6yf6s9uvd31byp7zql.png" alt="rg searching for a pattern" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rg&lt;/code&gt; also supports regular expressions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F530mw59cp8gspf8qsffl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F530mw59cp8gspf8qsffl.png" alt="Regex in rg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do not specify a file name, &lt;code&gt;rg&lt;/code&gt; will perform a recursive search. Similar to &lt;code&gt;grep -r&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzzxze3y2iet0g5yjx94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzzxze3y2iet0g5yjx94.png" alt="Recursive search with rg" width="675" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rg&lt;/code&gt; respects your &lt;code&gt;.gitignore&lt;/code&gt;. Which means if a file is matched in &lt;code&gt;.gitignore&lt;/code&gt;, it will not be searched. You can read more about how to use this feature in the &lt;a href="https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#automatic-filtering" rel="noopener noreferrer"&gt;manual&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/tldr-pages/tldr" rel="noopener noreferrer"&gt;&lt;code&gt;tldr&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;How many times have you forgot how a command works, and opened the man pages only to get buried under tons and tons of documentation, when you only wanted some examples? &lt;code&gt;tldr&lt;/code&gt; is not an exact replacement for man pages, but it is a community effort to simplify the manual pages. Rather than throwing an ocean of information about you, &lt;code&gt;tldr&lt;/code&gt; lists examples and small details, enough to get you started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyl6mhgi7g9gr4zsau9vr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyl6mhgi7g9gr4zsau9vr.png" alt="tldr tar output" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/dalance/procs" rel="noopener noreferrer"&gt;&lt;code&gt;procs&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Yet another tool written in Rust. &lt;code&gt;procs&lt;/code&gt; is a modern alternative for &lt;code&gt;ps&lt;/code&gt;. When you run &lt;code&gt;procs&lt;/code&gt;, it shows you a list of running processes in a nicely themed output. The output is automatically paged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdkdf94wwo4jshrqo8wd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdkdf94wwo4jshrqo8wd.png" alt="procs output" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run &lt;code&gt;procs&lt;/code&gt; followed by the name of a process, &lt;code&gt;procs&lt;/code&gt; will search for that process, similar to running &lt;code&gt;ps aux | grep process&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxda4uetzvalnnvi68e5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxda4uetzvalnnvi68e5.png" alt="procs can search processes" width="800" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;procs&lt;/code&gt; can also show other information which are not available to &lt;code&gt;ps&lt;/code&gt;, like TCP/UDP port, Read/Write throughput, or Docker container name. You can read more about them in the procs manual.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/ajeetdsouza/zoxide" rel="noopener noreferrer"&gt;&lt;code&gt;zoxide&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;zoxide&lt;/code&gt; is a fast replacement for the &lt;code&gt;cd&lt;/code&gt; command. It keeps track of the directories you visit and can quickly take you to the directory you want to go.&lt;/p&gt;

&lt;p&gt;You can use &lt;code&gt;zoxide&lt;/code&gt; just like the regular &lt;code&gt;cd&lt;/code&gt; command. For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z foo/bar/baz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zoxide&lt;/code&gt; keeps a track of the directories you visit, and you can use fuzzy finding to quickly visit a directory you have visited before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z baz # Takes you the highest ranking directory matching baz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;zoxide&lt;/code&gt; also comes with various integrations with many different tools. Check them out from their &lt;a href="https://github.com/ajeetdsouza/zoxide#third-party-integrations" rel="noopener noreferrer"&gt;manual&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you could find your next favourite command line utility from this post :). If you liked this article, do not forget to share and subscribe to &lt;a href="https://blog.abhattacharyea.dev" rel="noopener noreferrer"&gt;my blog&lt;/a&gt; for more such articles. &lt;/p&gt;

</description>
      <category>linux</category>
      <category>bash</category>
      <category>shell</category>
      <category>cli</category>
    </item>
    <item>
      <title>Help needed in figuring out Rails association</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Sat, 06 Jun 2020 08:40:47 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/help-needed-in-figuring-out-rails-association-540j</link>
      <guid>https://dev.to/heraldofsolace/help-needed-in-figuring-out-rails-association-540j</guid>
      <description>&lt;p&gt;I have two models - &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Network&lt;/code&gt;. In my case, each &lt;code&gt;User&lt;/code&gt; can have many &lt;code&gt;Network&lt;/code&gt; and each &lt;code&gt;Network&lt;/code&gt; can have many &lt;code&gt;User&lt;/code&gt; and belongs to one &lt;code&gt;User&lt;/code&gt; (the one who created).&lt;/p&gt;

&lt;p&gt;For example, let's say there is &lt;code&gt;user1&lt;/code&gt; with two networks &lt;code&gt;N1&lt;/code&gt; and &lt;code&gt;N2&lt;/code&gt;. The network &lt;code&gt;N1&lt;/code&gt; has &lt;code&gt;user2&lt;/code&gt; and the network &lt;code&gt;N2&lt;/code&gt; has &lt;code&gt;user3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What I want to achieve is the &lt;code&gt;creator&lt;/code&gt; field of &lt;code&gt;N1&lt;/code&gt; and &lt;code&gt;N2&lt;/code&gt; will give me &lt;code&gt;user1&lt;/code&gt; and the &lt;code&gt;users&lt;/code&gt; field of &lt;code&gt;N1&lt;/code&gt; will give me &lt;code&gt;[user2]&lt;/code&gt; and that of &lt;code&gt;N2&lt;/code&gt; will give me &lt;code&gt;[user3]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So far, it's ok. I can have the &lt;code&gt;creator&lt;/code&gt; field with &lt;code&gt;foreign_key&lt;/code&gt;, and I can use a &lt;code&gt;has_many through&lt;/code&gt; relation to figure out which networks contain which users and which users belong to which networks. &lt;/p&gt;

&lt;p&gt;But in that case, is there a way to quickly tell if there is a relation between two users or not?&lt;/p&gt;

&lt;p&gt;For example something like &lt;code&gt;user2.follows? user1&lt;/code&gt; will return &lt;code&gt;true&lt;/code&gt; since &lt;code&gt;user2&lt;/code&gt; belongs to a network of &lt;code&gt;user1&lt;/code&gt;. I can iterate through all the networks created by &lt;code&gt;user1&lt;/code&gt; and check if &lt;code&gt;user2&lt;/code&gt; belongs to it, but is there a better way?&lt;/p&gt;

</description>
      <category>help</category>
      <category>ruby</category>
      <category>rails</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Safety App - a Project I made as a Student</title>
      <dc:creator>Aniket Bhattacharyea</dc:creator>
      <pubDate>Fri, 22 May 2020 09:21:09 +0000</pubDate>
      <link>https://dev.to/heraldofsolace/safety-app-a-project-i-made-as-a-student-5bc7</link>
      <guid>https://dev.to/heraldofsolace/safety-app-a-project-i-made-as-a-student-5bc7</guid>
      <description>&lt;p&gt;As you might know, I am currently a student. As a mathematics undergraduate in the final year, Covid 19 has been a major setback. Recently, I found out about the &lt;a href="https://github.com/education/graduation/pulls" rel="noopener noreferrer"&gt;yearbook initiative by Github Education&lt;/a&gt; and wanted to share a project of mine as a student.&lt;/p&gt;

&lt;p&gt;It's a little difficult for me to consistently work on a project. Apart from studying and teaching students I also work as a part time web developer in DataSutram. Anyway this one had a personal interest.&lt;/p&gt;

&lt;p&gt;Last year one of my friends told me about a kidnapping in her area and it scared me to my core. I thought if I could do anything to help it would be nice. So I made the &lt;a href="https://play.google.com/store/apps/details?id=dev.abhattacharyea.safety" rel="noopener noreferrer"&gt;Safety App&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;It's a pretty simple application. It's main feature is that it lets you select your &lt;strong&gt;trusty&lt;/strong&gt; contacts and makes it easier to contact them during emergency.&lt;/p&gt;

&lt;p&gt;You can choose as many contacts as you want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1o9anfakc6tzt593r4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw1o9anfakc6tzt593r4r.png" alt="contacts" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app shows two persistent notifications&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk14ss6un6cof59fk6goz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk14ss6un6cof59fk6goz.jpg" alt="notification" width="800" height="1663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first one has 3 buttons. The first button is for making calls. If you click on it you will be shown a dialog with a list of your trusty contacts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqau9tqx4le2338k7sg43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqau9tqx4le2338k7sg43.png" alt="contacts" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click any of them, it will call the contact. The same with 2nd button except it launches the SMS app where you can send them an SMS.&lt;/p&gt;

&lt;p&gt;The third button is the most important one - SOS. When you press it, it will send an SOS to &lt;strong&gt;every&lt;/strong&gt; trusted contact with your location.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw5j0wnj81xrygqaacp3u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fw5j0wnj81xrygqaacp3u.jpg" alt="SOS" width="800" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observe that in every screenshot the first contact is called "Super Trusty Contact". It's because it is a special contact. How?&lt;/p&gt;

&lt;p&gt;These notifications are visible in the lockscreen too. When your screen is locked, there is no way to show you which contact to pick. There it picks the first contact. That's why it's super trusty. Although, the SOS os &lt;strong&gt;always&lt;/strong&gt; sent to &lt;strong&gt;every&lt;/strong&gt; contact.&lt;/p&gt;

&lt;p&gt;As for the 2nd notification, it's like the SOS but it lets you capture an audio and uploads it to the cloud and sends a link to your contacts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0zveud49ztmoqhoc8wxa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0zveud49ztmoqhoc8wxa.jpg" alt="Alt Text" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a map in the app which shows you locations of nearby police stations, hospitals etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca58p8dblphtmwcj284q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca58p8dblphtmwcj284q.png" alt="map" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click one of them, you can get at a glance a few infos and also a basic route.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9ceov2uji8p2a2kj2amv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9ceov2uji8p2a2kj2amv.jpg" alt="map" width="800" height="1663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1apepynv9inczqhncfqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1apepynv9inczqhncfqb.png" alt="route" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I made it
&lt;/h2&gt;

&lt;p&gt;The app is written in Kotlin and open sourced &lt;a href="https://github.com/Herald-Of-Solace/Safety-App" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Code wise this is not the best thing I have made. There are repetition everywhere, redundant codes and buggy stuff, but I wrote it in a hurry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to do
&lt;/h2&gt;

&lt;p&gt;Currently there are a few bugs. The notifications don't show up in the lockscreen for some phones. The notifications might disappear. Sometimes the app also crashes and sometimes the location cannot be fetched.&lt;/p&gt;

&lt;p&gt;I'm working on those issues and it's really difficult managing time for everything. Hopefully I'll get around to fixing them soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;The app is free forever and ad-free. This is an app that is meant to help people and I do not want to profit off ot. But still, the cloud is costly and I'm just a student managing my studies. If you find the app useful, and want to support development, you can donate from the app. You can also Paypal me at &lt;a href="mailto:aniketmail669@gmail.com"&gt;aniketmail669@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P. S. Our state just faced the fiercest super cyclone since 1737 and we're in debris. It's been 48 hours and we still have no electricity and we've run out of water. So please #PrayForBengal and let's hope we can turn around.&lt;/p&gt;

</description>
      <category>octograd2020</category>
      <category>github</category>
      <category>android</category>
      <category>kotlin</category>
    </item>
  </channel>
</rss>
