<?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: Khaja Hussain</title>
    <description>The latest articles on DEV Community by Khaja Hussain (@khaja_hussain_db1f84efe83).</description>
    <link>https://dev.to/khaja_hussain_db1f84efe83</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%2F2560465%2Ffaf7ab2d-3413-4aac-84ad-22ef1c22b6c0.png</url>
      <title>DEV Community: Khaja Hussain</title>
      <link>https://dev.to/khaja_hussain_db1f84efe83</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khaja_hussain_db1f84efe83"/>
    <language>en</language>
    <item>
      <title>Playwright Test Agents: Can AI Really Write and Fix Your Tests?</title>
      <dc:creator>Khaja Hussain</dc:creator>
      <pubDate>Fri, 23 Oct 2026 19:52:21 +0000</pubDate>
      <link>https://dev.to/khaja_hussain_db1f84efe83/playwright-test-agents-can-ai-really-write-and-fix-your-tests-158a</link>
      <guid>https://dev.to/khaja_hussain_db1f84efe83/playwright-test-agents-can-ai-really-write-and-fix-your-tests-158a</guid>
      <description>&lt;p&gt;A few years ago, automating a login page was fairly straightforward.&lt;/p&gt;

&lt;p&gt;You would open the application, inspect the HTML elements, write a Playwright or Selenium script, and run it.&lt;/p&gt;

&lt;p&gt;Everything worked fine until someone changed the UI.&lt;/p&gt;

&lt;p&gt;Maybe the login button was renamed. Maybe a form field moved. Or perhaps a developer replaced an ordinary HTML input with a custom Angular component.&lt;/p&gt;

&lt;p&gt;Suddenly, a test that had been working for months started failing.&lt;/p&gt;

&lt;p&gt;The application itself might be perfectly fine. But the automation script no longer knows how to interact with it.&lt;/p&gt;

&lt;p&gt;If you've worked with UI automation, you've probably come across situations like this.&lt;/p&gt;

&lt;p&gt;Now imagine having an assistant that can explore your application, suggest what should be tested, write the test scripts, and help investigate failures.&lt;/p&gt;

&lt;p&gt;That's the idea behind &lt;strong&gt;Playwright Test Agents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's understand how they work, using a simple example.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what is Playwright?
&lt;/h2&gt;

&lt;p&gt;Before getting into AI agents, let's understand Playwright itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; is an open-source browser automation framework developed by Microsoft.&lt;/p&gt;

&lt;p&gt;It allows developers and QA engineers to automatically test web applications.&lt;/p&gt;

&lt;p&gt;For example, instead of manually opening a website and checking whether login works, you can write a test that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opens the login page.&lt;/li&gt;
&lt;li&gt;Enters an email address.&lt;/li&gt;
&lt;li&gt;Enters a password.&lt;/li&gt;
&lt;li&gt;Clicks the Sign In button.&lt;/li&gt;
&lt;li&gt;Checks whether the application displays the expected result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The browser performs these actions automatically.&lt;/p&gt;

&lt;p&gt;Playwright supports Chromium, Firefox, and WebKit, so the same test can be executed across different browser engines.&lt;/p&gt;

&lt;p&gt;But there's one challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Someone still needs to decide what to test, write the automation scripts, and maintain them when the application changes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where AI agents become interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Playwright Test Agents?
&lt;/h2&gt;

&lt;p&gt;Playwright introduced Test Agents in version 1.56.&lt;/p&gt;

&lt;p&gt;Instead of relying entirely on manually written test scripts, these agents help with different stages of the testing process.&lt;/p&gt;

&lt;p&gt;There are three main agents:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Planner — Decides what to test&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Planner explores your application and prepares a test plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Generator — Writes the tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Generator takes that plan and creates Playwright test scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Healer — Helps fix failing tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Healer runs tests, investigates failures, and attempts to repair issues in the automation code.&lt;/p&gt;

&lt;p&gt;Think of them as three members of a QA team.&lt;/p&gt;

&lt;p&gt;One prepares the scenarios, another writes the automation, and the third investigates why something failed.&lt;/p&gt;

&lt;p&gt;Of course, they're AI agents, not actual team members. Their output still needs to be reviewed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's take a real-world example
&lt;/h2&gt;

&lt;p&gt;Imagine we're building a customer portal using Angular for the frontend and ASP.NET Core for the backend.&lt;/p&gt;

&lt;p&gt;The application has a signup page with four fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full Name&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;li&gt;Confirm Password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also a &lt;strong&gt;Create Account&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;As a developer, you might test it once with valid information, see that the account was created successfully, and move on.&lt;/p&gt;

&lt;p&gt;But what else could go wrong?&lt;/p&gt;

&lt;p&gt;What happens if someone enters an invalid email address?&lt;/p&gt;

&lt;p&gt;What if the password and confirmation password don't match?&lt;/p&gt;

&lt;p&gt;What if the user tries to register with an email address that already exists?&lt;/p&gt;

&lt;p&gt;What if someone clicks Create Account without entering anything?&lt;/p&gt;

&lt;p&gt;These are basic scenarios, but it's surprisingly easy to miss one when you're focused on delivering a feature.&lt;/p&gt;

&lt;p&gt;Let's see how the agents could help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Planner — Let AI prepare the test scenarios
&lt;/h2&gt;

&lt;p&gt;We start by giving the Planner a simple instruction.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Explore the signup page of my application and create a test plan. Include successful registration, invalid email addresses, empty fields, password mismatch, and duplicate email registration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Planner can inspect the application and prepare a Markdown file containing the proposed scenarios.&lt;/p&gt;

&lt;p&gt;An example plan might look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Valid signup details&lt;/td&gt;
&lt;td&gt;Account is created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty email address&lt;/td&gt;
&lt;td&gt;Required-field validation appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid email format&lt;/td&gt;
&lt;td&gt;Email validation error appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password mismatch&lt;/td&gt;
&lt;td&gt;Password mismatch message appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate email&lt;/td&gt;
&lt;td&gt;Appropriate error message appears&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The actual plan will depend on the application's behaviour and requirements.&lt;/p&gt;

&lt;p&gt;Notice something important here.&lt;/p&gt;

&lt;p&gt;We didn't start by writing TypeScript code.&lt;/p&gt;

&lt;p&gt;We started by explaining what the application should do.&lt;/p&gt;

&lt;p&gt;This is useful for beginners because it encourages thinking about &lt;strong&gt;test scenarios before automation scripts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, the Planner cannot automatically know every business rule. For example, it may not know whether your application requires a minimum password length of 8 or 12 characters unless that information is available.&lt;/p&gt;

&lt;p&gt;So review the plan before generating tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Generator — Turn the plan into Playwright tests
&lt;/h2&gt;

&lt;p&gt;Once the test plan is ready, the Generator can use it to create executable test scripts.&lt;/p&gt;

