<?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: Niki</title>
    <description>The latest articles on DEV Community by Niki (@snikidev).</description>
    <link>https://dev.to/snikidev</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%2F370563%2F5dadd521-b4c4-4e57-bf52-def16a564eba.png</url>
      <title>DEV Community: Niki</title>
      <link>https://dev.to/snikidev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/snikidev"/>
    <language>en</language>
    <item>
      <title>Defending your codebase from AI slop: guardrails that keep you in control</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Thu, 08 Oct 2026 19:28:53 +0000</pubDate>
      <link>https://dev.to/snikidev/defending-your-codebase-from-ai-slop-guardrails-that-keep-you-in-control-1b73</link>
      <guid>https://dev.to/snikidev/defending-your-codebase-from-ai-slop-guardrails-that-keep-you-in-control-1b73</guid>
      <description>&lt;p&gt;AI coding agents are fast. They also write code that compiles, looks fine at a glance, and slowly wears down your architecture. A component imports the database layer directly. A function grows to 200 lines with three nested ternaries. A dependency shows up that nobody asked for. A routes file picks up a &lt;code&gt;formatDate&lt;/code&gt; helper, then a &lt;code&gt;slugify&lt;/code&gt;, then a &lt;code&gt;retryWithBackoff&lt;/code&gt;. Tests run every line but never check a result.&lt;/p&gt;

&lt;p&gt;None of this is new. Junior developers in a hurry do the same things. What's new is the volume. If an agent opens ten PRs a day, careful human review alone won't catch everything.&lt;/p&gt;

&lt;p&gt;The answer isn't to stop using AI. It's to write your standards down in a form machines can enforce, so the rules hold no matter who or what wrote the code. Here's the stack I usually build for my projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Write the rules down first
&lt;/h2&gt;

&lt;p&gt;Tools can only enforce rules that exist. Before adding any tooling, I decide how I want my code to look, define the rules, and write them in two files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ARCHITECTURE.md&lt;/code&gt; covers the layers, which layer may import which, where state lives, and how data flows.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt;, essential nowadays, is usually created automatically and then edited by me. It covers conventions for AI agents: naming, patterns to use, patterns to avoid, and tools that are off-limits (for example: "we use Biome, don't add ESLint").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agents read these files. So do new hires. And every tool below points back to a section in one of them, so when a check fails, the message explains &lt;em&gt;why&lt;/em&gt; and not just &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Guard the boundaries: dependency-cruiser
&lt;/h2&gt;

&lt;p&gt;Something I've discovered quite recently: &lt;a href="https://www.npmjs.com/package/dependency-cruiser" rel="noopener noreferrer"&gt;dependency-cruiser&lt;/a&gt; checks which files import which. You describe the allowed directions between layers, and it fails the build when something crosses a line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI components can't import the database layer&lt;/li&gt;
&lt;li&gt;modules can't import each other circularly&lt;/li&gt;
&lt;li&gt;features can't reach into another feature's internals&lt;/li&gt;
&lt;li&gt;orphaned files that nothing imports get flagged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the kind of mistake AI makes most, because it takes whatever import path is closest. A rule that always gives the same answer stops it before review.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Guard the shape of the code: Semgrep
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dependency-cruiser&lt;/code&gt; tells you &lt;em&gt;which files talk to each other&lt;/em&gt;. It can't tell you &lt;em&gt;what the code inside a file is doing&lt;/em&gt;. That's what &lt;a href="https://semgrep.dev" rel="noopener noreferrer"&gt;Semgrep&lt;/a&gt; is for.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Semgrep works
&lt;/h3&gt;

&lt;p&gt;Semgrep is a pattern matcher that understands code. You write a rule that says "code shaped like X, in files matching Y, is a violation." Semgrep parses each file into a syntax tree and reports every match. It doesn't run your code or try to understand your whole app. It only checks shapes.&lt;/p&gt;

&lt;p&gt;A rule is a short YAML entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api-routes-no-helpers&lt;/span&gt;
    &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ERROR&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;routes.py is for route handlers only.&lt;/span&gt;
      &lt;span class="s"&gt;Move helpers to their own module (see ARCHITECTURE.md → API).&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;packages/api/src/api/routes.py&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;def $FUNC(...):&lt;/span&gt;
              &lt;span class="s"&gt;...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pattern-not-inside&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;@$ROUTER.$METHOD(...)&lt;/span&gt;
          &lt;span class="s"&gt;def $F(...):&lt;/span&gt;
              &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pattern&lt;/code&gt; is the code to match. &lt;code&gt;$FUNC&lt;/code&gt; matches any name, and &lt;code&gt;...&lt;/code&gt; matches anything.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pattern-not-inside&lt;/code&gt; is the exception. Here, functions with a route decorator are allowed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;paths&lt;/code&gt; limits the rule to certain files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message&lt;/code&gt; is what the developer (or the AI agent) sees when the rule fails. Write it as an instruction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running &lt;code&gt;semgrep --config .semgrep/rules/&lt;/code&gt; checks the whole repo against every rule in that folder. We keep one YAML file per area (app, API, mq) so rules stay easy to find, and each rule gets a small test fixture with code that should match and code that shouldn't, so we know the rule catches what we meant.&lt;/p&gt;

&lt;p&gt;Read &lt;a href="https://docs.semgrep.dev/writing-rules/generic-pattern-matching" rel="noopener noreferrer"&gt;their docs&lt;/a&gt; (or ask your agent to) on what's possible and get creative.&lt;/p&gt;

&lt;h3&gt;
  
  
  One-purpose files
&lt;/h3&gt;

&lt;p&gt;This was the rule we wanted most. Some files exist for one job, and AI agents love to drop "just one small helper" into them. After a few months, your routes file is half utilities.&lt;/p&gt;

&lt;p&gt;So we made it impossible. Each of these files may only contain its one kind of thing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API route modules&lt;/strong&gt; (&lt;code&gt;routes.py&lt;/code&gt;): route handlers only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App &lt;code&gt;services/&lt;/code&gt;&lt;/strong&gt;: one class, methods only, no loose functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App &lt;code&gt;functions/&lt;/code&gt;&lt;/strong&gt;: TanStack server functions only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt;: hooks only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route files&lt;/strong&gt;: route definitions only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DB schema&lt;/strong&gt;: table definitions only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API models/schemas&lt;/strong&gt;: models only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API repositories&lt;/strong&gt;: repository classes only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Helpers go in their own module, where they can be named, tested and reused. The rule message tells the agent exactly that, so it usually fixes itself on the next try.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other patterns worth enforcing
&lt;/h3&gt;

&lt;p&gt;Once you think in shapes, a lot of review comments turn into rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;query and mutation keys written inline instead of coming from a key factory&lt;/li&gt;
&lt;li&gt;data fetching inside components&lt;/li&gt;
&lt;li&gt;raw &lt;code&gt;fetch&lt;/code&gt; in routes and server functions&lt;/li&gt;
&lt;li&gt;Drizzle queries outside &lt;code&gt;db/queries&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;exported query functions that default to &lt;code&gt;tx: DbClient = db&lt;/code&gt; (this hides transaction bugs)&lt;/li&gt;
&lt;li&gt;queue names written as string literals&lt;/li&gt;
&lt;li&gt;raw SQL built from strings in the Python API&lt;/li&gt;
&lt;li&gt;API routes importing models directly instead of going through a repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Semgrep works on &lt;a href="https://docs.semgrep.dev/supported-languages" rel="noopener noreferrer"&gt;many languages&lt;/a&gt;, so it covers anything you want.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; if a review comment can be written as a code pattern, make it a Semgrep rule. Then it gets checked the same way on every PR.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Adding it to an existing codebase
&lt;/h3&gt;

&lt;p&gt;You probably have violations already. Don't let that stop you. In CI, Semgrep can compare against a baseline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On PRs&lt;/strong&gt;, only violations &lt;em&gt;added in that PR&lt;/em&gt; fail the check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On &lt;code&gt;main&lt;/code&gt;&lt;/strong&gt;, the full scan runs, so you can see the existing debt and pay it down over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New code is held to the standard from day one, and nobody has to fix everything first.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Scan for vulnerabilities: SAST, SCA and DAST
&lt;/h2&gt;

&lt;p&gt;Architecture rules keep the code tidy. They don't tell you whether it's safe. AI agents write the same security bugs humans do, just faster: SQL built from strings, an endpoint that forgets to check who's calling it, a token pasted into a config file, user input rendered as HTML.&lt;/p&gt;

&lt;p&gt;Security tools fall into three groups, and each sees something the others can't:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Looks at&lt;/th&gt;
&lt;th&gt;Finds&lt;/th&gt;
&lt;th&gt;Misses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;SAST&lt;/strong&gt; (static application security)&lt;/td&gt;
&lt;td&gt;Your source code, not running&lt;/td&gt;
&lt;td&gt;Injection, unsafe APIs, hardcoded secrets, tainted data flows&lt;/td&gt;
&lt;td&gt;Runtime config, auth bugs that depend on real data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;SCA&lt;/strong&gt; (software composition analysis)&lt;/td&gt;
&lt;td&gt;Your dependencies and lockfiles&lt;/td&gt;
&lt;td&gt;Known CVEs, malicious packages, licenses&lt;/td&gt;
&lt;td&gt;Bugs in your own code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;DAST&lt;/strong&gt; (dynamic application security)&lt;/td&gt;
&lt;td&gt;The running app, from outside&lt;/td&gt;
&lt;td&gt;Missing auth, exposed endpoints, bad headers, misconfigured servers&lt;/td&gt;
&lt;td&gt;Where in the code the bug is; code paths it never reaches&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SAST and SCA run on every PR in seconds or minutes. DAST needs a deployed app, so it runs against a preview environment or staging. Most of the tools below cover more than one group:&lt;/p&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;SAST&lt;/th&gt;
&lt;th&gt;SCA&lt;/th&gt;
&lt;th&gt;DAST&lt;/th&gt;
&lt;th&gt;Other&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Semgrep&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trivy&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Containers, IaC, secrets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snyk&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Containers, IaC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkmarx One&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Containers, IaC, secrets, API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub CodeQL&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Dependabot covers SCA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZAP&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Free, open source&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I am an OSS supporter, so I usually go with Semgrep and Trivy. Companies I've worked for went with whatever suited them best. Choose any; no judgement there.&lt;/p&gt;

&lt;h3&gt;
  
  
  SAST: your code
&lt;/h3&gt;

&lt;p&gt;Good SAST tools track data flow: they follow user input from a request handler and flag it if it reaches SQL, a shell or HTML unescaped. Ask an agent to "add search" and you may get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM products WHERE name LIKE &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, and it's SQL injection. &lt;a href="https://semgrep.dev/products/semgrep-code" rel="noopener noreferrer"&gt;Semgrep Code&lt;/a&gt; catches it with the engine you already run for architecture (add &lt;code&gt;--config p/owasp-top-ten&lt;/code&gt;). &lt;a href="https://codeql.github.com" rel="noopener noreferrer"&gt;CodeQL&lt;/a&gt; is free for public repos and shows results in the PR. &lt;a href="https://snyk.io/product/snyk-code/" rel="noopener noreferrer"&gt;Snyk Code&lt;/a&gt; and &lt;a href="https://checkmarx.com" rel="noopener noreferrer"&gt;Checkmarx&lt;/a&gt; cover the same ground in their platforms. Fail the build on high-confidence rules only. A noisy check gets switched off.&lt;/p&gt;

&lt;h3&gt;
  
  
  SCA: your dependencies
&lt;/h3&gt;

&lt;p&gt;Agents add packages freely, sometimes outdated, sometimes made up and squatted ("slopsquatting"). &lt;a href="https://trivy.dev" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt; is the free baseline for vulnerable packages, secrets, container images and IaC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy fs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--scanners&lt;/span&gt; vuln,secret,misconfig &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL &lt;span class="nt"&gt;--exit-code&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trivy only matches versions, so it flags vulnerable packages whether or not you use the vulnerable part. &lt;a href="https://semgrep.dev/products/semgrep-supply-chain" rel="noopener noreferrer"&gt;Semgrep Supply Chain&lt;/a&gt; checks reachability: does your code actually call the vulnerable function? &lt;a href="https://snyk.io" rel="noopener noreferrer"&gt;Snyk&lt;/a&gt; opens fix PRs and alerts when a new CVE hits code you already shipped. &lt;a href="https://checkmarx.com" rel="noopener noreferrer"&gt;Checkmarx One&lt;/a&gt; also detects malicious packages such as typosquats.&lt;/p&gt;

&lt;p&gt;Require CODEOWNERS approval on &lt;code&gt;package.json&lt;/code&gt; and lockfiles, so every new dependency is a human decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  DAST: the running app
&lt;/h3&gt;

&lt;p&gt;DAST sends real requests to a deployed app, so it catches what code scanning can't: an endpoint the agent left without an auth check, one user reading another's data by changing an ID, missing security headers, exposed debug pages. Run it against a preview deploy or staging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-t&lt;/span&gt; ghcr.io/zaproxy/zaproxy:stable zap-api-scan.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; https://preview.example.com/openapi.json &lt;span class="nt"&gt;-f&lt;/span&gt; openapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.zaproxy.org" rel="noopener noreferrer"&gt;ZAP&lt;/a&gt; is free. &lt;a href="https://github.com/projectdiscovery/nuclei" rel="noopener noreferrer"&gt;Nuclei&lt;/a&gt; checks for known exposures. &lt;a href="https://www.stackhawk.com" rel="noopener noreferrer"&gt;StackHawk&lt;/a&gt;, &lt;a href="https://snyk.io/product/snyk-api-web/" rel="noopener noreferrer"&gt;Snyk API &amp;amp; Web&lt;/a&gt; and &lt;a href="https://checkmarx.com/product/dast/" rel="noopener noreferrer"&gt;Checkmarx DAST&lt;/a&gt; are paid options. Give the scanner credentials and an API spec, or it will scan your login page and little else.&lt;/p&gt;