&lt;p&gt;For example, consider this scenario:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A user should not be able to register with an invalid email address.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A Playwright test for that scenario might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&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;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid email should show an error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:4200/signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Full Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid-email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password@123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Confirm Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password@123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&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 Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&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="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please enter a valid email&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;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an illustrative test. The field labels, page URL, and validation message would need to match your actual application.&lt;/p&gt;

&lt;p&gt;If you're new to Playwright, here's what the important lines do.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;page.goto()&lt;/code&gt; opens a webpage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getByLabel()&lt;/code&gt; finds an input field using its visible label.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fill()&lt;/code&gt; enters a value into that field.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getByRole()&lt;/code&gt; locates the button.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;click()&lt;/code&gt; simulates a user clicking it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;expect()&lt;/code&gt; checks whether the expected result appears.&lt;/p&gt;

&lt;p&gt;In this example, we're checking that the application displays an email validation message.&lt;/p&gt;

&lt;p&gt;Without AI, we could write this script manually.&lt;/p&gt;

&lt;p&gt;With the Generator, we can ask an agent to create similar scripts based on our test plan.&lt;/p&gt;

&lt;p&gt;That doesn't mean we should accept every generated test without checking it. An AI agent could write an assertion that doesn't reflect the intended business behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Healer — What happens when a test breaks?
&lt;/h2&gt;

&lt;p&gt;Now let's imagine something changes in our Angular application.&lt;/p&gt;

&lt;p&gt;Originally, our signup button was called:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create Account&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few weeks later, the design team changes the text to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign Up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application continues to work for normal users.&lt;/p&gt;

&lt;p&gt;But our automated test still searches for a button called Create Account.&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&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 Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the button name has changed, the test may fail.&lt;/p&gt;

&lt;p&gt;This is where the Healer becomes useful.&lt;/p&gt;

&lt;p&gt;The Healer can execute the failing test, inspect the page, and attempt to identify what changed.&lt;/p&gt;

&lt;p&gt;If it determines that the Sign Up button is the correct replacement, it may suggest updating the locator to:&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign Up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can then rerun the test to check whether the change resolves the failure.&lt;/p&gt;

&lt;p&gt;That's a relatively simple example, but it shows why test maintenance is such an important part of automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There's an important catch, though.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose registration itself has stopped working because the ASP.NET Core API is returning an HTTP 500 error.&lt;/p&gt;

&lt;p&gt;That's a genuine application defect.&lt;/p&gt;

&lt;p&gt;We don't want an AI agent to simply remove the failing assertion so that the test passes.&lt;/p&gt;

&lt;p&gt;A passing test is only valuable when it's checking the correct behaviour.&lt;/p&gt;

&lt;p&gt;The Healer may also skip a test if it believes the functionality is broken, so those outcomes need human review.&lt;/p&gt;

&lt;h2&gt;
  
  
  How can you try Playwright Test Agents?
&lt;/h2&gt;

&lt;p&gt;If you're a developer working with Angular, React, or ASP.NET Core, you can experiment with these agents using a local web application.&lt;/p&gt;

&lt;p&gt;You'll need Node.js, a supported AI coding assistant, and a Playwright project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, initialize Playwright&lt;/strong&gt; if it isn't already installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init playwright@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the installation prompts to configure your test project and install the required browsers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next, initialize the Playwright Test Agents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For VS Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright init-agents &lt;span class="nt"&gt;--loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright init-agents &lt;span class="nt"&gt;--loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Codex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright init-agents &lt;span class="nt"&gt;--loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands add agent definitions to your project. You'll still need a supported AI coding environment to use them.&lt;/p&gt;

&lt;p&gt;Now run your web application locally.&lt;/p&gt;

&lt;p&gt;For example, an Angular development server commonly runs at:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://localhost:4200&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open your supported AI agent interface and ask the Planner to create test scenarios for your signup page.&lt;/p&gt;

&lt;p&gt;Then use the Generator to create tests from the plan.&lt;/p&gt;

&lt;p&gt;To execute the tests, run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If something fails, the Healer can help investigate.&lt;/p&gt;

&lt;p&gt;A useful detail from the official documentation is that the Planner can use a &lt;strong&gt;seed test&lt;/strong&gt; to understand the application's setup, dependencies, and authentication requirements.&lt;/p&gt;

&lt;p&gt;This becomes important when testing applications that require login or special test data.&lt;/p&gt;

&lt;p&gt;For the complete setup and agent workflow, see the &lt;a href="https://playwright.dev/docs/test-agents" rel="noopener noreferrer"&gt;official Playwright Test Agents documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does this mean we no longer need QA engineers?
&lt;/h2&gt;

&lt;p&gt;I don't think so.&lt;/p&gt;

&lt;p&gt;AI can help reduce repetitive work, but software testing involves much more than clicking buttons and checking messages.&lt;/p&gt;

&lt;p&gt;Consider a payment application.&lt;/p&gt;

&lt;p&gt;An AI agent might verify that clicking Pay Now displays a success message.&lt;/p&gt;

&lt;p&gt;But what if the customer was charged twice?&lt;/p&gt;

&lt;p&gt;What if the payment succeeded but the database transaction failed?&lt;/p&gt;

&lt;p&gt;What if the request was retried after a network timeout?&lt;/p&gt;

&lt;p&gt;These are the kinds of situations where domain knowledge and engineering experience matter.&lt;/p&gt;

&lt;p&gt;AI may help generate scenarios, but someone still needs to understand the system, identify risks, validate assumptions, and decide whether the results are meaningful.&lt;/p&gt;

&lt;p&gt;For me, the interesting part isn't that AI can write a few lines of Playwright code.&lt;/p&gt;

&lt;p&gt;It's that AI agents can help with the broader testing workflow, from planning to maintenance.&lt;/p&gt;

&lt;p&gt;That's a more meaningful improvement than simply generating scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things I'd keep in mind
&lt;/h2&gt;

&lt;p&gt;If you're planning to introduce AI agents into an existing project, I'd start small.&lt;/p&gt;

&lt;p&gt;Choose one feature, perhaps login or signup, and see how the agents behave.&lt;/p&gt;

&lt;p&gt;Review the generated scenarios before running them.&lt;/p&gt;

&lt;p&gt;Run tests against a development or dedicated test environment, not production.&lt;/p&gt;

&lt;p&gt;Make sure you control the test data. Registration tests can easily create dozens of unwanted accounts if you're not careful.&lt;/p&gt;

&lt;p&gt;And most importantly, never assume that a green test result automatically means the feature is working correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Playwright Test Agents are an interesting step forward for browser automation.&lt;/p&gt;

&lt;p&gt;The Planner helps organize what needs testing. The Generator turns those scenarios into code. The Healer helps investigate and repair failing tests.&lt;/p&gt;

&lt;p&gt;For beginners, they can make test automation more approachable.&lt;/p&gt;

&lt;p&gt;For experienced developers and QA engineers, they offer another way to reduce repetitive work and spend more time understanding application behaviour.&lt;/p&gt;

&lt;p&gt;But the fundamentals haven't changed.&lt;/p&gt;

&lt;p&gt;A good automated test should verify something meaningful, be repeatable, and fail when the application behaves incorrectly.&lt;/p&gt;

&lt;p&gt;AI can help us build those tests. It shouldn't replace our understanding of what we're testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One small practical tip:&lt;/strong&gt; If you're testing a signup flow that requires email verification, you don't always need to use your personal email address. For manual testing, you can use any online temporary email generator such as &lt;a href="https://temp-email.ai" rel="noopener noreferrer"&gt;Temp email&lt;/a&gt; to create a disposable inbox and check whether registration or verification emails arrive correctly. For repeatable automated tests, a controlled test inbox or email-testing service is usually a better choice.&lt;/p&gt;

</description>
      <category>qa</category>
    </item>
    <item>
      <title>.NET Native AOT vs Traditional ASP.NET Core: What Developers Should Know</title>
      <dc:creator>Khaja Hussain</dc:creator>
      <pubDate>Wed, 21 Oct 2026 20:41:02 +0000</pubDate>
      <link>https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3c8e</link>
      <guid>https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3c8e</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3n3n" class="crayons-story__hidden-navigation-link"&gt;.NET Native AOT vs Traditional ASP.NET Core: What Developers Should Know&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/khaja_hussain_db1f84efe83" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2560465%2Ffaf7ab2d-3413-4aac-84ad-22ef1c22b6c0.png" alt="khaja_hussain_db1f84efe83 profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/khaja_hussain_db1f84efe83" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Khaja Hussain
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Khaja Hussain
                
                
              
              &lt;div id="story-author-preview-content-4891589" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/khaja_hussain_db1f84efe83" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2560465%2Ffaf7ab2d-3413-4aac-84ad-22ef1c22b6c0.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Khaja Hussain&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3n3n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 21&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3n3n" id="article-link-4891589"&gt;
          .NET Native AOT vs Traditional ASP.NET Core: What Developers Should Know
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/backend"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;backend&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/csharp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;csharp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dotnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dotnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/performance"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;performance&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3n3n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>architecture</category>
      <category>backend</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
    <item>
      <title>.NET Native AOT vs Traditional ASP.NET Core: What Developers Should Know</title>
      <dc:creator>Khaja Hussain</dc:creator>
      <pubDate>Wed, 21 Oct 2026 19:44:04 +0000</pubDate>
      <link>https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3n3n</link>
      <guid>https://dev.to/khaja_hussain_db1f84efe83/net-native-aot-vs-traditional-aspnet-core-what-developers-should-know-3n3n</guid>
      <description>&lt;p&gt;Recently, I was checking the memory usage of a server running several ASP.NET Core applications.&lt;/p&gt;

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

&lt;p&gt;Nothing had crashed. The applications were running fine. But memory utilization was higher than I would have liked, and it got me thinking about whether I could make better use of the server's resources.&lt;/p&gt;

&lt;p&gt;I've been working with C# and ASP.NET for years, mostly building APIs, database-driven applications, and Angular frontends. My usual stack is ASP.NET Core, SQL Server, and Entity Framework Core.&lt;/p&gt;

&lt;p&gt;So naturally, I started looking at .NET Native AOT.&lt;/p&gt;

&lt;p&gt;I'd heard about the smaller executables, faster startup times, and lower memory requirements. All of that sounded promising.&lt;/p&gt;

&lt;p&gt;But I had a more practical question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Could I take an existing ASP.NET Core application, enable Native AOT, and get the same functionality while using fewer resources?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The more I looked into it, the more I realized that the answer depends heavily on how the application is built.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what actually changes with Native AOT?
&lt;/h2&gt;

&lt;p&gt;In a traditional ASP.NET Core application, our C# code is compiled into Intermediate Language (IL).&lt;/p&gt;

&lt;p&gt;When the application runs, the .NET runtime uses Just-in-Time (JIT) compilation to turn that IL into native machine code as needed.&lt;/p&gt;

&lt;p&gt;Native AOT changes when that compilation happens.&lt;/p&gt;

&lt;p&gt;Instead of waiting until runtime, the application is compiled into native machine code during publishing.&lt;/p&gt;

&lt;p&gt;That means the deployed application doesn't need a JIT compiler to execute its code.&lt;/p&gt;

&lt;p&gt;It can also run without a separately installed .NET runtime because the necessary runtime components are included in the published application.&lt;/p&gt;

&lt;p&gt;For certain workloads, especially small services that start frequently, this can make a noticeable difference.&lt;/p&gt;

&lt;p&gt;But there's something worth understanding here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native AOT doesn't remove garbage collection from .NET.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're still writing managed C#. Objects still get allocated, and the garbage collector still manages memory.&lt;/p&gt;

&lt;p&gt;So if an application is holding a large collection in memory, caching too much data, or loading unnecessary records from SQL Server, Native AOT isn't going to magically fix that.&lt;/p&gt;

&lt;p&gt;This was an important distinction for me because memory usage was what got me interested in AOT in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Native AOT starts getting complicated
&lt;/h2&gt;

&lt;p&gt;Creating a new Minimal API with Native AOT support is fairly straightforward.&lt;/p&gt;

&lt;p&gt;The interesting part is what happens when we try to apply the same approach to an existing business application.&lt;/p&gt;

&lt;p&gt;Consider a typical ASP.NET Core project.&lt;/p&gt;

&lt;p&gt;It probably has controllers, services, dependency injection, Entity Framework Core, authentication, logging, and a few third-party packages.&lt;/p&gt;

&lt;p&gt;For a traditional ASP.NET Core application, that's a completely normal setup.&lt;/p&gt;

&lt;p&gt;For Native AOT, some of those choices become important.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. What about my existing controllers?
&lt;/h3&gt;

&lt;p&gt;This was the first major limitation that caught my attention.&lt;/p&gt;

&lt;p&gt;As of .NET 10, ASP.NET Core MVC controllers aren't supported by Native AOT.&lt;/p&gt;

&lt;p&gt;Minimal APIs are supported, although there are still compatibility limitations.&lt;/p&gt;

&lt;p&gt;If your application has dozens of controllers, you can't simply add &lt;code&gt;PublishAot&lt;/code&gt; to the project file and expect everything to work.&lt;/p&gt;

&lt;p&gt;You may have to restructure parts of the application.&lt;/p&gt;

&lt;p&gt;That isn't necessarily a bad thing for a new microservice, but it's a different conversation when you're maintaining an existing enterprise application.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Entity Framework Core is another consideration
&lt;/h3&gt;

&lt;p&gt;I use Entity Framework Core extensively, especially with SQL Server.&lt;/p&gt;

&lt;p&gt;It's convenient for filtering, pagination, relationships, and working with database entities.&lt;/p&gt;

&lt;p&gt;However, Native AOT support in EF Core is still experimental according to Microsoft's documentation.&lt;/p&gt;

&lt;p&gt;One of the challenges is query generation.&lt;/p&gt;