&lt;p&gt;DAST, I'd say, is a nice-to-have, not a must-have. &lt;a href="https://semgrep.dev/blog/2023/dast-devsecops/" rel="noopener noreferrer"&gt;Folks at Semgrep think so too&lt;/a&gt;. Occasional manual testing or pentesting is more than enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Measure complexity and test quality
&lt;/h2&gt;

&lt;p&gt;Something I've learned about from &lt;a href="https://x.com/unclebobmartin/status/2047661738456121506?s=20" rel="noopener noreferrer"&gt;Uncle Bob&lt;/a&gt;. I never thought about it until AI became a thing.&lt;/p&gt;

&lt;p&gt;CRAP stands for Change Risk Anti-Patterns, a metric used to identify risky, complex, and poorly tested code. The CRAP Index measures the maintenance risk of a specific function or method.&lt;/p&gt;

&lt;p&gt;Rule of thumb: a score above 30 indicates a high-risk, "CRAPpy" method that needs attention or refactoring.&lt;/p&gt;

&lt;p&gt;I found two tools I could integrate into my pipelines that would help to keep AI-generated code under control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/sebassdc/crap4ts" rel="noopener noreferrer"&gt;crap4ts&lt;/a&gt;&lt;/strong&gt; computes a CRAP score from complexity and coverage. Complex code with weak tests scores high. Set a limit and fail functions that go over it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://lukasniessen.github.io/ArchUnitTS/" rel="noopener noreferrer"&gt;ArchUnitTS&lt;/a&gt;&lt;/strong&gt; lets you write architecture rules as unit tests, if you'd rather keep them next to your test suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. AI reviewing AI: CodeRabbit with custom rules
&lt;/h2&gt;

&lt;p&gt;Some rules can't be written as patterns: "does this name match what the code does?" or "is this abstraction premature?" For those, we can use &lt;a href="https://docs.coderabbit.ai/triage/rules" rel="noopener noreferrer"&gt;CodeRabbit&lt;/a&gt; (or one of many other AI code review tools) with a strict config file, like &lt;code&gt;.coderabbit.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the strictest review profile, and it requests changes when it finds problems&lt;/li&gt;
&lt;li&gt;it reads &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt; as guidelines&lt;/li&gt;
&lt;li&gt;instructions for each folder, each linked to the doc section it comes from&lt;/li&gt;
&lt;li&gt;pre-merge checks: most only warn, but a few block the merge (for example, a schema change with no migration)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is that &lt;strong&gt;CodeRabbit is the last layer, not the first.&lt;/strong&gt; AI review isn't consistent, so anything that &lt;em&gt;can&lt;/em&gt; be checked by a tool that always gives the same answer should be. CodeRabbit handles judgement calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. More you can add
&lt;/h2&gt;

&lt;p&gt;What we've set up covers most of it. Here are a few more tools that can help you keep even tighter control over your repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://knip.dev" rel="noopener noreferrer"&gt;Knip&lt;/a&gt;&lt;/strong&gt; finds unused files, exports and dependencies. AI leaves a lot of dead code behind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutation testing&lt;/strong&gt; (which &lt;a href="https://x.com/unclebobmartin/status/2098432570887217520?s=20" rel="noopener noreferrer"&gt;Uncle Bob is also looking at and seems to think is an essential part of CI/CD checks&lt;/a&gt;) (&lt;a href="https://stryker-mutator.io" rel="noopener noreferrer"&gt;Stryker&lt;/a&gt;) changes your code on purpose and checks whether tests fail. It's the best way to catch tests that run code but never check results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR size limits:&lt;/strong&gt; fail or warn on PRs over a few hundred lines. Small PRs actually get reviewed. Not something I'd do, though.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit hooks:&lt;/strong&gt; such as &lt;a href="https://github.com/evilmartians/lefthook" rel="noopener noreferrer"&gt;Lefthook&lt;/a&gt; or, most commonly in JS-based projects, &lt;a href="https://typicode.github.io/husky/" rel="noopener noreferrer"&gt;husky&lt;/a&gt;. You can configure them to run anything, including Biome, Semgrep and type checks, locally, so you get feedback before you push.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The principle
&lt;/h2&gt;

&lt;p&gt;Every layer follows the same idea: &lt;strong&gt;move each rule to the most reliable tool that can enforce it.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write the rule in &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; / &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it's about imports → dependency-cruiser.&lt;/li&gt;
&lt;li&gt;If it's about code shape → Semgrep.&lt;/li&gt;
&lt;li&gt;If it's a security bug in your code → SAST (Semgrep, CodeQL, Snyk Code, Checkmarx).&lt;/li&gt;
&lt;li&gt;If it's about dependencies or secrets → SCA (Trivy, plus Semgrep Supply Chain, Snyk or Checkmarx).&lt;/li&gt;
&lt;li&gt;If it only shows up at runtime → DAST (ZAP, Nuclei, StackHawk, Snyk, Checkmarx) against a preview deploy.&lt;/li&gt;
&lt;li&gt;If it needs judgement → CodeRabbit, and then a human.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI can write as much code as it likes. It just has to pass the same checks as everyone else. That's how you stay in control.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>programming</category>
      <category>cicd</category>
    </item>
    <item>
      <title>The layers of an AI agent product</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Thu, 08 Oct 2026 19:26:45 +0000</pubDate>
      <link>https://dev.to/snikidev/the-layers-of-an-ai-agent-product-844</link>
      <guid>https://dev.to/snikidev/the-layers-of-an-ai-agent-product-844</guid>
      <description>&lt;p&gt;Since agents became a thing, I decided that I wanted to build my own. I'm a believer that building your own will teach you more about the technology and make you a better user.&lt;/p&gt;

&lt;p&gt;But. What is an "agent"? &lt;/p&gt;

&lt;p&gt;"Agent" gets used for everything from a single API call to a full SaaS product. That makes it hard to talk about where a problem lives, or which piece of the stack should solve it. It helps to split an agent product into layers, each with its own job.&lt;/p&gt;

&lt;p&gt;To keep it concrete, I'll use one running example throughout: a support agent that can look up orders and issue refunds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The structure
&lt;/h2&gt;

&lt;p&gt;An AI agent product has distinct layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model SDK → agent framework → harness → execution runtime → product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Databases and files store state for several of these layers. Proof, meaning observability and evals, isn't a layer: it measures how well all of them work.&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;Job&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model SDK&lt;/td&gt;
&lt;td&gt;One request, one response&lt;/td&gt;
&lt;td&gt;OpenAI SDK, Anthropic SDK, Google Gen AI SDK, Vercel AI SDK Core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent framework&lt;/td&gt;
&lt;td&gt;Run the tool-calling loop&lt;/td&gt;
&lt;td&gt;Pydantic AI, Mastra, LangChain / LangGraph, OpenAI Agents SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Harness&lt;/td&gt;
&lt;td&gt;Define the job, context, and boundaries&lt;/td&gt;
&lt;td&gt;Claude Code, Codex CLI, GitHub Copilot CLI, Cursor, your own agent code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution runtime&lt;/td&gt;
&lt;td&gt;Run the work durably, survive failures&lt;/td&gt;
&lt;td&gt;Temporal, Inngest, DBOS, Celery + a queue, LangGraph checkpointers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;Users, tenants, UI, billing, reconnecting&lt;/td&gt;
&lt;td&gt;ChatGPT, Claude.ai, Cursor, Copilot coding agent, your SaaS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proof (across all layers)&lt;/td&gt;
&lt;td&gt;Show what happened and whether it was good&lt;/td&gt;
&lt;td&gt;OpenTelemetry, Langfuse, LangSmith, Logfire, Braintrust, Pydantic Evals&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Model SDK
&lt;/h2&gt;

&lt;p&gt;A model SDK makes a request and returns a response. That response might contain text or a request to call a tool, but the SDK does not complete a multi-step task on its own.&lt;/p&gt;

&lt;p&gt;Here's what that looks like with the Anthropic Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Look up an order by ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&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="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Where is order 1234?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop_reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# "tool_use"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model didn't look anything up. It returned a &lt;code&gt;tool_use&lt;/code&gt; block that says "please call &lt;code&gt;get_order&lt;/code&gt; with &lt;code&gt;order_id="1234"&lt;/code&gt;". Running that function, sending the result back, and asking again is &lt;strong&gt;your&lt;/strong&gt; job. The SDK is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent framework
&lt;/h2&gt;

&lt;p&gt;An agent framework manages that loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assemble context.&lt;/li&gt;
&lt;li&gt;Call the model.&lt;/li&gt;
&lt;li&gt;Validate the requested tool arguments.&lt;/li&gt;
&lt;li&gt;Execute the tools.&lt;/li&gt;
&lt;li&gt;Add their results to the conversation.&lt;/li&gt;
&lt;li&gt;Call the model again, until it finishes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stripped down, this is what's under the hood of every framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Where is order 1234?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&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="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop_reason&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_use&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&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;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_use&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# validate first in real code
&lt;/span&gt;            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_use_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A framework gives you that loop plus the tedious parts: turning function signatures into JSON schemas, validating arguments, retrying when the model sends bad ones, streaming, and switching model providers. The same agent in &lt;a href="https://ai.pydantic.dev/" rel="noopener noreferrer"&gt;Pydantic AI&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic:claude-sonnet-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@agent.tool_plain&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Look up an order by ID.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orders&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="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Where is order 1234?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other frameworks make different trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ai.pydantic.dev/" rel="noopener noreferrer"&gt;Pydantic AI&lt;/a&gt;&lt;/strong&gt;: Python, type-first. Tool arguments and outputs are validated with Pydantic models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://mastra.ai/" rel="noopener noreferrer"&gt;Mastra&lt;/a&gt;&lt;/strong&gt;: TypeScript. Agents, tools with Zod schemas, and workflows in one package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.langchain.com/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; / &lt;a href="https://www.langchain.com/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;&lt;/strong&gt;: LangGraph models the loop as an explicit graph of nodes and edges, which helps when the flow has branches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://openai.github.io/openai-agents-python/" rel="noopener noreferrer"&gt;OpenAI Agents SDK&lt;/a&gt;&lt;/strong&gt;: A small loop with handoffs between agents and guardrails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent run may make many model calls; it is not one continuous model request. Our "where is my order" question is already two calls: one that asks for the tool, one that writes the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Harness
&lt;/h2&gt;

&lt;p&gt;The framework is the loop. The harness is the job.&lt;/p&gt;

&lt;p&gt;A framework answers &lt;em&gt;how&lt;/em&gt; an agent runs: call the model, validate tool arguments, execute tools, append results, call again until it stops. It also covers the tedious parts around that loop: schemas, retries, streaming, provider switching. It doesn't know what the agent is for.&lt;/p&gt;

&lt;p&gt;A harness answers &lt;em&gt;what&lt;/em&gt; this agent is allowed to be: instructions, which tools exist, what context is loaded, memory, permissions, budgets, and stopping rules. It determines what information reaches the model and what actions the model is allowed to request.&lt;/p&gt;

&lt;p&gt;Think of a web app. FastAPI is the framework. Your routes, auth rules, and queries are the harness. FastAPI will route a request. It will not decide that refunds over $100 need a human.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Framework answers&lt;/th&gt;
&lt;th&gt;Harness answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How do I call the model and run tools?&lt;/td&gt;
&lt;td&gt;The loop, schemas, validation, retries&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which tools exist?&lt;/td&gt;
&lt;td&gt;How to register them&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;get_order&lt;/code&gt; and &lt;code&gt;refund&lt;/code&gt;, nothing else&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What does the model see?&lt;/td&gt;
&lt;td&gt;Where to put instructions and messages&lt;/td&gt;
&lt;td&gt;This customer's summary, not the full history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What is it allowed to do?&lt;/td&gt;
&lt;td&gt;Gives you hooks for checks&lt;/td&gt;
&lt;td&gt;Refunds over $100 go to a human&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When does it stop?&lt;/td&gt;
&lt;td&gt;Stops when the model stops calling tools&lt;/td&gt;
&lt;td&gt;After 10 model calls, or when the budget runs out&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The swap test makes it concrete. Swap the framework, port the same instructions, tools, and limits, and the agent should still do the same job. Swap the harness and you have a different agent, even on the same library. The same Pydantic AI loop can run a support agent that only reads one customer's orders, or an analytics agent with read access to the whole warehouse. Same loop, different harness, different product.&lt;/p&gt;

&lt;p&gt;A note on wording: products often call the whole thing a harness, loop included. Anthropic, for example, describes Claude Code as the agentic harness around Claude. Here, framework means the reusable loop, and harness means the job-specific decisions on top of it.&lt;/p&gt;

&lt;p&gt;For our support agent, the harness is the code around &lt;code&gt;agent.run()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic_ai.usage&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UsageLimits&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic:claude-sonnet-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a support agent. Only discuss the customer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s own orders.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@agent.instructions&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;customer_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Context selection: load a summary, not the whole history
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Customer: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Notes: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;support_summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@agent.tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Scoping: the model can only see this customer's orders
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@agent.tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RunContext&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Permissions: large refunds need a human
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Refund queued for human approval.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;usage_limits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;UsageLimits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# Budget and stopping rule
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing in there is new framework machinery. It's decisions: what the agent is for, what it can see, what it can do, and when it must stop.&lt;/p&gt;