&lt;p&gt;In a traditional EF Core application, queries can be built dynamically at runtime.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dbContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsQueryable&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&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="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&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="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search&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="n"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&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="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;categoryId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageIndex&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a common pattern in business applications.&lt;/p&gt;

&lt;p&gt;Filtering and pagination requirements change constantly, and developers need the flexibility to construct queries based on user input.&lt;/p&gt;

&lt;p&gt;EF Core's experimental Native AOT support uses query precompilation, which has limitations with dynamically composed queries like this.&lt;/p&gt;

&lt;p&gt;You can sometimes rewrite queries into shapes that can be precompiled, but that adds design constraints.&lt;/p&gt;

&lt;p&gt;For a small API with a handful of predictable queries, that might be manageable.&lt;/p&gt;

&lt;p&gt;For a large application with complicated search screens and reporting requirements, it's worth thinking carefully before committing to that approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying Native AOT with a small API
&lt;/h2&gt;

&lt;p&gt;If you're curious about Native AOT, I wouldn't recommend starting by converting a large existing application.&lt;/p&gt;

&lt;p&gt;I'd start with a small API.&lt;/p&gt;

&lt;p&gt;With the .NET 10 SDK installed, you can create a project using Microsoft's AOT template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new webapiaot &lt;span class="nt"&gt;-n&lt;/span&gt; AotDemo
&lt;span class="nb"&gt;cd &lt;/span&gt;AotDemo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template is already configured for Native AOT and uses Minimal APIs.&lt;/p&gt;

&lt;p&gt;One difference you'll notice is the way JSON serialization is configured.&lt;/p&gt;

&lt;p&gt;Let's say we have a simple endpoint returning orders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json.Serialization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateSlimBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureHttpJsonOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SerializerOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeInfoResolverChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AppJsonSerializerContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Paid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;49.99m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;19.50m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;OrderDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonSerializable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderDto&lt;/span&gt;&lt;span class="p"&gt;[]))]&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppJsonSerializerContext&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerContext&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;Notice the &lt;code&gt;JsonSerializable&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;In a regular ASP.NET Core application, JSON serialization often relies on runtime reflection to discover the properties of objects.&lt;/p&gt;

&lt;p&gt;Native AOT works better when that information is generated during compilation.&lt;/p&gt;

&lt;p&gt;That's why &lt;code&gt;System.Text.Json&lt;/code&gt; source generation is important.&lt;/p&gt;

&lt;p&gt;The API response itself is ordinary JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;49.99&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pending"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;19.50&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While experimenting with different API implementations, I would also compare their actual responses.&lt;/p&gt;

&lt;p&gt;It's easy to focus on startup time or memory usage and overlook whether a change to serialization has affected the response structure.&lt;/p&gt;

&lt;p&gt;For larger sample responses, especially arrays containing nested objects, a table view can help. Tools like &lt;a href="https://jsontotable.org" rel="noopener noreferrer"&gt;JSON Parser&lt;/a&gt; make it easier to inspect JSON responses and understand their structure in a more readable format.&lt;/p&gt;

&lt;p&gt;Of course, I would only use sample or non-sensitive data with an online tool. For actual validation, automated tests should compare the expected response structure and values.&lt;/p&gt;

&lt;p&gt;Once the application is ready, you can publish it with Native AOT.&lt;/p&gt;

&lt;p&gt;For example, on a properly configured Windows development machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-r&lt;/span&gt; win-x64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Native AOT publishing requires platform-specific build tools, including the appropriate C++ compiler toolchain.&lt;/p&gt;

&lt;p&gt;Also, remember that running the project with &lt;code&gt;dotnet run&lt;/code&gt; doesn't test the final Native AOT executable. The real test is the published binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Would Native AOT make every API faster?
&lt;/h2&gt;

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

&lt;p&gt;This is where I think developers need to separate startup performance from request-processing performance.&lt;/p&gt;

&lt;p&gt;Native AOT can significantly improve startup time and reduce application footprint.&lt;/p&gt;

&lt;p&gt;But that doesn't automatically mean an API processing thousands of requests will have better throughput.&lt;/p&gt;

&lt;p&gt;Suppose an endpoint spends most of its time executing a SQL Server query.&lt;/p&gt;

&lt;p&gt;If that query takes 300 milliseconds, switching from JIT to AOT won't suddenly make the database return results in 30 milliseconds.&lt;/p&gt;

&lt;p&gt;Database indexes, query execution plans, network latency, caching, and application logic still matter.&lt;/p&gt;

&lt;p&gt;Also, the traditional .NET JIT compiler is highly optimized. Once an application has warmed up, it can deliver excellent performance.&lt;/p&gt;

&lt;p&gt;So I wouldn't make a migration decision based only on a comparison of two empty API templates.&lt;/p&gt;

&lt;p&gt;I'd want to see how my actual application behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I would compare the two approaches
&lt;/h2&gt;

&lt;p&gt;Before considering Native AOT for production, I'd build a small service that represents part of my existing workload.&lt;/p&gt;

&lt;p&gt;Ideally, I'd publish the same functionality with both JIT and Native AOT, using comparable deployment configurations.&lt;/p&gt;

&lt;p&gt;I'd measure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Startup time:&lt;/strong&gt; How quickly can the application begin serving requests?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory usage:&lt;/strong&gt; What is the process memory consumption when idle and under a representative workload?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API latency:&lt;/strong&gt; How do average and p95/p99 response times compare?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput:&lt;/strong&gt; How many requests can each version handle before latency starts increasing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment size:&lt;/strong&gt; How much space does each published application require?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility:&lt;/strong&gt; Did I have to rewrite code or replace any libraries?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd also run the tests multiple times on the same hardware and check the published AOT application's behavior, not just its build output.&lt;/p&gt;

&lt;p&gt;If the API works with both approaches and AOT gives me a measurable benefit, great.&lt;/p&gt;

&lt;p&gt;But if I have to spend days rewriting controllers or working around library limitations to save a small amount of memory, I would question whether the trade-off makes sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I think Native AOT fits best
&lt;/h2&gt;

&lt;p&gt;For me, Native AOT is particularly interesting for small, focused services.&lt;/p&gt;

&lt;p&gt;A background worker, a lightweight API, or a service that needs to start quickly could be a good candidate.&lt;/p&gt;

&lt;p&gt;An application with hundreds of controllers, extensive EF Core usage, and several reflection-heavy libraries is a different story.&lt;/p&gt;

&lt;p&gt;In that situation, I'd first look at the existing application's memory allocations, database queries, caching strategy, and hosting configuration.&lt;/p&gt;

&lt;p&gt;Sometimes the most effective optimization isn't changing the compilation model. It's fixing an inefficient query or reducing the amount of data the application keeps in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I started looking into Native AOT because I wanted to understand whether it could help reduce the resources consumed by my ASP.NET Core applications.&lt;/p&gt;

&lt;p&gt;I haven't migrated those applications to Native AOT, and I don't have production benchmarks to share yet.&lt;/p&gt;

&lt;p&gt;What I've learned so far is that Native AOT is worth exploring, but it shouldn't be treated as an automatic upgrade for every existing .NET application.&lt;/p&gt;

&lt;p&gt;If I were starting a small, independent API today, I would seriously consider testing Native AOT from the beginning.&lt;/p&gt;

&lt;p&gt;For an established ASP.NET Core application built around controllers and Entity Framework Core, I would be more cautious.&lt;/p&gt;

&lt;p&gt;The deciding factor shouldn't be which approach looks faster in a benchmark screenshot.&lt;/p&gt;

&lt;p&gt;It should be whether the performance improvement justifies the architectural and maintenance cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm curious how other .NET developers are approaching this. Have you used Native AOT in production, or are you still sticking with traditional ASP.NET Core for your APIs?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/native-aot?view=aspnetcore-10.0" rel="noopener noreferrer"&gt;Microsoft: ASP.NET Core support for Native AOT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/ef/core/performance/nativeaot-and-precompiled-queries" rel="noopener noreferrer"&gt;Microsoft: EF Core NativeAOT support and precompiled queries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot" rel="noopener noreferrer"&gt;Microsoft: Native AOT deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>performance</category>
    </item>
    <item>
      <title>What Switching From C# to Rust Actually Taught Me</title>
      <dc:creator>Khaja Hussain</dc:creator>
      <pubDate>Sat, 11 Jul 2026 09:01:28 +0000</pubDate>
      <link>https://dev.to/khaja_hussain_db1f84efe83/what-switching-from-c-to-rust-actually-taught-me-27do</link>
      <guid>https://dev.to/khaja_hussain_db1f84efe83/what-switching-from-c-to-rust-actually-taught-me-27do</guid>
      <description>&lt;p&gt;I've spent most of my career writing C#. ASP.NET Core, EF Core, the whole ecosystem — it's productive, well-documented, and I've never had a real complaint about it. This isn't a "C# vs Rust, one wins" post. C# remains one of my favorite languages to build with. This is just about why I got curious about Rust, and what I noticed when I actually rewrote something instead of just reading takes about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I got curious in the first place
&lt;/h2&gt;

&lt;p&gt;Three things kept pulling my attention:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No GC pauses.&lt;/strong&gt; C#'s garbage collector is well-engineered, but it still pauses your program at moments it chooses, not you. For latency-sensitive work, that unpredictability is worth understanding better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile-time memory safety.&lt;/strong&gt; Rust catches null references and data races at compile time instead of at runtime. A different tradeoff, not a "fix" for something broken in C#.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership.&lt;/strong&gt; People kept describing it as a big deal. I wanted to actually understand why instead of nodding along.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two weeks were genuinely hard — I fought the borrow checker constantly, rewriting the same function three times before it compiled. It clicked faster than I expected though, and it left me with a better mental model for shared state in general, one that's helped me even back in C#. I ended up collecting everything I wish someone had told me going in over at &lt;a href="https://rusttutorial.org" rel="noopener noreferrer"&gt;Rust Tutorial&lt;/a&gt; — more on that at the end, if ownership is the part that's holding you back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project: a real-time order tracker
&lt;/h2&gt;

&lt;p&gt;To compare the two properly, I built a small dummy project — a real-time order tracking service. Incoming order events update in-memory state, and connected dashboards get live updates over a WebSocket. It's a common shape: inventory systems, delivery tracking, live ops dashboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C# version:&lt;/strong&gt; ASP.NET Core minimal API, SignalR for the WebSocket push, a &lt;code&gt;ConcurrentDictionary&lt;/code&gt; for state.&lt;br&gt;
&lt;strong&gt;Rust version:&lt;/strong&gt; &lt;code&gt;axum&lt;/code&gt; for HTTP/WebSockets, &lt;code&gt;tokio&lt;/code&gt; as the runtime, state behind an &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;HashMap&amp;gt;&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Both versions expose API endpoints that return JSON responses. To test them, you can send a few sample requests and use an &lt;a href="https://jsontotable.org/" rel="noopener noreferrer"&gt;online JSON parser&lt;/a&gt; to inspect the responses, especially when dealing with nested order data. This also makes it easier to verify that both implementations return the same structure.&lt;/p&gt;

&lt;p&gt;Nothing production-grade — just enough of a real workload to notice actual differences instead of comparing "Hello World" benchmarks, which don't tell you much.&lt;/p&gt;

&lt;h2&gt;
  
  
  What felt different, not better or worse
&lt;/h2&gt;

&lt;p&gt;The frameworks themselves were both pleasant to use. What changed was how explicit I had to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared state.&lt;/strong&gt; C#'s &lt;code&gt;ConcurrentDictionary&lt;/code&gt; just worked — I trusted the library and moved on. Rust made me spell out the &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt; at every point it was touched. More typing, but I came away understanding my own concurrency model better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errors.&lt;/strong&gt; A malformed event in the C# version could throw from a background task and, if I'd missed a try/catch, fail quietly. Rust's &lt;code&gt;Result&lt;/code&gt; made every fallible step something I had to explicitly acknowledge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nulls.&lt;/strong&gt; "Order not found yet" was a null check I could forget in C#. In Rust it's &lt;code&gt;Option&lt;/code&gt;, and the compiler won't let it slide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this means Rust is "better" — it just made assumptions in my C# code more visible, which was uncomfortable at first and useful by the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick, informal numbers (not a real benchmark)
&lt;/h2&gt;

&lt;p&gt;Full transparency: this was a five-minute &lt;code&gt;wrk&lt;/code&gt; run on my own laptop, not a controlled test environment. Don't read too much into the exact figures — but the general pattern lined up with what's commonly reported elsewhere too.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;C# (ASP.NET Core)&lt;/th&gt;
&lt;th&gt;Rust (axum)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Idle memory footprint&lt;/td&gt;
&lt;td&gt;~110 MB&lt;/td&gt;
&lt;td&gt;~9 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start time&lt;/td&gt;
&lt;td&gt;~1.2s&lt;/td&gt;
&lt;td&gt;~40ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests/sec (simple load test)&lt;/td&gt;
&lt;td&gt;~9,000&lt;/td&gt;
&lt;td&gt;~34,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p99 latency&lt;/td&gt;
&lt;td&gt;~42ms&lt;/td&gt;
&lt;td&gt;~6ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy artifact&lt;/td&gt;
&lt;td&gt;App + .NET runtime&lt;/td&gt;
&lt;td&gt;Single ~6MB binary&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gap narrowed once .NET's JIT fully warmed up under sustained load — it's genuinely competitive once hot. Rust's edge was most consistent in memory footprint and tail latency, which tracks: no GC means no surprise pause landing in your worst-case requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Rust felt rougher
&lt;/h2&gt;