&lt;p&gt;Coding agents are the best-known harnesses. &lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, &lt;a href="https://github.com/openai/codex" rel="noopener noreferrer"&gt;Codex CLI&lt;/a&gt;, and &lt;a href="https://github.com/features/copilot/cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI&lt;/a&gt; all run roughly the same loop against a model. What makes them different is what's under the hood of the harness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instructions&lt;/strong&gt;: a long system prompt describing how to work in a codebase, plus project files such as &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt;: read, edit, and search files; run shell commands; fetch URLs; spawn sub-agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt;: ask before running a command or editing a file, allow-lists for safe commands, sandboxes that block network or writes outside the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context management&lt;/strong&gt;: search the codebase instead of loading it, truncate large tool outputs, and summarise (compact) the conversation when it approaches the context limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stopping rules&lt;/strong&gt;: finish when the model stops calling tools, when the user interrupts, or when a limit is hit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Swap the model under Claude Code and you still have most of Claude Code. Swap the harness and you have a different product. That's also why Anthropic ships the &lt;a href="https://docs.anthropic.com/en/docs/claude-code/sdk" rel="noopener noreferrer"&gt;Claude Agent SDK&lt;/a&gt;: it's the Claude Code harness offered as a library.&lt;/p&gt;

&lt;p&gt;Memory is part of that context strategy. It might be recent messages in a database, a summary, or a file such as &lt;code&gt;MEMORY.md&lt;/code&gt;. The file is not inherently "memory": the harness must decide when to write it, how to scope it, and what to load into future model requests. The model sees the context it is given, not everything stored on disk.&lt;/p&gt;

&lt;p&gt;In the support agent above, &lt;code&gt;support_summary&lt;/code&gt; is memory. Some job has to write it after each conversation, and &lt;code&gt;customer_context&lt;/code&gt; decides to load it. Without both, it's just a column in a table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Execution runtime
&lt;/h2&gt;

&lt;p&gt;The framework can run inside an HTTP request, but a durable service separates accepting a task from executing it. An API records a run; a worker executes it; checkpoints record completed progress so another worker can continue after a failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /runs  →  insert run (status=queued)  →  202 Accepted { run_id }
                         ↓
                 queue → worker → agent loop → checkpoint after each step
                         ↓
                 run status=completed, events stored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The options range from simple to heavy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A queue and workers&lt;/strong&gt; (Celery, BullMQ, SQS + Lambda): easy to start. Checkpointing is up to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable execution engines&lt;/strong&gt; (&lt;a href="https://temporal.io/" rel="noopener noreferrer"&gt;Temporal&lt;/a&gt;, &lt;a href="https://www.inngest.com/" rel="noopener noreferrer"&gt;Inngest&lt;/a&gt;, &lt;a href="https://www.dbos.dev/" rel="noopener noreferrer"&gt;DBOS&lt;/a&gt;): each model call and tool call becomes a recorded step. If a worker dies, another one replays the recorded results and continues from the next step. Pydantic AI has built-in integrations for Temporal, DBOS, and Prefect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework checkpointers&lt;/strong&gt;: LangGraph saves graph state after each step to Postgres or SQLite, so a thread can resume or pause for human approval. Mastra workflows can suspend and resume.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What a durable execution engine actually is
&lt;/h3&gt;

&lt;p&gt;An event log plus replay. Every engine has the same parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storage&lt;/strong&gt;: an append-only log per run ("step started", "step completed with this result"), usually in Postgres.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A queue and workers&lt;/strong&gt;: your code runs in your workers, built with the engine's SDK. Temporal works like BullMQ with Redis: workers pull tasks from the Temporal server, which you self-host or rent as Temporal Cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay&lt;/strong&gt;: when a worker dies, another one re-runs the code from the top. Finished steps return their recorded result instead of running again, so execution continues from the first unfinished step. That's the difference from a plain job queue, which retries the whole job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries and timeouts&lt;/strong&gt;: per step, with backoff, plus heartbeats so a dead worker's task gets picked up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timers and signals&lt;/strong&gt;: a run can wait days for a human approval without a process sleeping that long.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A basic version is a &lt;code&gt;steps&lt;/code&gt; table keyed by &lt;code&gt;(run_id, step_id)&lt;/code&gt; and a helper that checks it before running each step. That's roughly how &lt;a href="https://www.dbos.dev/" rel="noopener noreferrer"&gt;DBOS&lt;/a&gt; works, as a library on Postgres. Once you need timers, signals, and deploys while runs are in flight, use an engine.&lt;/p&gt;

&lt;p&gt;A checkpoint is different from memory. Memory helps the agent answer the next question; a checkpoint says which steps of the current task have finished. It cannot freeze an in-flight model call or undo an email already sent. Retried actions therefore need idempotency or a way to reconcile their outcome.&lt;/p&gt;

&lt;p&gt;For the refund tool, that means passing an idempotency key derived from the run and the step, so a retry after a crash doesn't refund twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;step_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Payment APIs such as Stripe support this directly. For APIs that don't, check whether the action already happened before repeating it.&lt;/p&gt;

&lt;p&gt;A database can store memory, checkpoints, and product data. That does not make "database" a separate agent layer. It is infrastructure serving different responsibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product and proof
&lt;/h2&gt;

&lt;p&gt;The product adds authentication, tenancy, a UI, billing, and a way to reconnect to a run's events. A browser connection should not own the work, and it should not be required for the run to complete or be charged.&lt;/p&gt;

&lt;p&gt;The Copilot coding agent and Codex in the cloud are good examples: you assign a task, close the tab, and come back to a pull request. The run lives in the runtime, not in your browser. The UI just subscribes to its events, usually over &lt;a href="https://dev.to/posts/sse-vs-websockets-for-ai-chat/"&gt;SSE&lt;/a&gt;, and picks up where it left off on reconnect.&lt;/p&gt;

&lt;p&gt;Observability shows what happened across model calls, tools, and workers. In practice that's traces: one span per model call and tool call, with tokens, latency, and cost attached. OpenTelemetry has semantic conventions for generative AI, and tools such as &lt;a href="https://langfuse.com/" rel="noopener noreferrer"&gt;Langfuse&lt;/a&gt;, &lt;a href="https://www.langchain.com/langsmith" rel="noopener noreferrer"&gt;LangSmith&lt;/a&gt;, and &lt;a href="https://pydantic.dev/logfire" rel="noopener noreferrer"&gt;Logfire&lt;/a&gt; display them.&lt;/p&gt;

&lt;p&gt;Evals measure whether the agent completed useful tasks at acceptable quality and cost. For the support agent, that's a set of real conversations with expected outcomes: did it refund the right order, did it refuse to show another customer's data, did it escalate the $500 refund? Run them on every prompt or model change with &lt;a href="https://www.braintrust.dev/" rel="noopener noreferrer"&gt;Braintrust&lt;/a&gt;, &lt;a href="https://ai.pydantic.dev/evals/" rel="noopener noreferrer"&gt;Pydantic Evals&lt;/a&gt;, or a plain test suite.&lt;/p&gt;

&lt;p&gt;Tests and load measurements address operational reliability. Kill a worker mid-run. Deploy during a run. Send a thousand runs at once.&lt;/p&gt;

&lt;p&gt;A run can finish reliably and still give a poor answer. It can also give an excellent answer once and fail to survive the next deploy. Both problems need measuring.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>The silent promise that will kill your Node.js server</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Fri, 23 Jan 2026 06:35:01 +0000</pubDate>
      <link>https://dev.to/snikidev/the-silent-promise-that-will-kill-your-nodejs-server-1b8g</link>
      <guid>https://dev.to/snikidev/the-silent-promise-that-will-kill-your-nodejs-server-1b8g</guid>
      <description>&lt;p&gt;You've got a production server. It's been running fine for hours. Then suddenly it crashes. No warning, no graceful degradation. Just dead.&lt;/p&gt;

&lt;p&gt;The culprit? A single line of code that looks completely innocent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;saveMessageToDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;You're building a chat API. When a user sends a message, you want to stream the response back immediately while saving it to the database in the background. Classic fire-and-forget pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleChat&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&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;callAI&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="c1"&gt;// Fire and forget - don't wait for this&lt;/span&gt;
    &lt;span class="nf"&gt;saveMessageToDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;Looks fine. What could go wrong?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kill Shot
&lt;/h2&gt;

&lt;p&gt;Three hours later, the AI provider has a hiccup. The stream connection drops mid-transfer. &lt;code&gt;saveMessageToDatabase&lt;/code&gt; throws an error.&lt;/p&gt;

&lt;p&gt;Your server is dead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"PM2/Docker/Kubernetes will restart it!" Sure — and you've still lost every in-flight request, any unsaved state, and your users just saw an error. Prevention beats recovery.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why try-catch Didn't Save You
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;try-catch&lt;/code&gt; only catches errors from &lt;strong&gt;awaited&lt;/strong&gt; promises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ This try-catch is useless&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;saveMessageToDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Returns immediately&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Never reached&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;await&lt;/code&gt;, the function returns a Promise and exits the try block successfully. The actual error happens &lt;em&gt;later&lt;/em&gt;, asynchronously, after the try-catch is long gone.&lt;/p&gt;

&lt;p&gt;When that Promise eventually rejects with no handler attached, Node.js sees an "unhandled rejection" and, depending on your Node version, terminates the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;One character. Well, a few:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;saveMessageToDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. By attaching &lt;code&gt;.catch()&lt;/code&gt;, you tell Node "I know this might fail, and I'm handling it." The callback can be empty if you're logging inside the function. You just need to mark the Promise as "handled."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;For any fire-and-forget async operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Always attach .catch() to floating promises&lt;/span&gt;
&lt;span class="nf"&gt;doSomethingAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

&lt;span class="c1"&gt;// Or log the error if you want visibility&lt;/span&gt;
&lt;span class="nf"&gt;doSomethingAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Background task failed:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;try-catch&lt;/code&gt; and &lt;code&gt;async/await&lt;/code&gt; are not magic. They follow specific rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;try-catch&lt;/code&gt; only catches synchronous throws and awaited rejections&lt;/li&gt;
&lt;li&gt;A Promise without &lt;code&gt;await&lt;/code&gt; or &lt;code&gt;.catch()&lt;/code&gt; is a ticking time bomb&lt;/li&gt;
&lt;li&gt;"Fire and forget" still needs a &lt;code&gt;.catch()&lt;/code&gt; — you're forgetting the &lt;em&gt;result&lt;/em&gt;, not the &lt;em&gt;error&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your server will thank you.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>My go-to patterns for full-stack/frontend projects</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Thu, 15 Jan 2026 11:50:06 +0000</pubDate>
      <link>https://dev.to/snikidev/my-go-to-patterns-for-full-stackfrontend-projects-150a</link>
      <guid>https://dev.to/snikidev/my-go-to-patterns-for-full-stackfrontend-projects-150a</guid>
      <description>&lt;p&gt;After working on quite a few frontend and full-stack projects (mostly React + TypeScript + some flavour of server/backend), I kept coming back to the same handful of patterns. They bring structure, reduce mental overhead, and make the codebase feel maintainable even as it grows.&lt;/p&gt;

&lt;p&gt;These aren't revolutionary, but they're pragmatic choices that have worked well across different apps. Here's the current set I reach for almost every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. React Query + Query Key Factory pattern
&lt;/h2&gt;

&lt;p&gt;I end up using &lt;strong&gt;TanStack Query&lt;/strong&gt; (React Query) in nearly every project. To keep query keys consistent, readable, and refactor-friendly, I follow the &lt;a href="https://tkdodo.eu/blog/effective-react-query-keys#use-query-key-factories" rel="noopener noreferrer"&gt;&lt;strong&gt;query key factory&lt;/strong&gt;&lt;/a&gt; approach.&lt;/p&gt;

&lt;p&gt;Centralised factories make keys predictable and give great auto-completion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/query-keys.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookingKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;all&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;bookings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;detail&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="kr"&gt;string&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;bookingKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;upcoming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;patientId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;bookingKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upcoming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&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;Then in components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookingKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getBooking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingId&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;The same factory file becomes the single source of truth for &lt;strong&gt;invalidations&lt;/strong&gt; too. You can define an invalidation map and trigger &lt;code&gt;queryClient.invalidateQueries()&lt;/code&gt; calls from one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same file or a companion invalidations.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invalidateOnBookingChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&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;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookingKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Or more granular:&lt;/span&gt;
  &lt;span class="c1"&gt;// queryClient.invalidateQueries({ queryKey: bookingKeys.upcoming(...) });&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having invalidations centralised means you can easily track and manage data freshness across different parts of the app (dashboard, lists, details pages) without hunting through components or mutations. One change here ripples everywhere consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Server Actions / Server Functions
&lt;/h2&gt;

&lt;p&gt;I almost never write traditional API routes anymore. Instead, I use whatever flavour of server actions / functions the framework provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/13/app/api-reference/functions/server-actions" rel="noopener noreferrer"&gt;Next.js Server Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.astro.build/en/guides/actions/" rel="noopener noreferrer"&gt;Astro Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/start/latest/docs/framework/react/guide/server-functions" rel="noopener noreferrer"&gt;TanStack Start server functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are still essentially API-like endpoints under the hood—they can be called directly (e.g., via fetch or form POST), so you &lt;strong&gt;must protect them&lt;/strong&gt; with authentication, rate limiting, CSRF tokens (where applicable), and input validation just like any API.&lt;/p&gt;