&lt;p&gt;To keep this balanced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compile times&lt;/strong&gt; grew noticeably slower than C#'s as the project did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem gaps&lt;/strong&gt; — some things .NET gives you out of the box (certain auth patterns, mature ORMs) took more assembly in Rust's crates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The learning curve is real.&lt;/strong&gt; Not the right moment to introduce it if your team is shipping next week.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When I'd reach for which
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;I'd reach for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Typical CRUD app / internal business tool&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;C#&lt;/strong&gt; — EF Core and the .NET ecosystem get you there faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team is already deep in .NET, tight deadline&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;C#&lt;/strong&gt; — the right call, no reason to add a learning curve under pressure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time systems where latency predictability matters&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Rust&lt;/strong&gt; — no GC pause means a flatter tail latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory-constrained environments (containers, edge, embedded)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Rust&lt;/strong&gt; — the footprint difference is real and adds up at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need a small, dependency-free deployable binary&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Rust&lt;/strong&gt; — one binary, nothing else to ship&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich enterprise integrations (auth providers, ORMs, admin tooling)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;C#&lt;/strong&gt; — still the deeper batteries-included ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want to learn something that reshapes how you think about memory and concurrency&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Rust&lt;/strong&gt; — worth the rough two weeks even if you never ship it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So, am I dropping C#?
&lt;/h2&gt;

&lt;p&gt;Not even close. For CRUD apps and business logic, C# is still one of the most productive languages I know, and that hasn't changed. What changed is that I now have another tool for the specific cases where predictable latency and a small footprint actually matter — and Rust earned that spot honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're curious about Rust coming from C
&lt;/h2&gt;

&lt;p&gt;If you want the official deep reference, &lt;a href="https://doc.rust-lang.org/book/" rel="noopener noreferrer"&gt;The Rust Book&lt;/a&gt; is where most of the Rust community points beginners first. Alongside that, I also put together a helpful website while working through ownership and borrowing myself — plain explanations, runnable examples, nothing assumed: &lt;strong&gt;&lt;a href="https://rusttutorial.org" rel="noopener noreferrer"&gt;Rust Tutorial&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you're a C#/.NET dev thinking about giving Rust a try, either one is a solid place to start.&lt;/p&gt;

&lt;p&gt;Have you made a similar jump — from C#, Java, Go, or anywhere else? Curious what surprised you.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Compared TOON vs Minified JSON Using OpenAI’s Tokenizer</title>
      <dc:creator>Khaja Hussain</dc:creator>
      <pubDate>Tue, 12 May 2026 00:29:29 +0000</pubDate>
      <link>https://dev.to/khaja_hussain_db1f84efe83/i-compared-toon-vs-minified-json-using-openais-tokenizer-31d0</link>
      <guid>https://dev.to/khaja_hussain_db1f84efe83/i-compared-toon-vs-minified-json-using-openais-tokenizer-31d0</guid>
      <description>&lt;p&gt;Recently I noticed a lot of developers talking about TOON:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/toon-format/toon" rel="noopener noreferrer"&gt;TOON GitHub repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea behind TOON is interesting. Instead of sending traditional JSON, TOON tries to reduce token usage and make data more compact for LLMs.&lt;/p&gt;

&lt;p&gt;Since token costs are becoming a real concern for AI products, I wanted to test it myself.&lt;/p&gt;

&lt;p&gt;Not theoretically.&lt;br&gt;
Not with benchmarks from slides.&lt;br&gt;
Just a simple real-world comparison.&lt;/p&gt;

&lt;p&gt;I used OpenAI’s tokenizer tool and compared:&lt;/p&gt;

&lt;p&gt;TOON format&lt;br&gt;
Minified JSON&lt;/p&gt;

&lt;p&gt;For the conversion process, I used:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jsontotable.io/json-to-toon" rel="noopener noreferrer"&gt;JSON to TOON Converter&lt;/a&gt;&lt;br&gt;
&lt;a href="https://jsontotable.org" rel="noopener noreferrer"&gt;JSON Parser&lt;/a&gt;&lt;br&gt;
&lt;a href="https://jsonformatter.ai" rel="noopener noreferrer"&gt;JSON formatter&lt;/a&gt;&lt;br&gt;
&lt;a href="https://comparetext.org" rel="noopener noreferrer"&gt;Compare Text&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Minified JSON:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;{"user":{"id":1001,"name":"Khaja","email":"&lt;a href="mailto:khaja@example.com"&gt;khaja@example.com&lt;/a&gt;","roles":["admin","developer"],"settings":{"theme":"dark","notifications":true}}}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TOON version:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;user:&lt;br&gt;
  id: 1001&lt;br&gt;
  name: Khaja&lt;br&gt;
  email: &lt;a href="mailto:khaja@example.com"&gt;khaja@example.com&lt;/a&gt;&lt;br&gt;
  roles[2]: admin,developer&lt;br&gt;
  settings:&lt;br&gt;
    theme: dark&lt;br&gt;
    notifications: true&lt;br&gt;
`&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TOON Result&lt;/strong&gt;: Tokens: 37&lt;br&gt;
&lt;strong&gt;Minified JSON Result&lt;/strong&gt;: Tokens: 38&lt;/p&gt;

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




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

&lt;p&gt;That means TOON saved only 1 token in this example.&lt;/p&gt;

&lt;p&gt;Honestly, that surprised me.&lt;/p&gt;

&lt;p&gt;So… Is TOON Useful?&lt;/p&gt;

&lt;p&gt;I think the answer is: yes, but with nuance.&lt;/p&gt;

&lt;p&gt;TOON is not “bad.”&lt;br&gt;
In fact, I actually like the direction.&lt;/p&gt;

&lt;p&gt;It makes developers think seriously about:&lt;/p&gt;

&lt;p&gt;token efficiency&lt;br&gt;
AI-friendly data formats&lt;br&gt;
prompt optimization&lt;br&gt;
serialization overhead&lt;/p&gt;

&lt;p&gt;Those are important conversations.&lt;/p&gt;

&lt;p&gt;But after testing it, I’m not convinced that TOON alone will dramatically reduce costs for most companies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In many cases&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;removing whitespace already gives huge savings&lt;br&gt;
gzip/brotli compression already works extremely well&lt;br&gt;
AI models are already heavily trained on JSON structures&lt;/p&gt;

&lt;p&gt;So the practical gains may be smaller than the hype suggests.&lt;/p&gt;

&lt;p&gt;But Small Savings Can Still Matter at Scale&lt;/p&gt;

&lt;p&gt;Here’s the interesting part.&lt;/p&gt;

&lt;p&gt;Even tiny optimizations matter when companies process millions of requests.&lt;/p&gt;

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

&lt;p&gt;10 million API calls&lt;br&gt;
large AI prompts&lt;br&gt;
multiple agents&lt;br&gt;
long conversation histories&lt;/p&gt;

&lt;p&gt;Saving even 1–2% tokens at scale could potentially save hundreds or thousands of dollars over time.&lt;/p&gt;

&lt;p&gt;So I do understand why people are excited about TOON.&lt;/p&gt;

&lt;p&gt;The Bigger Challenge: Ecosystem&lt;/p&gt;

&lt;p&gt;Personally, I think TOON’s biggest challenge is not token count.&lt;/p&gt;

&lt;p&gt;It’s ecosystem adoption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON already has&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;mature tooling&lt;br&gt;
validators&lt;br&gt;
parsers&lt;br&gt;
database support&lt;br&gt;
IDE integrations&lt;br&gt;
API ecosystem dominance&lt;/p&gt;

&lt;p&gt;Replacing that is extremely difficult.&lt;/p&gt;

&lt;p&gt;In real production systems, compatibility usually matters more than tiny syntax improvements.&lt;/p&gt;

&lt;p&gt;My Take&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After testing both formats, my conclusion is&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;TOON introduces interesting ideas&lt;br&gt;
token savings appear modest in smaller examples&lt;br&gt;
the ecosystem challenge is massive&lt;br&gt;
but the conversation around AI-native serialization formats is valuable&lt;/p&gt;

&lt;p&gt;I don’t think JSON is disappearing anytime soon.&lt;/p&gt;

&lt;p&gt;But I do think experiments like TOON push the industry forward.&lt;/p&gt;

&lt;p&gt;And honestly, that’s a good thing.&lt;/p&gt;

&lt;p&gt;Would love to hear what other developers think or any suggestion what to compare next.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>A free, high-performance backend for small projects — Cloudflare Workers in 10 minutes</title>
      <dc:creator>Khaja Hussain</dc:creator>
      <pubDate>Sun, 03 May 2026 11:00:22 +0000</pubDate>
      <link>https://dev.to/khaja_hussain_db1f84efe83/a-free-high-performance-backend-for-small-projects-cloudflare-workers-in-10-minutes-4769</link>
      <guid>https://dev.to/khaja_hussain_db1f84efe83/a-free-high-performance-backend-for-small-projects-cloudflare-workers-in-10-minutes-4769</guid>
      <description>&lt;p&gt;Every developer wants the same thing: a free way to run small backend tasks. A webhook for a side project. A contact form handler. An OG image generator. A small proxy to hide an API key. Things that handle a few hundred requests a week. They do not need a $20/month server, an Azure Function App you will forget about next month, or a new Lambda that takes an hour to set up.&lt;/p&gt;

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

&lt;p&gt;I have had this problem for years. Most of my work is in .NET shops — AWS for production, Azure for clients on the Microsoft stack. Both are great for real workloads. Both feel like overkill when the actual task is "send me an email when a Stripe customer pays."&lt;/p&gt;

&lt;p&gt;Last weekend I needed exactly that webhook. Spinning up a new Function App for one endpoint felt silly. Adding it to an existing production API would mix two unrelated things — the next person reading the code would wonder why a billing handler was sitting inside the customer-facing app. Every option I knew was way too much setup for one tiny task.&lt;/p&gt;

&lt;p&gt;So I finally tried something I had been avoiding for three years: &lt;strong&gt;&lt;a href="https://workers.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;.&lt;/strong&gt; It solved the problem in ten minutes. Free, no credit card, deployed to 300+ data centres. Here is how it went, and why I should have tried it sooner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I had been ignoring Cloudflare Workers
&lt;/h2&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No .NET story.&lt;/strong&gt; I work in C#. Writing JavaScript at the edge felt like a whole new world just to deploy one small function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The marketing made it look like big-company infra.&lt;/strong&gt; Every Workers post I had read mentioned Durable Objects, R2, KV, Queues, AI bindings. It looked like a big platform you commit to, not a tool you grab for one task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I thought the free tier was a trick.&lt;/strong&gt; "100,000 requests per day" sounded like a number with hidden conditions and a credit-card requirement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of those were true.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10 minutes
&lt;/h2&gt;

&lt;p&gt;Install &lt;a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/" rel="noopener noreferrer"&gt;Wrangler&lt;/a&gt;, the Cloudflare CLI:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The login opens a browser. Click approve and you are done. No credit card asked. I checked twice — I have been burned before.&lt;/p&gt;

&lt;p&gt;Scaffold a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create cloudflare@latest stripe-webhook
&lt;span class="nb"&gt;cd &lt;/span&gt;stripe-webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick "Hello World" Worker, JavaScript, skip the deploy step. The generator puts everything in one folder. Open &lt;code&gt;src/index.js&lt;/code&gt; and replace the default code with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST only&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;405&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid JSON&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checkout.session.completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&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="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount_total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&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="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer_email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.resend.com/emails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESEND_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alerts@mydomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;me@mydomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`New payment: $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; just paid $&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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;Run it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;span class="c"&gt;# Ready on http://localhost:8787&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send a fake Stripe payload with curl. Worked on the first try.&lt;/p&gt;

&lt;p&gt;Deploy it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight seconds later it printed a &lt;code&gt;*.workers.dev&lt;/code&gt; URL. Total bundle size: 1.2 KB. I pointed Stripe's webhook config at that URL, sent a test event from the Stripe dashboard, and the email arrived in my inbox before the dashboard finished refreshing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprised me
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developers.cloudflare.com/workers/platform/limits/" rel="noopener noreferrer"&gt;free tier&lt;/a&gt; really is 100,000 requests a day. No credit card. The Worker runs in 300+ Cloudflare data centres at the same time. A Stripe webhook from San Francisco hits the San Jose data centre, my email goes out from Resend's nearest server, and the whole round trip takes under 200ms. No cold start. No Function App I have to remember to delete next month before it starts billing me.&lt;/p&gt;

&lt;p&gt;If you store a webhook secret with &lt;code&gt;wrangler secret put STRIPE_WEBHOOK_SECRET&lt;/code&gt; and verify the signature (you should — Stripe sends a &lt;a href="https://stripe.com/docs/webhooks/signatures" rel="noopener noreferrer"&gt;&lt;code&gt;Stripe-Signature&lt;/code&gt;&lt;/a&gt; header for this), the whole thing is still about 60 lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  One gotcha worth knowing
&lt;/h2&gt;

&lt;p&gt;If the request body is invalid JSON, &lt;code&gt;request.json()&lt;/code&gt; throws a &lt;code&gt;SyntaxError&lt;/code&gt; and Cloudflare returns a generic 500. That is bad for any user-facing endpoint, and it is worse for Stripe — Stripe retries on 5xx errors, so a broken parser quietly turns into duplicate retries while your dashboard looks fine. Wrap the parse in a try/catch and return a 400 with a clear message. Stripe webhook payloads are also big and hard to read; pretty-printing them with a &lt;a href="https://jsontotable.io/json-formatter" rel="noopener noreferrer"&gt;JSON formatter&lt;/a&gt; and &lt;a href="https://jsontotable.org" rel="noopener noreferrer"&gt;JSON viewer&lt;/a&gt; makes debugging much faster. I learned the try/catch lesson the hard way — one panicked late-night Stripe dashboard refresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently next time
&lt;/h2&gt;