&lt;p&gt;The big wins come from less boilerplate and more purpose-driven code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct function calls from client → no manual endpoint definitions&lt;/li&gt;
&lt;li&gt;Automatic type safety between client and server&lt;/li&gt;
&lt;li&gt;Easier error handling and revalidation&lt;/li&gt;
&lt;li&gt;Colocated logic (form → action → DB → response)&lt;/li&gt;
&lt;li&gt;Better integration with React's Suspense and transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not magic, it just removes ceremony while keeping security responsibilities intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Permission / Authorisation Management with CASL
&lt;/h2&gt;

&lt;p&gt;Most apps end up needing fine-grained permissions. I centralise that logic with &lt;a href="https://casl.js.org/v6/en/" rel="noopener noreferrer"&gt;CASL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Define abilities once (often user/session-based):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AbilityBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createMongoAbility&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@casl/ability&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defineAbilitiesFor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cannot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbilityBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createMongoAbility&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manage&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;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Booking&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;patientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&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;Booking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&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;Booking&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;patientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;cannot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&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;Booking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// explicit deny example&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;build&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;Then use in services with simple conditionals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookingService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;updateBooking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bookingId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Booking&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ability&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineAbilitiesFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&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;booking&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;getBookingDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// from queries&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ability&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not authorized to update this booking&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;// Proceed with update...&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateBooking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="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;Or inline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ability&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;can&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Booking&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;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// show sensitive data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeps permission logic declarative, testable, and out of business-flow code.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Lightweight Repository / Query pattern
&lt;/h2&gt;

&lt;p&gt;I keep a &lt;code&gt;queries/&lt;/code&gt; folder with plain async functions that are &lt;strong&gt;pure DB statements&lt;/strong&gt; and nothing else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// queries/bookings.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;getBookingDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Booking&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;// Drizzle/Prisma/etc. query only&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookings&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;limit&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="k"&gt;export&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;updateBooking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Booking&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;// Pure update, no side effects&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookings&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="nx"&gt;data&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;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&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;Strict rules for these functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only data access (i.e. SELECT, INSERT, UPDATE, DELETE)&lt;/li&gt;
&lt;li&gt;No business logic&lt;/li&gt;
&lt;li&gt;No authorisation checks&lt;/li&gt;
&lt;li&gt;No emails, queues, external calls, or side effects&lt;/li&gt;
&lt;li&gt;Reusable from any service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This thin &lt;strong&gt;Data Access Layer&lt;/strong&gt; makes swapping ORMs trivial (change only &lt;code&gt;queries/&lt;/code&gt; files) and keeps services focused on orchestration.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Optimistic Initial Data in React Query
&lt;/h2&gt;

&lt;p&gt;Pass SSR/SSG-fetched data as &lt;code&gt;initialData&lt;/code&gt; to avoid loading flashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookingKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcoming&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&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;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUpcomingBookings&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;initialData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;initialUpcoming&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SSR is non-negotiable today.&lt;/strong&gt; The era of plain Create React App SPAs is over. Even the React team officially deprecated CRA for new apps in early 2025 and &lt;a href="https://react.dev/learn/start-a-new-react-project" rel="noopener noreferrer"&gt;recommends frameworks instead&lt;/a&gt;. Modern file-based routing frameworks (Next.js, Tanstack Start, Astro, etc.) all have SSR/SSG built in. Leveraging that initial data improves perceived performance, reduces layout shifts, and gives users something meaningful on first paint. Why throw it away?&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Container / Presentational  aka Smart / Dumb Components pattern
&lt;/h2&gt;

&lt;p&gt;I still like &lt;a href="https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0" rel="noopener noreferrer"&gt;this classic separation&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Presentational&lt;/strong&gt; (dumb): only props, no hooks/state/fetching → pure UI, very easy to unit test and reason about&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container&lt;/strong&gt; (smart): handles data, state, orchestration, passes props down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Presentational – great for snapshot/visual testing&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;BookingListView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPageChange&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Skeleton&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BookingItem&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pagination&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onPageChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;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;// Container&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;BookingList&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBookings&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BookingListView&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...{&lt;/span&gt; &lt;span class="nx"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPageChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;setPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dumb components become trivial to test in isolation, since it requires no mocking data layers or auth.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Custom Hook pattern
&lt;/h2&gt;

&lt;p&gt;Any time you see a component growing fat with state + data fetching + pagination + error handling → extract it to a custom hook.&lt;/p&gt;

&lt;p&gt;Before: 50+ lines of useQuery/useState/session logic inside the component.&lt;/p&gt;

&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PatientDashboard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialUpcoming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialPast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialNext&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;upcoming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;past&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;nextAppointment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isLoadingUpcoming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;upcomingPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;setUpcomingPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDashboard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;initialUpcoming&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialPast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialNext&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-8"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NextAppointmentCard&lt;/span&gt; &lt;span class="na"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;nextAppointment&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoadingNextAppointment&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;BookingList&lt;/span&gt; &lt;span class="na"&gt;bookings&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;upcoming&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;upcomingPage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onPageChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setUpcomingPage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; If you see &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;useQuery&lt;/code&gt; (or similar) clustered together for one clear purpose → extract to a custom hook.&lt;/p&gt;

&lt;p&gt;Components stay focused on rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Strategy pattern (e.g. for third-party providers)
&lt;/h2&gt;