&lt;p&gt;Use Workers first for any small task that needs an HTTP endpoint. That is a much bigger category than just "webhook" — link shorteners, OG image generators, form handlers, edge auth, JSON proxies, internal Slack bots. Trying it is one &lt;code&gt;npm create&lt;/code&gt; command away.&lt;/p&gt;

&lt;p&gt;I feel a bit silly for waiting three years. The mental model is one sentence: a Worker is a function that takes a &lt;code&gt;Request&lt;/code&gt; and returns a &lt;code&gt;Response&lt;/code&gt;. That is the whole platform. Everything else (KV, Durable Objects, AI bindings) is optional. If you want the full step-by-step instead of the highlights here, &lt;a href="https://jsonparser.ai/blog/json/deploy-free-json-api-cloudflare-workers" rel="noopener noreferrer"&gt;this Cloudflare Workers deploy guide&lt;/a&gt; covers the whole setup with a real validating endpoint, the free-tier limits in detail, and a few more gotchas to know before you ship.&lt;/p&gt;

&lt;p&gt;If you have been avoiding Workers like I was, try it on the next small task you hit. Fifteen minutes will tell you if it fits how you think. For me, it did.&lt;/p&gt;

</description>
      <category>backend</category>
    </item>
    <item>
      <title>Understanding Protocol Buffers: A Fast Alternative to JSON</title>
      <dc:creator>Khaja Hussain</dc:creator>
      <pubDate>Sun, 15 Dec 2024 20:36:23 +0000</pubDate>
      <link>https://dev.to/khaja_hussain_db1f84efe83/understanding-protocol-buffers-a-fast-alternative-to-json-ga2</link>
      <guid>https://dev.to/khaja_hussain_db1f84efe83/understanding-protocol-buffers-a-fast-alternative-to-json-ga2</guid>
      <description>&lt;p&gt;In the world of data interchange, JSON (JavaScript Object Notation) has been a long-standing favourite. It’s simple, human-readable, and works seamlessly across platforms. For many use cases, JSON is “good enough.” But as systems scale, and the need for speed and efficiency increases, JSON’s text-based format can become a bottleneck.&lt;/p&gt;

&lt;p&gt;That’s where Protocol Buffers (Protobuf) come in. Developed by Google, Protobuf is a powerful, compact, and lightning-fast data serialization format that has become a popular choice for modern applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Consider Protocol Buffers Over JSON?&lt;/strong&gt;&lt;br&gt;
Compactness: JSON’s text-based format can result in larger payloads. Protobuf, on the other hand, uses a binary format, which dramatically reduces the size of the data.&lt;/p&gt;

&lt;p&gt;Speed: Protobuf’s binary format is faster to serialize (convert data to a transferable format) and deserialize (convert back to usable data).&lt;/p&gt;

&lt;p&gt;Schema Evolution: Protobuf includes a schema that defines the structure of your data, making it easier to evolve your APIs without breaking backward compatibility.&lt;/p&gt;

&lt;p&gt;Efficiency at Scale: For applications with high traffic or limited bandwidth (e.g., mobile apps, IoT devices), Protobuf’s efficiency can result in lower latency and better performance.&lt;/p&gt;

&lt;p&gt;Protocol Buffers: A Smarter Way to Handle Data&lt;br&gt;
In the world of data interchange, JSON (JavaScript Object Notation) has been a long-standing favorite. It’s simple, human-readable, and works seamlessly across platforms. For many use cases, JSON is “good enough.” But as systems scale, and the need for speed and efficiency increases, JSON’s text-based format can become a bottleneck.&lt;/p&gt;

&lt;p&gt;That’s where Protocol Buffers (Protobuf) come in. Developed by Google, Protobuf is a powerful, compact, and lightning-fast data serialization format that has become a popular choice for modern applications.&lt;/p&gt;

&lt;p&gt;Why Consider Protocol Buffers Over JSON?&lt;br&gt;
Compactness: JSON’s text-based format can result in larger payloads. Protobuf, on the other hand, uses a binary format, which dramatically reduces the size of the data.&lt;/p&gt;

&lt;p&gt;Speed: Protobuf’s binary format is faster to serialize (convert data to a transferable format) and deserialize (convert back to usable data).&lt;/p&gt;

&lt;p&gt;Schema Evolution: Protobuf includes a schema that defines the structure of your data, making it easier to evolve your APIs without breaking backward compatibility.&lt;/p&gt;

&lt;p&gt;Efficiency at Scale: For applications with high traffic or limited bandwidth (e.g., mobile apps, IoT devices), Protobuf’s efficiency can result in lower latency and better performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Quick Comparison: JSON vs. Protobuf&lt;/strong&gt;&lt;br&gt;
Let’s take a simple example. Imagine you’re sending information about a user:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using JSON:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "id": 123,
  "name": "John Doe",
  "email": "john.doe@example.com"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is straightforward and human-readable. But it’s also relatively large because of all the extra characters like {}, :, and the field names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Protobuf:&lt;/strong&gt;&lt;br&gt;
First, you define a schema (usually in a .proto file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When serialized to binary format, this same data is compacted into a tiny, efficient payload—unreadable to humans but incredibly fast for computers to process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to Stick with JSON&lt;/strong&gt;&lt;br&gt;
JSON is still a fantastic choice for many use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If human-readability is a priority (e.g., logging or configuration files).&lt;/li&gt;
&lt;li&gt;For simple, low-traffic systems where performance isn’t critical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Use Protobuf&lt;/strong&gt;&lt;br&gt;
If your application needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle large-scale data exchange (e.g., microservices, real-time APIs).&lt;/li&gt;
&lt;li&gt;Operate under bandwidth constraints (e.g., mobile or IoT devices).&lt;/li&gt;
&lt;li&gt;Ensure compatibility while evolving the API schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JSON is like the comfortable, everyday car you drive around town—reliable and easy to use. Protobuf, on the other hand, is a sleek sports car—designed for speed, efficiency, and high performance. While JSON is great for most day-to-day tasks, Protobuf shines when you need to go the extra mile.&lt;/p&gt;

&lt;p&gt;So, whether you stick with JSON or make the leap to Protobuf depends on your needs. But if you’re building for the future and performance is key, Protobuf is a solid choice to keep things running smoothly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some helpful links to understand Protobuf and Json:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://protobuf.dev/" rel="noopener noreferrer"&gt;Protocol Buffers Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/protocolbuffers/protobuf" rel="noopener noreferrer"&gt;protobuf&lt;/a&gt;&lt;br&gt;
&lt;a href="https://en.wikipedia.org/wiki/JSON" rel="noopener noreferrer"&gt;Json&lt;/a&gt;&lt;br&gt;
&lt;a href="https://jsontotable.org/Blog/Api-development.html" rel="noopener noreferrer"&gt;JSON in API Development&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
  </channel>
</rss>