&lt;p&gt;When you might switch providers (Zoom → Google Meet → others), hide the implementation behind a unified interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// services/meeting.ts&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MeetingService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createMeeting&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;CreateMeetingInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// strategy selected by config / env&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;activeMeetingProvider&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="nx"&gt;input&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;Keeps services clean and future-proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;These patterns show up in almost every project I work on now. Together they deliver:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Readable, well-organised code&lt;/li&gt;
&lt;li&gt;Fewer weird logic bugs (everything has its place)&lt;/li&gt;
&lt;li&gt;Lower maintenance cost (easier tests, fewer surprises)&lt;/li&gt;
&lt;li&gt;Faster feature development (less time fighting structure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And most importantly, when everything follows clear conventions (and you document them in a single &lt;code&gt;ARCHITECTURE.md&lt;/code&gt; or similar) AI tools like Cursor or Copilot suddenly become much more accurate. They "get" the patterns right away and generate code that actually fits, without you prompting it 10 more times to put it all in the right folders, in the right format.&lt;/p&gt;

&lt;p&gt;All the classic engineering benefits, without overcomplicating things.&lt;/p&gt;

</description>
      <category>react</category>
      <category>patterns</category>
      <category>ai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I am an AI Engineer</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Mon, 20 Oct 2025 20:14:41 +0000</pubDate>
      <link>https://dev.to/snikidev/i-am-an-ai-engineer-1i95</link>
      <guid>https://dev.to/snikidev/i-am-an-ai-engineer-1i95</guid>
      <description>&lt;p&gt;Over the past year, I've completed five projects for medium and large established companies. Four were greenfield builds from scratch; the other involved integrating AI features into an existing platform.&lt;/p&gt;

&lt;p&gt;I'm fundamentally a &lt;strong&gt;software engineer&lt;/strong&gt; who's deeply curious about AI. I've gravitated here because it's fascinating.&lt;/p&gt;

&lt;p&gt;That said, if you need to pin me on the AI spectrum, &lt;strong&gt;AI Engineer&lt;/strong&gt; fits best. The field is still evolving, so titles are a bit of a mess. I mean, we're still debating software engineering titles after decades!&lt;/p&gt;

&lt;p&gt;In this post, I thought I'd tell you more about what I actually do day-to-day and what I believe makes me an AI Engineer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "AI Engineer"?
&lt;/h2&gt;

&lt;p&gt;While looking for gigs, I’ve seen a LOT of job titles. They're like a startup's org chart - fluid and confusing. Here's some that I saw and what job descriptions and requirements they had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Machine Learning Engineer&lt;/strong&gt;: Heavy on deploying ML models at scale, often with a focus on production pipelines (e.g., using Kubernetes for model serving). It's more ops-intensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Scientist&lt;/strong&gt;: This is a classic data role. They dive into data analysis, hypothesis testing, and storytelling with stats. They might build prototypes in Jupyter notebooks and prepare a presentation about findings, but they're not usually wiring it all into a full app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Engineer&lt;/strong&gt;: The pipeline maestro. They build ETL processes, manage data lakes on AWS S3 or GCP BigQuery, and ensure data flows cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep Learning Engineer&lt;/strong&gt;: Specialised in neural nets, often researching architectures like transformers or GANs. Think tweaking layers in PyTorch for cutting-edge performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Solutions Architect&lt;/strong&gt;: Basically a Solutions Architect (or DevOps?) that knows how to work with AI-specific tools. They design high-level systems, advise on cloud setups (e.g., Azure AI vs. Google Vertex), and align tech with business goals. Less coding, more diagramming and cloud infrastructure setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research Engineer&lt;/strong&gt;: Academia-adjacent, pushing boundaries with papers. They invent new algorithms, not integrate them into products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Algorithm Engineer&lt;/strong&gt;: I've seen this pop up too: focused on optimising core math, like efficient sorting for AI inference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI space is still nascent, so overlap is huge, and for some roles the title and description were completely different from what I have here.&lt;/p&gt;

&lt;p&gt;For me, &lt;strong&gt;AI Engineer&lt;/strong&gt; screams &lt;em&gt;hands-on builder&lt;/em&gt;. I'm not theorising in a lab or handling streams of petabytes of data, but rather I'm crafting end-to-end solutions that &lt;em&gt;work&lt;/em&gt; in the real world.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does an AI Engineer actually do?
&lt;/h2&gt;

&lt;p&gt;At my core, I'm solving &lt;strong&gt;software engineering problems&lt;/strong&gt; in an AI context. I take AI capabilities (like pre-trained models from Azure AI Foundry or OpenRouter) and make them work in production.&lt;/p&gt;

&lt;p&gt;Here's a breakdown of my toolkit and typical tasks:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Model setup and tweaking&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;On every project I had to choose a foundation model (e.g., OpenAI GPT-5, XAI Grok-4, Gemini-2.5 from Google AI, or Google Vertex).&lt;/p&gt;

&lt;p&gt;So far I never had to fine-tune a model for a production-ready project; most things were resolved via different prompting or extra context. I did fine-tune once in a hackathon we had at Sainsbury’s, and if I had to do it for a project, I would just use standard platform-provided tools.&lt;/p&gt;

&lt;p&gt;For example, Azure &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/concepts/fine-tuning-overview" rel="noopener noreferrer"&gt;has a guide&lt;/a&gt; on techniques, fine-tuning data formats, and tools it has to improve general LLMs. This would be my starting point.&lt;/p&gt;

&lt;p&gt;Another important and frequent task I found myself doing is testing and experimenting with prompts, handling token limits, and just making sure it behaves more or less deterministically.&lt;/p&gt;

&lt;p&gt;“Deterministic” as in “trying to control” or “predict” what the output will be so that we could prep our code/app/ui for this.&lt;/p&gt;

&lt;p&gt;Counterintuitive? Yes. But that’s what a lot of companies want, that’s what I’ve seen the most amongst requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Integration and infrastructure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;LLM calls are just API calls to someone else’s infrastructure. These calls can’t exist on their own; you have to embed them into a framework.&lt;/p&gt;

&lt;p&gt;Of course, some LLM- or AI agent-oriented frameworks have a built-in server, like &lt;a href="https://docs.langchain.com/langsmith/langgraph-server" rel="noopener noreferrer"&gt;LangGraph server&lt;/a&gt;, but not all, and quite often those are raw. I, personally, don’t like using those in production.&lt;/p&gt;

&lt;p&gt;So what I did was set up either a FastAPI server, where I could add auth middleware, database integrations, custom routing, or just use existing Next.js or Astro API routes as a back-end to expose AI models or agents, and custom SSE or WebSockets setup to handle real-time responses in the front-end.&lt;/p&gt;

&lt;p&gt;With that setup came lots of Docker containers. Lots and lots of Docker images… 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Frontend and user experience&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Why would you do all this infrastructure if no one will be able to interact with it? So we’re putting on a front-end engineer hat and handle all those streaming chat responses token-by-token for that snappy feel with React and TypeScript.&lt;/p&gt;

&lt;p&gt;Moreover, LLMs not only generate text but also images. And they accept images and docs. So I spent a lot of time debugging multimodal issues, figuring out why an LLM rejects an image, converting it to base64, and sending it correctly via REST API.&lt;/p&gt;

&lt;p&gt;And then doing the same vice versa.&lt;/p&gt;

&lt;p&gt;Also, lots of parsing and formatting tasks, because LLMs don’t like HTML. So if you scraped a page and want to feed it into an LLM, then you better convert it to markdown.&lt;/p&gt;

&lt;p&gt;Same goes for tables. LLMs don’t understand tables. Transforming them to an LLM-appropriate markdown or JSON is what I did.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;End-to-end problem solving&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Just like any software project, there’s a lot more to handle end-to-end, like observability and monitoring with tools like Azure Insights, Sentry, LangSmith; ensuring low latency; adding guardrails and AI configs so that AI doesn’t say anything inappropriate; API rate limiting; error handling (do you send an error as a chat message or show a toast with an error message?); handling secrets (because we’re not inexperienced vibe coders who push secrets to public repos, are we?)…&lt;/p&gt;

&lt;p&gt;Essentially, all of the classical software engineering, I’d say. Just with a bit of AI twist. 🤷&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’m not touching
&lt;/h2&gt;

&lt;p&gt;I'm not touching the "data territory" much. If you need me to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scour datasets, clean outliers with Pandas,&lt;/li&gt;
&lt;li&gt;Train from scratch on SageMaker,&lt;/li&gt;
&lt;li&gt;Evaluate with metrics like ROC-AUC or Weights &amp;amp; Biases dashboards,&lt;/li&gt;
&lt;li&gt;Iterate on hyperparameters...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...I &lt;em&gt;can&lt;/em&gt; do it (I know the theory from self-study and curiosity). But that's veering into Data Scientist/Engineer land, and if you’re not doing PoC you better find someone who’s better at this.&lt;/p&gt;

&lt;p&gt;My sweet spot is &lt;strong&gt;software issues&lt;/strong&gt;. Things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debugging why a model inference fails in production.&lt;/li&gt;
&lt;li&gt;Optimising API endpoints for 100ms response times.&lt;/li&gt;
&lt;li&gt;Ensuring cross-browser compatibility for AI-generated content.&lt;/li&gt;
&lt;li&gt;Scaling from prototype to handling 10k concurrent users.&lt;/li&gt;
&lt;li&gt;Integrating with legacy systems (e.g., bridging AI outputs to a monolithic Java backend).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  I’m a blue-collar worker of the AI World
&lt;/h2&gt;

&lt;p&gt;In essence, as an AI Engineer, I'm the bridge between fancy AI models and real apps. I handle the coding, setup, and fixes to make everything work smoothly in production. From my five projects with different companies, I've learned this role is all about practical building, not data crunching or research.&lt;/p&gt;

&lt;p&gt;No PhD needed. Just strong coding skills and a love for getting AI into users' hands.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>software</category>
      <category>career</category>
    </item>
    <item>
      <title>Understanding the Agentic Commerce Protocol: your guide to selling on ChatGPT</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Mon, 06 Oct 2025 09:13:12 +0000</pubDate>
      <link>https://dev.to/snikidev/understanding-the-agentic-commerce-protocol-your-guide-to-selling-on-chatgpt-2jeh</link>
      <guid>https://dev.to/snikidev/understanding-the-agentic-commerce-protocol-your-guide-to-selling-on-chatgpt-2jeh</guid>
      <description>&lt;p&gt;The e-commerce world just got a major upgrade with the launch of &lt;strong&gt;ChatGPT’s Instant Checkout&lt;/strong&gt;, powered by the &lt;strong&gt;Agentic Commerce Protocol (ACP)&lt;/strong&gt;. If you haven’t checked it out yet, go to &lt;a href="https://www.agenticcommerce.dev/" rel="noopener noreferrer"&gt;agenticcommerce.dev&lt;/a&gt; to see what it is. As a software engineer with zero experience in ACP let me tell you how I understand it (in simple terms).&lt;/p&gt;

&lt;p&gt;Because I know that starting tomorrow, companies will start posting job listings asking for “5+ years of ACP experience”. So, let’s figure out what it takes to get shops selling on ChatGPT.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Easy Way In: Etsy and Shopify Merchants
&lt;/h2&gt;

&lt;p&gt;If you run your shop on &lt;strong&gt;Etsy&lt;/strong&gt; or &lt;strong&gt;Shopify&lt;/strong&gt;, you’ve hit the jackpot. These platforms are already integrated with ChatGPT’s Instant Checkout. No extra coding, no fuss. Your products are automatically eligible to appear when users search for stuff like “large t-shirt under $40.” OpenAI has done the heavy lifting, so Etsy and Shopify merchants are live (or rolling out soon) with zero setup. Lucky you!&lt;/p&gt;

&lt;h2&gt;
  
  
  For Everyone Else: Here’s the Work
&lt;/h2&gt;

&lt;p&gt;If you aren’t on Etsy or Shopify, integrating with ChatGPT requires some effort. Don’t worry - it’s manageable, especially if you’ve built e-commerce APIs before. The process boils down to four steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registering&lt;/li&gt;
&lt;li&gt;Pushing products&lt;/li&gt;
&lt;li&gt;Setting up checkout endpoints&lt;/li&gt;
&lt;li&gt;Capturing payments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s walk through it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 0: Register as a Merchant
&lt;/h3&gt;

&lt;p&gt;First, you need to get your shop approved by OpenAI. Head to &lt;a href="https://chatgpt.com/merchants" rel="noopener noreferrer"&gt;chatgpt.com/merchants&lt;/a&gt; and fill out the merchant application form. It’s straightforward - provide details about the business, primary product categories, and whether you’re setting up a product feed or checkout integration. OpenAI reviews submissions on a rolling basis (expect 1-2 weeks). This step ensures only vetted merchants join the ecosystem, keeping things secure and trustworthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Push Products to ChatGPT’s Collection
&lt;/h3&gt;

&lt;p&gt;Once approved, you need to make ChatGPT aware of your products. This isn’t a public web search. ChatGPT uses a curated registry of approved ACP implementers to ensure compliance, accuracy, and trust. You’ll provide a &lt;strong&gt;product feed&lt;/strong&gt; in a structured JSON format, either via an endpoint (e.g., &lt;code&gt;/products/feed&lt;/code&gt;) or a file, following OpenAI’s &lt;a href="https://developers.openai.com/commerce/specs/feed" rel="noopener noreferrer"&gt;Product Feed Spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s what goes in the feed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product details&lt;/strong&gt;: ID, name, description, price, images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variants&lt;/strong&gt;: Size, color, etc. (e.g., “large blue t-shirt”).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability&lt;/strong&gt;: Stock status, shipping options.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can update the feed every 15 minutes to reflect price changes or stock levels or whatever. Right now, product rankings are organic, based purely on relevance to the user’s query (e.g., “large t-shirt under $40”). No paid promotions yet, but I bet OpenAI will add that soon. Capitalism always finds a way!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set Up Checkout Endpoints with ACP
&lt;/h3&gt;

&lt;p&gt;When a user finds a product in ChatGPT and clicks “Buy,” the magic happens through the &lt;strong&gt;Agentic Commerce Protocol&lt;/strong&gt;. Think of ACP as a standardized API with predictable inputs and outputs that any AI agent (like ChatGPT) can understand. It’s like USB-C for commerce - universally agreed-upon formats that make life easier. My iPhone’s Lightning cable still drives me nuts in a USB-C world! But I digress.&lt;/p&gt;

&lt;p&gt;Here’s how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Initiate Checkout&lt;/strong&gt;: ChatGPT sends a POST request to your &lt;code&gt;/checkout_sessions&lt;/code&gt; endpoint to start the process. The payload includes buyer info (name, email, maybe address—ChatGPT often pre-fills this to speed things up), items, and shipping details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate and Update&lt;/strong&gt;: As the user selects sizes, delivery options, or tweaks the cart, ChatGPT sends more requests to update the session (e.g., POST to &lt;code&gt;/checkout_sessions/{id}&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete Purchase&lt;/strong&gt;: When the user enters payment details (e.g., card or Apple Pay), ChatGPT sends a secure payment token to &lt;code&gt;/checkout_sessions/{id}/complete&lt;/code&gt;. You create the order and process the payment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These endpoints map to your client’s existing backend (e.g., inventory, tax calculations), so you’re just wrapping your current APIs in ACP’s format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Capture Payments and Orders
&lt;/h3&gt;

&lt;p&gt;Payments are handled via your existing provider, with &lt;strong&gt;Stripe&lt;/strong&gt; being the easiest (it supports ACP’s Shared Payment Token out of the box). ChatGPT passes a secure token (no raw card data, keeping it PCI-compliant), and you charge it using your usual payment flow. If you’re not on Stripe, you’ll need to align your provider with ACP’s Delegated Payments Spec. Fulfillment (shipping, returns) stays with your existing systems, and you update ChatGPT via webhooks (e.g., “order shipped”).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;ACP makes AI-driven commerce predictable and scalable. Unlike custom APIs that vary by platform, ACP’s standardized endpoints mean your shop can work with ChatGPT today and other AI agents tomorrow. It’s a win for developers like me who love consistency. Plus, you stay in control as the merchant of record, owning the customer relationship without ChatGPT getting in the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Here’s the plan if you already want to get your e-shop on ChatGPT:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Apply&lt;/strong&gt;: Submit the merchant form at &lt;a href="https://chatgpt.com/merchants" rel="noopener noreferrer"&gt;chatgpt.com/merchants&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Feed&lt;/strong&gt;: Create a product feed endpoint or file with your client’s product data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Endpoints&lt;/strong&gt;: Implement ACP’s &lt;code&gt;/checkout_sessions&lt;/code&gt; and related endpoints, mapping to your existing APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test and Launch&lt;/strong&gt;: Use OpenAI’s sandbox to test, get certified, and go live.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is just a generic overview guide. Didn't want to get into the weeds too much. But if you have any questions or want some assistance making this work for your shop - get in touch, I'll be happy to help. 🙌&lt;/p&gt;

</description>
      <category>acp</category>
      <category>chatgpt</category>
      <category>ai</category>
      <category>openai</category>
    </item>
    <item>
      <title>From 60 to 25 hours: practical tips for effective AI-assisted development</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Thu, 15 May 2025 21:06:31 +0000</pubDate>
      <link>https://dev.to/snikidev/from-60-to-25-hours-practical-tips-for-effective-ai-assisted-development-5e3n</link>
      <guid>https://dev.to/snikidev/from-60-to-25-hours-practical-tips-for-effective-ai-assisted-development-5e3n</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Cut development time by 60% (from 60 to 25 hours) using AI tools effectively&lt;/li&gt;
&lt;li&gt;Choose your tech stack yourself - don't let AI make these decisions&lt;/li&gt;
&lt;li&gt;Set up your project structure manually - AI tends to use outdated packages and weird folder structures&lt;/li&gt;
&lt;li&gt;Break down tasks into small, specific requests for AI - don't ask it to build entire features at once&lt;/li&gt;
&lt;li&gt;Provide relevant documentation snippets to help AI understand framework-specific requirements&lt;/li&gt;
&lt;li&gt;Keep context minimal - attach only relevant files, not entire codebases&lt;/li&gt;
&lt;li&gt;Start fresh sessions for new features to avoid recurring patterns&lt;/li&gt;
&lt;li&gt;Always refactor AI-generated code - it tends to be messy and needs cleanup&lt;/li&gt;
&lt;li&gt;Try different AI models to find what works best for you (Claude, Gemini-2.5, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Bottom line: Use AI to handle tedious tasks while maintaining control over architecture and design decisions&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I was doing one of the projects, I was using Copilot and I noticed that I finished this project way faster than I ever finished a similar project in the past. 25 vs 60 hours. AMAZING, no?! 🔥&lt;/p&gt;

&lt;p&gt;I was like turbo-charged! I was able to do code as fast as my ADHD mind was thinking about different aspects of it: layout, styling, state management, comms with back-end API, DB schema and relationships, error handling…&lt;/p&gt;

&lt;p&gt;All of this I was able to keep in my head AND be productive! Which is usually not the case, and I end up with a bunch of &lt;code&gt;TODO&lt;/code&gt; comments and no code (at first).&lt;/p&gt;

&lt;p&gt;And then refactoring all that multiple times using a different design pattern!&lt;/p&gt;

&lt;p&gt;So this blog post is an instruction on how to make yourself productive with AI copilot/cursor/whatever, instead of fighting with LLM, swearing at it and rejecting all of its work.&lt;/p&gt;

&lt;h2&gt;
  
  
  You don't need AI to select the stack
&lt;/h2&gt;

&lt;p&gt;Maybe a little bit of DeepSearch.&lt;/p&gt;

&lt;p&gt;When starting you should already know what tech stack and patterns you're going to use.&lt;/p&gt;

&lt;p&gt;The only reasons you should use AI at the start of the project if you wanna compare frameworks for your particular use case, e.g. zustand vs jotai.&lt;/p&gt;

&lt;p&gt;In my case I knew I had to use Astro, because it was a front-end project with only some back-end required. It was perfect.&lt;/p&gt;

&lt;p&gt;This was a "live app", i.e. all users needed to be notified of the changes, so obviously I had to use WebSockets for that.&lt;/p&gt;

&lt;p&gt;And then data was of predictable shape, had lots of relationships, so it meant SQL, and PostgreSQL in particular. LOVE it! I'm even subscribed to &lt;a href="https://postgresweekly.com/" rel="noopener noreferrer"&gt;Postgres Weekly&lt;/a&gt;! 🐘&lt;/p&gt;

&lt;p&gt;I also wanted &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt; to manage all the migrations and CRUD operations.&lt;/p&gt;

&lt;p&gt;I knew that multiple components in React would need to interact with the same state, so I went with &lt;a href="https://effector.dev/" rel="noopener noreferrer"&gt;effector&lt;/a&gt;. It's my favourite because of the &lt;code&gt;sample&lt;/code&gt; , you can do some crazy logic with it, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Watch for submitForm event, when it gets triggered ...&lt;/span&gt;
  &lt;span class="na"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;submitForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ... take the $userName store's state ...&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$userName&lt;/span&gt;
  &lt;span class="c1"&gt;// ... and transform the state (`name`) AND the parameters from the submitForm function (`password`)&lt;/span&gt;
  &lt;span class="c1"&gt;// into parameters that signInFx would accept...&lt;/span&gt;
  &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&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;password&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="c1"&gt;// ... and then call the function with signInFx({ name, password })&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signInFx&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;Isn't that awesome?! AND it all lives outside of your UI logic. SO clean!&lt;/p&gt;

&lt;p&gt;I could've also gone with &lt;a href="https://github.com/nanostores/nanostores" rel="noopener noreferrer"&gt;nanostores&lt;/a&gt;, that Astro is promoting.&lt;/p&gt;

&lt;p&gt;And, obviously, &lt;a href="https://ui.shadcn.com/" rel="noopener noreferrer"&gt;shadcn&lt;/a&gt; for UI components. Is there any other option?&lt;/p&gt;

&lt;p&gt;Ok, I like &lt;a href="https://www.heroui.com/" rel="noopener noreferrer"&gt;HeroUI&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;The rest feel raw or just weird.&lt;/p&gt;

&lt;p&gt;To sum up:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't let AI choose your tech stack for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Init the project
&lt;/h2&gt;

&lt;p&gt;Don't ask AI to do it. It's just gonna do the shit job, use outdated packages, and setup weird-ass folder structure.&lt;/p&gt;

&lt;p&gt;Set it up yourself. The way YOU like. The way YOU are used to work.&lt;/p&gt;

&lt;p&gt;So I did. Just followed the Astro setup on shadcn basically&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-astro@latest astro-app  &lt;span class="nt"&gt;--template&lt;/span&gt; with-tailwindcss &lt;span class="nt"&gt;--install&lt;/span&gt; &lt;span class="nt"&gt;--add&lt;/span&gt; react &lt;span class="nt"&gt;--git&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I needed a dashboard. So I just took blocks from shadcn and adjusted items to just the ones I need.&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%2Fwww.sniki.dev%2F.netlify%2Fimages%3Furl%3D_astro%252Fshadcn-blocks.BllGEqZf.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%2Fwww.sniki.dev%2F.netlify%2Fimages%3Furl%3D_astro%252Fshadcn-blocks.BllGEqZf.png" alt="shadcn-blocks.png" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No AI. You're gonna spend more time explaining AI what you want.&lt;/p&gt;

&lt;p&gt;Same with things like &lt;code&gt;Dockerfile&lt;/code&gt; . There are templates, you can just copy and tweak it. Do it yourself, it's quicker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Release the &lt;del&gt;Kraken&lt;/del&gt; AI!
&lt;/h2&gt;

&lt;p&gt;Once you have the setup ready, it's time to outsource some work.&lt;/p&gt;

&lt;p&gt;The only way I felt productive and content with what AI was giving me, is when I described step by step how I'd do it myself.&lt;/p&gt;

&lt;p&gt;So for the form I went&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 AddItem form React component using Tanstack Form.
It should have 3 inputs: customer (text, required), order number (text, required), vehicle (select field with Van and Truck as options, required)
It should have 2 buttons add (submit) and reset (nullify all the values in all the fields).
onSubmit create a function with empty body. We'll implement it later.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Keep it short
&lt;/h2&gt;

&lt;p&gt;Notice how I didn't let it go further. If I didn't it might've went ahead and created some weird function, and an &lt;code&gt;/api/form&lt;/code&gt; route, it would've made all decisions for me.&lt;/p&gt;

&lt;p&gt;And I didn't want it to. I wanted it to make it the way I think is right.&lt;/p&gt;

&lt;p&gt;So that's one of the key secrets to make AI productive.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stop Copilot from going. Don't ask it to do the entire project. Just ask it do a component.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then an endpoint. Then a util function. Then ask it to put it all together. AI is like children. Asking it too many things at once, will make it ignore your important details that you gave it.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's never OpenAI
&lt;/h2&gt;

&lt;p&gt;For programming, I noticed, I never use any models released by OpenAI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's always Claude, either Sonnet 3.5 or 3.7 or, now, Gemini-2.5.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Gemini-2.5 is actually &lt;a href="https://web.lmarena.ai/leaderboard" rel="noopener noreferrer"&gt;considered the best as of today&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Although GPT-4.1, they said, should be good at coding. And vibing…? Or is that GPT-4.5?&lt;/p&gt;

&lt;p&gt;Anyway, switch some models up, see which ones vibe the best with you.&lt;/p&gt;

&lt;p&gt;It's all about vibes these days, isn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Help it see what you see
&lt;/h2&gt;

&lt;p&gt;I've noticed that Cursor is doing web search. But Copilot isn't yet. So the model will not know everything, especially if it's new or rarely used.&lt;/p&gt;

&lt;p&gt;Like Astro. All models are really shit at Astro. Try asking it create an action with a certain input and output. Any one of them is gonna do such a shit job, that you'd start doubting the rest of its abilities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Insert snippets from Get Started or Example pages from the docs. This context would really help AI in generating the code that you have in your head and not a random shit from the internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or you can setup &lt;a href="https://github.com/RohitKrish46/docs-mcp-server" rel="noopener noreferrer"&gt;MCP for yourself&lt;/a&gt; that would do the docs search for you, e.g.&lt;/p&gt;

&lt;p&gt;I haven't done that for myself yet tho.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't overdo the context
&lt;/h2&gt;

&lt;p&gt;You know how most (?) AI code editors let you attach files for context? This is an awesome feature!&lt;/p&gt;

&lt;p&gt;They also let you attach code bases. This is dumb.&lt;/p&gt;

&lt;p&gt;Remember, LLMs are like children: you give them too much, they'll forget most of it, you'll get garbage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Attach only the relevant files for the ask.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't let it see all of it. In fact...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Close the session once you done the feature/ask. Let it start over.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That way you don't get it stuck in a recurring shit-generating pattern. Let it be a gold fish with a memory limited to a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactor
&lt;/h2&gt;

&lt;p&gt;Once the feature is done, you won't be able to look at it, it's gonna be heinous.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ABR - Always Be Refactoring.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The generated code will be all over the place, it's gonna have weird if/else logic, triple and quadruple ternary operators, repeated code… Complete unreadable mess!&lt;/p&gt;

&lt;p&gt;So you need to go through it with a fresh eye, move some functions out, change the logic… Refactor!&lt;/p&gt;

&lt;h2&gt;
  
  
  Notice your wins
&lt;/h2&gt;

&lt;p&gt;If you don't notice any significant wins when working with AI help, then you're either a) been working with AI for ages now and used to it (not the case today, I'd say) or b) it didn't work for you, fix it.&lt;/p&gt;

&lt;p&gt;For me I noticed these wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I switched from React provider pattern (I forgot I was using Astro) to state management in "vanilla JS" way in under 2 mins;&lt;/li&gt;
&lt;li&gt;I quickly scaffolded a gigantic switch statement for handling events for WebSockets on the server and associated actions it needs to take, e.g. call to DB, filter and map things out, handle errors, etc.;&lt;/li&gt;
&lt;li&gt;I created effector stores and events and connected all of them in multiple &lt;code&gt;sample&lt;/code&gt;s in under 5 mins with minor refactors, a thing I was always dreading to do even considering my admiration for &lt;code&gt;sample&lt;/code&gt; ;&lt;/li&gt;
&lt;li&gt;I went from using &lt;code&gt;react-use-websocket&lt;/code&gt; hook, to using &lt;code&gt;socket.io&lt;/code&gt; after realising that islands architecture wouldn't work with the hook, to native implementation of WebSockets, after learning that socket.io has a compatibility issue with my server;&lt;/li&gt;
&lt;li&gt;I scaffolded (and later refactored into a decent code) forms with validation and styling with a UI and form libraries of my choice in 5 mins. And I hate doing forms. This is the best win of all.&lt;/li&gt;
&lt;li&gt;I then refactored, cleaned and made all of my components, util functions, API endpoints, etc. small, testable, humanly readable, and DRY in less than 10 mins.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Most important win is, I didn't lose myself, the coder, the programmer that likes to write code, create and make shit work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I just outsourced the annoying and tedious bits that made my job soulless and energy draining.&lt;/p&gt;

&lt;p&gt;That's what AI is good for.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>githubcopilot</category>
      <category>productivity</category>
      <category>llm</category>
    </item>
    <item>
      <title>Creating a simple RAG in python with AzureOpenAI and LlamaIndex</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Thu, 24 Oct 2024 12:33:53 +0000</pubDate>
      <link>https://dev.to/snikidev/creating-a-simple-rag-in-python-with-azureopenai-and-llamaindex-3loc</link>
      <guid>https://dev.to/snikidev/creating-a-simple-rag-in-python-with-azureopenai-and-llamaindex-3loc</guid>
      <description>&lt;p&gt;During a recent stream, I explored the process of ingesting a PDF using LlamaIndex and AzureOpenAI. This blog post will guide you through the steps to accomplish this task.&lt;/p&gt;

&lt;p&gt;The objective was straightforward: answer questions based on information contained in PDF files stored in a folder. Here's the process we'll follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Embed the PDF data: Convert PDFs into a format comprehensible for AI&lt;/li&gt;
&lt;li&gt;Initialise AzureOpenAI and provide it with the embedded data&lt;/li&gt;
&lt;li&gt;Ask a question and analyse the response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the &lt;a href="https://docs.llamaindex.ai/en/stable/examples/customization/llms/AzureOpenAI/" rel="noopener noreferrer"&gt;LlamaIndex documentation&lt;/a&gt; provides an excellent guide, I've included some visual aids to enhance clarity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Environment
&lt;/h2&gt;

&lt;p&gt;First, let's install the necessary packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;dotenv llama-index llama-index-llms-azure-openai llama-index-embeddings-azure-openai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that AzureOpenAI is not included in the &lt;code&gt;llama-index&lt;/code&gt; package and must be installed separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring AzureOpenAI
&lt;/h2&gt;

&lt;p&gt;Navigate to Azure AI Studio and deploy your chosen model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fwww.railyard.works%2F_astro%2Fdeploy-llm.Ds-yoiUk_Z1IDkHp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fwww.railyard.works%2F_astro%2Fdeploy-llm.Ds-yoiUk_Z1IDkHp.webp" alt="Deploying LLM Model" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once deployed, configure LlamaIndex to use it with the following settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AzureOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AZURE_OPENAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;azure_endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AZURE_OPENAI_ENDPOINT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-05-01-preview&lt;/span&gt;&lt;span class="sh"&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;BUT.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Azure openAI resources unfortunately differ from standard openAI resources as you can't generate embeddings unless you use an embedding model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means that we need a separate embedding model for generating embeddings. Let’s do that.&lt;/p&gt;

&lt;p&gt;Deploy an embedding model that starts with &lt;code&gt;text-embedding-*&lt;/code&gt;, since in this case we're working exclusively with text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fwww.railyard.works%2F_astro%2Fdeploy-embedding-model.9p_PeLib_2dNNHH.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fwww.railyard.works%2F_astro%2Fdeploy-embedding-model.9p_PeLib_2dNNHH.webp" alt="Deploying Embedding Model" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure LlamaIndex to use the embedding model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AzureOpenAIEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deployment_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AZURE_OPENAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;azure_endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AZURE_OPENAI_EMBEDDING_ENDPOINT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2023-05-15&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing the RAG System
&lt;/h2&gt;

&lt;p&gt;With the setup complete, let's load PDFs into the data folder and query the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Can we advertise online gambling to under 18 year olds?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the script in our virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python ./src/main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we get a response of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No, advertising online gambling to under 18 year olds is not permitted. Marketing communications must not exploit the vulnerabilities of this age group, and any advertisements that feature under-18s or are directed at them are likely to be considered irresponsible and in breach of established rules.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;p&gt;We can make it better by embedding the data once and storing it in a database like Azure CosmosDB, which is a MongoDB at its core. This means we get rid of repetitive embedding overhead every time we run the script, allowing the LLM to access pre-embedded data directly from the database. I'll cover this in my future posts.&lt;/p&gt;

&lt;p&gt;And &lt;a href="https://github.com/snikidev/llamaindex-python-rag" rel="noopener noreferrer"&gt;here is the repo&lt;/a&gt; with this and any future code.&lt;/p&gt;

</description>
      <category>llamaindex</category>
      <category>rag</category>
      <category>azure</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I cut the deployment time of an Astro (Node.js) site on Azure from 1 hour to ~3 mins</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Mon, 23 Sep 2024 13:54:04 +0000</pubDate>
      <link>https://dev.to/snikidev/how-i-cut-the-deployment-time-of-an-astro-nodejs-site-on-azure-from-1-hour-to-3-mins-1c73</link>
      <guid>https://dev.to/snikidev/how-i-cut-the-deployment-time-of-an-astro-nodejs-site-on-azure-from-1-hour-to-3-mins-1c73</guid>
      <description>&lt;p&gt;Before:&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%2Frailyard.works%2Fblog_posts%2Fdeployment-on-azure-before.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%2Frailyard.works%2Fblog_posts%2Fdeployment-on-azure-before.png" alt="Deployment on azure before" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After:&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%2Frailyard.works%2Fblog_posts%2Fdeployment-on-azure-after.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%2Frailyard.works%2Fblog_posts%2Fdeployment-on-azure-after.png" alt="Deployment on Azure after" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The way Azure sets it up for you when you go through the steps of deploying a new Azure Web App service is objectively horrible. This is what Azure pushes to your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy&lt;/span&gt;
&lt;span class="c1"&gt;# More GitHub Actions for Azure: https://github.com/Azure/actions&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and deploy Node.js app to Azure Web App&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Node.js version&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20.x"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pnpm/action-setup@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install, build, and test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install&lt;/span&gt;
          &lt;span class="s"&gt;npm run build --if-present&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Zip artifact for deployment&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zip release.zip ./* -r&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload artifact for deployment job&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-app&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release.zip&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Production"&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy-to-webapp.outputs.webapp-url }}&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt; &lt;span class="c1"&gt;#This is required for requesting the JWT&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download artifact from build job&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-app&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unzip artifact for deployment&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unzip release.zip&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Azure&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZUREAPPSERVICE_CLIENTID }}&lt;/span&gt;
          &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZUREAPPSERVICE_TENANTID }}&lt;/span&gt;
          &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Azure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Web&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;App"&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-to-webapp&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/webapps-deploy@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app-name"&lt;/span&gt;
          &lt;span class="na"&gt;slot-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Production"&lt;/span&gt;
          &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what’s happening here is that we&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the project in GitHub actions&lt;/li&gt;
&lt;li&gt;Zip it&lt;/li&gt;
&lt;li&gt;Send it to Azure&lt;/li&gt;
&lt;li&gt;Unzip it&lt;/li&gt;
&lt;li&gt;Run it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem is that we’re zipping it together with &lt;code&gt;node_modules&lt;/code&gt; folder. So sending it and unzipping it takes a LOT of time. 🤯&lt;/p&gt;

&lt;p&gt;I’m not sure why they recommend it this way, but after reading a bunch of poorly worded and outdated Azure guides and some short StackOverflow posts, I’ve figured out the way a Node.js app should be deployed.&lt;/p&gt;

&lt;p&gt;Essentially we need to only send the code from GitHub to Azure and build right there. For that you don’t need to do any post-install scripts or anything, Azure App Service does it for you, &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/configure-language-nodejs?pivots=platform-linux#customize-build-automation" rel="noopener noreferrer"&gt;here are the steps&lt;/a&gt; that it goes through as soon as the code reaches the service.&lt;/p&gt;

&lt;p&gt;But it doesn’t do that by default, so you need to enable it. You do that by setting the environment variable &lt;code&gt;SCM_DO_BUILD_DURING_DEPLOYMENT&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in &lt;strong&gt;Settings &amp;gt; Environment variables&lt;/strong&gt; in Azure Portal.&lt;/p&gt;

&lt;p&gt;Once we’ve done that, we now need to remove the build step from our GitHub Actions workflow and we’re done. Here is how the template looks like now. 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy&lt;/span&gt;
&lt;span class="c1"&gt;# More GitHub Actions for Azure: https://github.com/Azure/actions&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and deploy Node.js app to Azure Web App&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prepare&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Zip artifact for deployment&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zip release.zip ./* -r&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload artifact for deployment job&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-app&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release.zip&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prepare&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Production"&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy-to-webapp.outputs.webapp-url }}&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt; &lt;span class="c1"&gt;#This is required for requesting the JWT&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download artifact from build job&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-app&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Unzip artifact for deployment&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unzip release.zip&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Azure&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZUREAPPSERVICE_CLIENTID }}&lt;/span&gt;
          &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZUREAPPSERVICE_TENANTID }}&lt;/span&gt;
          &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Azure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Web&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;App"&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-to-webapp&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/webapps-deploy@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;app-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web-app"&lt;/span&gt;
          &lt;span class="na"&gt;slot-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Production"&lt;/span&gt;
          &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also a few “gotchas” I’ve noticed when deploying to Azure Web App service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Forget yarn or pnpm. npm only. There is no way you can get those into App Service environment. And if there is, I really don’t think it’s worth the effort. Azure is using &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/resources-kudu" rel="noopener noreferrer"&gt;Kudu&lt;/a&gt; so you better know its ins and outs, I feel like, if you decide to install additional packages there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HOST=0.0.0.0 PORT=8080&lt;/code&gt; before your start script, which in my (Astro) case was &lt;code&gt;node ./dist/server/entry.mjs&lt;/code&gt; . You can’t pick the port. I tried. Didn’t work.&lt;/li&gt;
&lt;li&gt;Node version &lt;code&gt;20.14&lt;/code&gt; is what it’s currently running in Azure as of the moment of writing this post. It was hard to find, they don’t print it in the dropdown when you select the environment when deploying the App Service. I got a few errors because the Node version wasn’t what my app was expecting 🤷‍♂️&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And most important tip: &lt;strong&gt;upgrade your service to paid.&lt;/strong&gt; The whole build and deploy process is unbearably slow on a free plan. But, frankly, that’s not an Azure specific issue, that’s most of PaaS nowadays, isn’t it?&lt;/p&gt;

&lt;p&gt;Hope that helps! 😎&lt;/p&gt;

</description>
      <category>azure</category>
      <category>node</category>
      <category>githubactions</category>
      <category>astro</category>
    </item>
    <item>
      <title>Astro.js as an alternative to Next.js: pushing the limits</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Wed, 17 Apr 2024 10:02:29 +0000</pubDate>
      <link>https://dev.to/snikidev/astrojs-as-an-alternative-to-nextjs-pushing-the-limits-30ga</link>
      <guid>https://dev.to/snikidev/astrojs-as-an-alternative-to-nextjs-pushing-the-limits-30ga</guid>
      <description>&lt;p&gt;I’m tired of Next. I want to try something fresh. So I thought this was the time I look into other frameworks. Where would I go? Nuxt? Remix? Nah…&lt;/p&gt;

&lt;p&gt;Astro! Let’s test it and see if it can handle modern project requirements.&lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/snikidev/astro-limitless" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;, follow along!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why am I doing this?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Next.js is complicated. But some of the best websites out there use a plain, vanilla JS. We don’t need anything fancy to build great products. I mean, we could use a bit of help from a simple (!!) framework that gets out of your way and lets you deliver. And I want to find that framework.&lt;/li&gt;
&lt;li&gt;Next.js v13 was a &lt;del&gt;shit&lt;/del&gt; shift. In v13 Next.js introduced a mentality shift with RSC on how we develop an app, and since I have to shift my mentality, I’d like to shift it in all possible directions. Open my mind a little bit more than I have to.&lt;/li&gt;
&lt;li&gt;Vercel is not your friend. And Next.js is THEIR product. Sooner or later I feel like we’ll be shifting our mentality to adjust to how Vercel wants us to do things. I don’t wish to be locked in like that.&lt;/li&gt;
&lt;li&gt;I’m just tired of Next.js. Every company makes me work with Next.js. Always Next. I don’t really mind, but I miss those times when I was talking about Next as a “new framework” that we “should look into adopting”! I and others like me did a very good job. Well done, folks! But now I want to mix things up a bit, time to try something fresh! Again! 😅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I guess you could say that Next.js is the reason I’m looking into Astro. 😁&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Astro.js
&lt;/h2&gt;

&lt;p&gt;I’ve been ignoring it like I do with Remix and Svelte and a bunch of other frameworks. However, I’ve learned that Astro is different, and I’m gonna talk about how it’s different later in the post.&lt;/p&gt;

&lt;p&gt;First, I needed to define a set of criteria with which I could judge its usefulness. How do I identify whether I should choose it (or recommend it) for a new project?&lt;/p&gt;

&lt;p&gt;So I started thinking and reflecting on all the companies and all the projects I ever I worked with. What was I doing there? What requirements they had in common? What kind of work they were assigning to me?&lt;/p&gt;

&lt;p&gt;And so I defined a list of such things, things that I was doing on EVERY job or contract. This is what everyone wanted me to do from tiny startups to humongous enterprises.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Has React &amp;amp; TypeScript: because I’ve rarely seen a company that wants Vue or plain JavaScript. Everyone wants React and it has to be with TypeScript.&lt;/li&gt;
&lt;li&gt;Static pages: Fetch content from headless CMS at build time, create the HTML page and host it somewhere on a simple host.&lt;/li&gt;
&lt;li&gt;Global state management shared between components without prop drilling: Because there’s always at least a menu of some sort that you need to control from 3 different places.&lt;/li&gt;
&lt;li&gt;Custom theme (global): everyone has its own colours, fonts, etc. No hardcoding and repeating/copy+pasting those HEX values all over the CSS files!&lt;/li&gt;
&lt;li&gt;Scoped CSS (e.g. &lt;code&gt;.module.css&lt;/code&gt;): no one wants to load ALL of the CSS for every page and then use 1/100th of it.&lt;/li&gt;
&lt;li&gt;SSR rendered content that needs to be changed or updated from the client (e.g. using &lt;code&gt;@tanstack/query&lt;/code&gt;), e.g. you know that sidebar with filters on a page that lists a couple of dozens of products?&lt;/li&gt;
&lt;li&gt;User Authentication with multiple hidden pages: we don’t want to need to auth the user only on the client side. Let’s redirect the user on the server, i.e. BEFORE they get to the protected page.&lt;/li&gt;
&lt;li&gt;Localisation and internationalisation: I’ve done a bunch of projects that needed two or more languages. I’m doing one now that requires more than eight languages and more than ten geos/territories!&lt;/li&gt;
&lt;li&gt;Embedding Google Analytics and other tracking tools: because if it’s a client-facing site we &lt;em&gt;gots&lt;/em&gt; to know about our visitors, right?&lt;/li&gt;
&lt;li&gt;Design System in a Storybook: every company I joined (and I do mean EVERY) wanted their React components displayed neatly in a Storybook for all kinds of stakeholders to see/play with.&lt;/li&gt;
&lt;li&gt;Testing, incl. unit, integration and visual (i.e. snapshot testing).&lt;/li&gt;
&lt;li&gt;Server API function (or action) where secret keys can be used.&lt;/li&gt;
&lt;li&gt;Incremental Static Regeneration (ISR), for cases when we have hundreds or even thousands of pages.&lt;/li&gt;
&lt;li&gt;Deploy to a custom Docker-based environment somewhere to AWS Fargate, because not every company out there wants to use Vercel, Railway, and other “cool” and “hip” platforms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I did this silly “Frankenstein” project with those requirements. Let’s talk about the results!&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Astro is EXPLICIT
&lt;/h3&gt;

&lt;p&gt;The main conclusion that I got out of it is that Astro.js is like putting your project on a diet. On a JavaScript diet.&lt;/p&gt;

&lt;p&gt;The thing about Astro is that it promotes the ****“reduced JavaScript” approach. That’s a whole other kind of mentality shift than what I initially was hoping for.&lt;/p&gt;

&lt;p&gt;So it doesn’t render JavaScript unless you EXPLICITLY tell it to.&lt;/p&gt;

&lt;p&gt;“Explicit” is the word I’d associate with Astro. While Next hides a lot of implementation details, Astro does nothing of such a thing and leaves implementation to us, developers.&lt;/p&gt;

&lt;p&gt;If you want some interactivity with JavaScript on the front-end - you need to write it, e.g. &lt;code&gt;addListener&lt;/code&gt;s in the &lt;code&gt;script&lt;/code&gt; tags. Otherwise, Astro ships nothing to the browser.&lt;/p&gt;

&lt;p&gt;Before I wasn’t really thinking explicitly much about how much JavaScript is and isn’t used in the browser. Most of the time, I just let frameworks figure this out for me, and only when we were starting to have issues with performance, etc. only then I start looking “under the hood” and optimising.&lt;/p&gt;

&lt;p&gt;All in all, I’m happy with this approach. I kinda like it. Cognitively it feels easier to work this way.&lt;/p&gt;

&lt;p&gt;This leads to the next point.&lt;/p&gt;

&lt;h3&gt;
  
  
  React is just a sprinkle on top
&lt;/h3&gt;

&lt;p&gt;Astro allows you to use ALL sorts of UI frameworks. Including React. BUT!&lt;/p&gt;

&lt;p&gt;React is a UI library. And Astro is using it as such. RSC is not a thing there (yet?). So if you’re writing a &lt;code&gt;.tsx&lt;/code&gt;/ &lt;code&gt;.jsx&lt;/code&gt; file, you can be 100% sure it’s going to be rendered, live and work only in the browser.&lt;/p&gt;

&lt;p&gt;Yes, you can certainly feed it server-fetched data, so the user doesn’t see the loader for too long. But it still remains a client only component.&lt;/p&gt;

&lt;p&gt;For that reason, you can’t really use React ONLY or go too deep component-wise. They’re all end up on the client.&lt;/p&gt;

&lt;p&gt;Do you want that? Is that a good thing? It depends on the project.&lt;/p&gt;

&lt;p&gt;If you need to render things on the server first, say, for SEO reasons, and make it interactive, then I’d assume you’d need to have a conditional variable, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasBeenChangedByUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hasBeenChangedByUser&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AstroComponent&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And both &lt;code&gt;&amp;lt;AstroComponent /&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;ReactComponent /&amp;gt;&lt;/code&gt;, in this case, would look very similar. You could do a sharable code though, because &lt;a href="https://docs.astro.build/en/concepts/islands/#creating-an-island" rel="noopener noreferrer"&gt;Astro still allows rendering &lt;code&gt;.tsx&lt;/code&gt; component on the server&lt;/a&gt;, it just strips them of all the JavaScript, making them redundant for React purpose but great for layout, etc.&lt;/p&gt;

&lt;p&gt;In my experience, there weren’t many cases when we’d had to create something drastic like that, rather a healthy balance of server vs client. So this “React == UI only” approach doesn’t really bother me. I can render what I need on the server inside &lt;code&gt;.astro&lt;/code&gt; component, and components that need to be overly interactive, they’re client in any framework anyway, because interactivity == JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  No providers!
&lt;/h3&gt;

&lt;p&gt;With Astro, we have two types of files: &lt;code&gt;.astro&lt;/code&gt; and &lt;code&gt;.tsx&lt;/code&gt;. Guess what &lt;code&gt;.astro&lt;/code&gt; components can’t have that React just LOVES?&lt;/p&gt;

&lt;p&gt;Providers!&lt;/p&gt;

&lt;p&gt;Here’s another mentality shift for you. If you were thinking in a providers pattern, you need to snap out of it. You can no longer store your theme in a provider. No more &lt;a href="https://www.radix-ui.com/" rel="noopener noreferrer"&gt;RadixUI&lt;/a&gt; or &lt;a href="https://mantine.dev/" rel="noopener noreferrer"&gt;MantineUI&lt;/a&gt; for you.&lt;/p&gt;

&lt;p&gt;So to have a theme and reusable CSS variables you’d need to go with how Tailwind works. Tailwind doesn’t use providers. It stores all the variables in it’s own config file.&lt;/p&gt;

&lt;p&gt;Something like &lt;a href="https://nextui.org/" rel="noopener noreferrer"&gt;NextUI&lt;/a&gt; would work, i.e. a “New Gen” UI framework. It’s Tailwind based AND it works in both &lt;code&gt;.astro&lt;/code&gt; and &lt;code&gt;.tsx&lt;/code&gt; files. Amazing!&lt;/p&gt;

&lt;p&gt;To be frank, my last 4 projects were Tailwind-based. So I don’t see a problem with that. I can live with that choice.&lt;/p&gt;

&lt;p&gt;Guess WHAT also generally uses providers?&lt;/p&gt;

&lt;p&gt;State managers!&lt;/p&gt;

&lt;h3&gt;
  
  
  The time has come for the new kind of state managers
&lt;/h3&gt;

&lt;p&gt;We want to store state across Astro and React components. One state, two frameworks. How do you do that?&lt;/p&gt;

&lt;p&gt;Framework agnostic state managers!&lt;/p&gt;

&lt;p&gt;In its docs, Astro recommends &lt;a href="https://github.com/nanostores/nanostores" rel="noopener noreferrer"&gt;nanostores&lt;/a&gt;, but I’ve used &lt;a href="https://effector.dev/" rel="noopener noreferrer"&gt;effector&lt;/a&gt; in the past. And LOVED IT. So I’ve used it for this project as well.&lt;/p&gt;

&lt;p&gt;State managers like these are just JavaScript. They don’t care what frameworks they’re used with. They do have wrappers for them, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useUnit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;effector-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUnit&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;$text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$size&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;but you can do without them.&lt;/p&gt;

&lt;p&gt;Have you noticed that in v5 &lt;code&gt;@tanstack/query&lt;/code&gt; moved on from using provider pattern for their QueryClient, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-query&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;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;QueryClientProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to a more function-based approach, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&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;queryClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s a reason for that, I think. It’s more flexible, more universal, doesn’t lock you into a particular framework. You can fetch data on the server in &lt;code&gt;.astro&lt;/code&gt; component, store it in your &lt;code&gt;queryClient&lt;/code&gt; and then use it in a React component on the client with &lt;code&gt;useQuery&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Switch React to Vue and you won’t need to touch the state in any way. Amazing refactoring experience, isn’t it? 🤯&lt;/p&gt;

&lt;p&gt;Following the providers rant…&lt;/p&gt;

&lt;h3&gt;
  
  
  i18n
&lt;/h3&gt;

&lt;p&gt;Since there are no providers, we need to somehow communicate the language we’re working with to every component when setting up our localisation. So we do it this way 👇&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In Astro component&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLangFromUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"react"&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Then in Modal.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;lang&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;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ModalHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
           &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;modal.header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ModalHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the language won’t be coming from a provider, but rather as a prop. And we’ll be getting it from our server &lt;code&gt;.astro&lt;/code&gt; pages.&lt;/p&gt;

&lt;p&gt;Which, again, to me isn’t a big deal. It’s just different. It works and it still looks nice. We can even define a “parent” interface that we’ll be using for all the prop types, e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PropsBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ComponentProp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;PropsBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Astro is just like Next but more explicit
&lt;/h3&gt;

&lt;p&gt;I keep coming back to that word.&lt;/p&gt;

&lt;p&gt;In a lot of other aspects, Astro is not that different from Next developer experience and mentality-wise. I didn’t even feel pain when switching. I already knew where things would go, where things would come from, how to structure a project, etc.&lt;/p&gt;

&lt;p&gt;Astro has its own place for &lt;code&gt;getServerSideProps&lt;/code&gt; (or &lt;code&gt;use server&lt;/code&gt;); it has an &lt;code&gt;api&lt;/code&gt; folder you can create to store some serverless functions in; it runs in node and you can build a Dockerfile for it. and you can throw all your Google Analytics scripts to &lt;a href="https://partytown.builder.io/google-tag-manager#forward-events" rel="noopener noreferrer"&gt;PARTYTOWN&lt;/a&gt;, just like you’d do in Next.&lt;/p&gt;

&lt;p&gt;And if you need to set up SSR authentication, you can do that, it’s pretty straightforward. I was able to set up one with &lt;a href="https://supabase.com/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr" rel="noopener noreferrer"&gt;Supabase Auth&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So it’s just like Next, but better.&lt;/p&gt;

&lt;p&gt;Just a few ‘gotchas’ though…&lt;/p&gt;

&lt;h3&gt;
  
  
  The “gotchas”
&lt;/h3&gt;

&lt;p&gt;Astro has no runtime. This means no unit tests. This also means no Storybook for your Astro components (although, they’re &lt;a href="https://github.com/storybookjs/storybook/issues/18356" rel="noopener noreferrer"&gt;working on it&lt;/a&gt;!)&lt;/p&gt;

&lt;p&gt;Can you live with that?&lt;/p&gt;

&lt;p&gt;I’d say yes. When it comes to testing, server-rendered components don’t have much interactivity going on anyway, no user clicks, no state changes, etc. So tests might be considered redundant or over-engineer.&lt;/p&gt;

&lt;p&gt;And the layout render we can always test in e2e/integration/visual kinds of tests with Cypress or PlayWright.&lt;/p&gt;

&lt;p&gt;As for Storybook, well, not everything needs to be in a Storybook. Surely we can find a nice balance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Astro needs more work
&lt;/h3&gt;

&lt;p&gt;At the moment ISR comes down to cache &lt;a href="https://logsnag.com/blog/implementing-on-demand-revalidation-in-astro" rel="noopener noreferrer"&gt;control/invalidation&lt;/a&gt; in SSR pages, not static page regeneration.&lt;/p&gt;

&lt;p&gt;There is no per-page building, no ISR. If you rebuild static pages, you rebuild all of them.&lt;/p&gt;

&lt;p&gt;The build is fast though, as &lt;a href="https://astro.build/blog/experimental-static-build" rel="noopener noreferrer"&gt;it was pointed out&lt;/a&gt;, Astro certainly can handle 10K+ pages.&lt;/p&gt;

&lt;p&gt;Nonetheless, the Astro team is already &lt;a href="https://github.com/withastro/roadmap/issues/698" rel="noopener noreferrer"&gt;thinking and working on the build engine&lt;/a&gt;. So it will only become better.&lt;/p&gt;

&lt;p&gt;Not even more confusing as it was in Next.js case, I hope… 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  To sum up
&lt;/h2&gt;

&lt;p&gt;Astro is great. But it is slightly different from the way Next.js projects are done and not everyone would be happy to compromise. And that’s ok. We all have our own set of requirements, values, goals, the environment we work in, etc.&lt;/p&gt;

&lt;p&gt;However, I think Astro is the future. And I don’t mean Astro framework. I think the Astro way of doing things: explicit, transparent, and minimalistic. This is the future where all the frameworks work as one, and where frameworks don’t overwrite standardised APIs and expose important bits to the developers allowing us to modify and hack it to each project’s own needs.&lt;/p&gt;

&lt;p&gt;The future is simple, vanilla JavaScript because simplicity always wins. 🚀&lt;/p&gt;

&lt;p&gt;For more talking and less reading, feel free to &lt;a href="https://www.linkedin.com/posts/nkakuev_is-astro-ready-for-proper-projects-doing-activity-7171165857065099267-UlVW?utm_source=share&amp;amp;utm_medium=member_desktop" rel="noopener noreferrer"&gt;watch my stream&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Creating an autogrowing textarea with React and Typescript</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Thu, 28 Dec 2023 13:57:58 +0000</pubDate>
      <link>https://dev.to/snikidev/creating-an-autogrowing-textarea-with-react-and-typescript-45oc</link>
      <guid>https://dev.to/snikidev/creating-an-autogrowing-textarea-with-react-and-typescript-45oc</guid>
      <description>&lt;p&gt;In my 6 years career I almost never had to make an autogrowing textarea field like the ones we have in Jira or Linear (i.e. description fields) 👇&lt;/p&gt;


  


&lt;p&gt;But recently, I had to make one. While there are already some posts about this, e.g. &lt;a href="https://medium.com/@oherterich/creating-a-textarea-with-dynamic-height-using-react-and-typescript-5ed2d78d9848" rel="noopener noreferrer"&gt;THIS&lt;/a&gt; post by Owen, I thought I'd post my solution, which covers a slightly different scenario.&lt;/p&gt;

&lt;p&gt;So basically, I create a hook&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useInputHeight&lt;/span&gt; &lt;span class="o"&gt;=&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;string&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="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;setInputHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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="na"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLTextAreaElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;querySelector&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;textarea&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="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;px&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="nf"&gt;setInputHeight&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;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setInputHeight&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;setInputHeight&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;It returns a &lt;code&gt;setInputHeight&lt;/code&gt; function that we call in our &lt;code&gt;onChange&lt;/code&gt; function.&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;const&lt;/span&gt; &lt;span class="nx"&gt;setNameInputHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useInputHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textarea[name="email"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleInputChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLTextAreaElement&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;setInputHeight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other functionality&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you need a &lt;code&gt;value&lt;/code&gt; argument? No, you don't, but in my case, the value was being updated on the client, so I had to adjust the field height every time it updated without me triggering the &lt;code&gt;onChange&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;If that's not the case, simplify it to be&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useInputHeight&lt;/span&gt; &lt;span class="o"&gt;=&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;string&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;setInputHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLTextAreaElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;querySelector&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;textarea&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="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textarea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;px&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;setInputHeight&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;That's all you'd need.&lt;/p&gt;

&lt;p&gt;You may have also noticed that I'm not using &lt;code&gt;useRef()&lt;/code&gt; and simply doing &lt;code&gt;document.querySelector&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Would &lt;code&gt;ref&lt;/code&gt; work? Yes, yes it should. The only reason I was using &lt;code&gt;querySelector&lt;/code&gt; was because I was using a UI library and textarea there wasn't allowing a &lt;code&gt;ref&lt;/code&gt; prop. So I didn't want to do all those workarounds, &lt;code&gt;querySelector&lt;/code&gt; seemed like a great option.&lt;/p&gt;

&lt;p&gt;Do feel free to use a &lt;code&gt;ref&lt;/code&gt; if you'd like, after all, it IS a more 'React way' of doing things 😁👍&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>ui</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Storybook 7.0 + React.js + TailwindCSS + CSS modules + Typescript setup that #$%&amp; works</title>
      <dc:creator>Niki</dc:creator>
      <pubDate>Mon, 20 Feb 2023 22:48:18 +0000</pubDate>
      <link>https://dev.to/snikidev/storybook-70-reactjs-tailwindcss-css-modules-typescript-setup-that-works-281f</link>
      <guid>https://dev.to/snikidev/storybook-70-reactjs-tailwindcss-css-modules-typescript-setup-that-works-281f</guid>
      <description>&lt;p&gt;UPDATE: when writing this I have found &lt;a href="https://storybook.js.org/recipes/tailwindcss" rel="noopener noreferrer"&gt;THIS article&lt;/a&gt;, that, coincidentally, was also updated around the same time.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;rant&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Setting up a storybook project should NOT be THAT hard. I mean, everyone uses Storybook, it’s the only viable option out there when working on a design system. &lt;/p&gt;

&lt;p&gt;TailwindCSS is at the stage when it’s already gained the momentum. Even large companies are considering it. Practically EVERY front-end dev has already worked with it.&lt;/p&gt;

&lt;p&gt;And CSS modules have beed out there for more that 6 years I believe. And even though in JavaScript terms it’s a dinosaur, a legacy, it’s very much not dead and still a top choice for many, including moi.&lt;/p&gt;

&lt;p&gt;Not even going to say anything about Typescript and React. If you’re not using TS or never worked with TS today - you’re unemployable. And React is a standard. &lt;/p&gt;

&lt;p&gt;Surely there should be an article somewhere on how to set all this up correctly. SURELY?!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;/rant&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Well, after 4 hours of googling and trial and error, I finally was able to crack this “OH SO UNUSUAL” tech stack setup. Here is &lt;a href="https://github.com/snikidev/storybook-tailwindcss-setup" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;, and let me highlight two points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Point 1: Webpack 5 builder and framework
&lt;/h2&gt;

&lt;p&gt;Webpack 4 is still the default choice for Storybook, but Storybook has already released support for Webpack 5, so I see no reason why we’d stick to v4. Important note here is that since we’re updating the &lt;code&gt;core&lt;/code&gt; property, we should also update the &lt;code&gt;framework&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/react-webpack5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;core&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@storybook/builder-webpack5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Point 2: Don’t overwrite default Webpack rules, but add more instead
&lt;/h2&gt;

&lt;p&gt;It (unfortunately!) became a habit for whatever reason, that if I need to add some custom loader, I exclude the default rule from the array and re-create it. We shouldn’t do that, because it messes up the Storybook config in a way that it becomes hard to unfuck it. &lt;/p&gt;

&lt;p&gt;Because we need to add a PostCSS loader to our &lt;code&gt;.css&lt;/code&gt; files, don’t try to re-create css rule with all the &lt;code&gt;css-loader&lt;/code&gt; and &lt;code&gt;style-loader&lt;/code&gt; etc., but just push that extra rule to the end of that array, and be done with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;webpackFinal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;config&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;test&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;css$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postcss-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;postcssOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="na"&gt;autoprefixer&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;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&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;That’s pretty much all I wanted to say. Those two points took me a while to understand, so hope your found my post within your first hour of searching. 😅&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
