<?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: sudokar</title>
    <description>The latest articles on DEV Community by sudokar (@sudokar).</description>
    <link>https://dev.to/sudokar</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%2F534385%2F8b9f629f-074c-461f-b1d6-94f74d177787.jpg</url>
      <title>DEV Community: sudokar</title>
      <link>https://dev.to/sudokar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sudokar"/>
    <language>en</language>
    <item>
      <title>Test Rocket: Rethinking the Test Pyramid for the AI Era</title>
      <dc:creator>sudokar</dc:creator>
      <pubDate>Tue, 18 Aug 2026 06:33:10 +0000</pubDate>
      <link>https://dev.to/sudokar/test-rocket-rethinking-the-test-pyramid-for-the-ai-era-2d6n</link>
      <guid>https://dev.to/sudokar/test-rocket-rethinking-the-test-pyramid-for-the-ai-era-2d6n</guid>
      <description>&lt;p&gt;Every few years someone redraws the testing pyramid and gives it a new name. Test Pyramid, Testing Honeycomb, Testing Trophy: each one showed up because the economics of building software had shifted just enough that the old shape stopped making sense.&lt;/p&gt;

&lt;p&gt;I think AI-assisted development just did that again, and this time the shape looks less like a pyramid and more like a rocket. So that's what I'm calling it, the &lt;strong&gt;Test Rocket&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we got from Pyramid to Trophy
&lt;/h2&gt;

&lt;p&gt;The classic Test Pyramid says: write a lot of fast, cheap unit tests, fewer integration tests, and only a handful of end-to-end tests. The logic held up for a long time. Unit tests are quick to write and quick to run, while anything higher up the stack tends to be slower, pricier, and more fragile.&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%2Fok9iwyx05ryxov3kbc10.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%2Fok9iwyx05ryxov3kbc10.png" alt="Test Pyramid" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That worked fine until applications got more distributed. Once your system is a bunch of services talking to each other, most of the interesting bugs stop living inside a single function and start living in the gaps between components: a serialization mismatch here, a timeout assumption there.&lt;/p&gt;

&lt;p&gt;Spotify ran into this with microservices and wrote up the &lt;strong&gt;Testing Honeycomb&lt;/strong&gt;. Their point was simple: a service's real complexity is in how it talks to databases and other services, and unit tests that mock all of that away end up testing implementation details instead of behavior. So they leaned harder into integration tests and kept the unit layer intentionally small.&lt;/p&gt;

&lt;p&gt;Kent C. Dodds made a similar call for frontend work with the &lt;strong&gt;Testing Trophy&lt;/strong&gt;: static analysis as the base, then a fairly small unit layer, then integration tests doing most of the heavy lifting in the middle, then a thin sliver of E2E on top.&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%2Fpifp7roqkf5hrdgw3amz.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%2Fpifp7roqkf5hrdgw3amz.png" alt="Testing Trophy" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interesting move here had nothing to do with unit tests being bad. It was a challenge to the assumption that integration tests have to be slow and expensive. Modern tooling made them fast enough to use liberally, and they tend to catch more real bugs per test because they're exercising multiple pieces at once.&lt;/p&gt;

&lt;p&gt;Honeycomb and Trophy were both making the same underlying argument. A pile of isolated unit tests is a bad investment when the behavior you actually care about only shows up when components interact.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI changes the economics again
&lt;/h2&gt;

&lt;p&gt;Neither the Pyramid, the Honeycomb, nor the Trophy accounted for something that simply didn't exist when they were written: &lt;strong&gt;unit tests can be an extremely fast feedback loop for an AI coding agent, not just a safety net for humans.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Watch an agent work for a while and the pattern becomes clear. It edits some code, runs a large pile of unit tests, reads the failures, and revises. Then does it again. And again.&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%2Fc60awc3xms42sr2wez0a.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%2Fc60awc3xms42sr2wez0a.png" alt="Agentic Feedback Loop" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That loop changes what a unit-test suite is &lt;em&gt;for&lt;/em&gt;. It's no longer just something a developer maintains to catch regressions later. It's part of the agent's own working loop, telling it second by second whether the change it just made actually did what it was supposed to do.&lt;/p&gt;

&lt;p&gt;And that changes the cost equation. Writing and maintaining a hundred extra unit tests used to mean a hundred extra units of human effort: someone had to write them, review them, keep them passing. An agent can generate a useful test in seconds. So the old question, "can we afford this many unit tests," mostly stops being the right question. What matters now is: &lt;strong&gt;do these tests actually tell the agent (or the reviewer) something true about behavior?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Test Rocket
&lt;/h2&gt;

&lt;p&gt;The Test Rocket keeps the same four layers as the Trophy (static analysis, unit, integration, E2E) but shifts where the weight sits.&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%2Fkki5qjnllyzyrtxrxq7w.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%2Fkki5qjnllyzyrtxrxq7w.png" alt="Test Rocket" width="800" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The name isn't just decoration. Static analysis is the thrust base: broad, unglamorous, doing the unsexy work of keeping the thing from tipping over. Unit and integration tests make up the body, two full stages instead of one thin and one thick, because both are doing real propulsion now. Neither one is sized to be an afterthought. And E2E sits up top as the nose cone: small, essential, and not where the mass or the power lives.&lt;/p&gt;

&lt;p&gt;The point of the Rocket isn't a specific ratio you're supposed to hit. It's this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build out real unit &lt;em&gt;and&lt;/em&gt; integration feedback loops, and keep the higher layers focused on the kind of confidence only they can give you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Static analysis&lt;/strong&gt; doesn't stay flat either. It's the cheapest, fastest checkpoint there is, faster than even the first unit test run, so it's the natural place to catch an agent's mistakes before they cost a full test cycle. Teams working with agents lean on it harder: stricter type checking, custom lint rules for house conventions the agent keeps missing, dependency and security scans, complexity gates on generated code. Same reason the unit layer grows: it's fast enough to run on every single change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit tests&lt;/strong&gt; are where the shift really shows up. In an AI-driven workflow they're the fastest way for an agent to check "did I just break something," which makes a much bigger unit layer worth having, even when the tests are fully isolated.&lt;/p&gt;

&lt;p&gt;But "bigger" only means something if you know what's going in it. Think of it less as Honeycomb or Trophy's unit layer with the volume knob turned up, and more as new categories that were always worth writing but rarely survived the cost-benefit call a human had to make:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boundary and edge-case tests (nulls, empty collections, off-by-one ranges) that were correct in principle but never made the cut when someone had to write each one by hand.&lt;/li&gt;
&lt;li&gt;Property-based tests that check a rule holds across a generated range of inputs, instead of the two or three hand-picked examples a human had time for.&lt;/li&gt;
&lt;li&gt;A permanent regression test for every bug the agent fixes, so the same failure can't quietly come back later. Something teams always meant to do consistently and rarely did.&lt;/li&gt;
&lt;li&gt;Characterization tests that pin down existing behavior before a refactor, so both the agent and the reviewer have a safety net for changes that used to be too risky to attempt.&lt;/li&gt;
&lt;li&gt;Narrow, single-behavior tests around whatever the agent is actively touching, so a failure points at exactly what broke instead of a broad integration failure that still needs to be tracked down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are exotic. They're the tests a careful developer always knew were worth having and rarely had the hours to write. The idea of a good unit test hasn't changed. What changed is that these specific kinds finally got cheap enough to actually exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration tests&lt;/strong&gt; still do what unit tests can't: prove that the real database, the real HTTP boundary, the real message queue actually work together. AI helps here too, generating fixtures and test containers faster, but standing up a real dependency and waiting on real I/O doesn't collapse the way authoring cost did for unit tests. That's why this layer doesn't need its own "what's new" list: it was already substantial under Honeycomb and Trophy. The Rocket doesn't trade it away to fund the unit layer, it keeps both substantial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2E tests&lt;/strong&gt; stay at the top, validating the handful of journeys that matter most. Cheap generation doesn't change their calculus the way it does unit tests or static analysis, because their cost was never about who writes them. It's the run itself: a real browser, a real backend, minutes instead of milliseconds, and flakiness no amount of AI-assisted authoring fixes. An agent can write fifty E2E scripts as easily as five, but running all fifty on every change is still slow. They stay expensive enough that you don't want them carrying weight unit and integration already cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, but weren't unit tests supposed to be the problem?
&lt;/h2&gt;

&lt;p&gt;Fair question, and the old criticism still holds in one specific way: a bad unit test is still a bad unit test. If it's asserting implementation details, mocking every single collaborator, breaking every time you rename a variable, or existing purely to nudge a coverage number, none of that gets better just because an AI wrote it faster.&lt;/p&gt;

&lt;p&gt;What actually changed is the economics, not the standard for what makes a test good. The old argument against a big unit layer was about human time: writing and babysitting a large pile of isolated tests wasn't worth it when integration tests gave you more confidence per test written. Once the marginal cost of producing a &lt;em&gt;useful&lt;/em&gt; unit test drops, and its value as instant feedback to an agent goes up, that math just doesn't hold the way it used to.&lt;/p&gt;

&lt;p&gt;So no, Test Rocket isn't secretly the Pyramid wearing a new hat. It's not arguing that unit tests should dominate again, and it's not arguing for a specific 50/50 split either, that would just be swapping one arbitrary ratio for another. What it's arguing is narrower: neither unit nor integration should be the thin layer anymore. Honeycomb and Trophy each picked one of the two to keep small, on the assumption that a large amount of the other layer was expensive to produce and maintain. Once that assumption stops holding for unit tests specifically, there's no longer a good reason to keep either layer deliberately thin. The exact ratio between them should come from where your bugs actually live, not from how much either layer used to cost to write.&lt;/p&gt;

&lt;p&gt;Each layer is still answering a different question:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests: did this specific piece of local behavior just break?&lt;/li&gt;
&lt;li&gt;Integration tests: do the real components actually work together?&lt;/li&gt;
&lt;li&gt;E2E tests: does the journey a user cares about still work end to end?&lt;/li&gt;
&lt;li&gt;Static analysis: is this code even structurally sound?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A caveat that matters more than it sounds like it should
&lt;/h2&gt;

&lt;p&gt;This is where I'd push back on my own idea a little, because there's a real trap here, and it's not the one people usually reach for first.&lt;/p&gt;

&lt;p&gt;The obvious worry is cost: teams churning out low-value tests because it's cheap now. The sharper worry is Honeycomb's original one: heavily mocked, isolated unit tests get coupled to implementation details, which makes them actively harmful during refactors, not just wasteful to write. Cheap AI-generated tests make that worse before they make it better. An agent looking at code it just wrote has every incentive to generate a test that mirrors that code's structure, since that's the fastest path to green. A bigger, cheaper unit layer can easily reintroduce exactly the failure mode Honeycomb warned about, just at higher volume, especially if the same pass that writes the implementation also writes the tests and grades itself against them. A test derived from the code it's checking will happily assert "the code does what the code does," which says nothing about whether it does what it's supposed to.&lt;/p&gt;

&lt;p&gt;And routing every generated test through a human reviewer doesn't fix this. It just moves the bottleneck somewhere less visible and kills the speed that was the whole point. The fix needs to be automated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Derive tests from the spec, not the code.&lt;/strong&gt; Generated from what the feature should do, in a pass that never sees the implementation. This is the direct answer to Honeycomb: a test that never saw the code can't get coupled to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derive tests from the plan&lt;/strong&gt; the agent committed to before writing code. A weaker independence guarantee than spec-derived (same reasoning wrote both), but a useful second signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the test before the code (TDD).&lt;/strong&gt; If the test has to pass &lt;em&gt;before&lt;/em&gt; the implementation is "done," it can't have been shaped to match code that doesn't exist yet. This bakes spec-first independence into the workflow itself, rather than relying on discipline to keep the two passes separate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a second, adversarial agent&lt;/strong&gt; to hunt for disagreements between test and behavior, instead of trusting the implementation's own context to vouch for itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run mutation testing&lt;/strong&gt;, sparingly. It confirms the suite actually fails against injected faults, but running the whole suite once per mutant gets expensive fast. Treat it as a targeted, occasional check on changed code or high-risk paths, not a blanket policy. The adversarial-agent approach above scales better as a default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Human review still has a role spot-checking specs, but it can't be the main gate. That gate needs to move as fast as the agent it's checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;The Pyramid optimized for cost. Honeycomb and Trophy optimized for confidence and for catching failures at the boundaries. The Rocket adds a third thing to optimize for: how well the suite feeds an AI that's continuously generating and rewriting code.&lt;/p&gt;

&lt;p&gt;That changes the value of the fast layers. Static analysis becomes a stronger first checkpoint, catching cheap structural mistakes before they consume a test cycle. Unit tests provide fast behavioral feedback, while integration and E2E tests add progressively higher-confidence validation.&lt;/p&gt;

&lt;p&gt;Unit tests didn't get better as an idea. What they're used for, and what they cost to produce, changed underneath us.&lt;/p&gt;

&lt;p&gt;More tests turn out to be part of the answer, but they were never the point on their own. The point is a fast, layered system that gives an AI enough signal to move quickly, and gives engineers enough confidence to actually hit deploy.&lt;/p&gt;

&lt;p&gt;That's the &lt;strong&gt;Test Rocket&lt;/strong&gt; 🚀.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>software</category>
      <category>programming</category>
    </item>
    <item>
      <title>Seamless CJS and ESM: Building Dual-Format Packages with Nx</title>
      <dc:creator>sudokar</dc:creator>
      <pubDate>Sun, 26 Oct 2025 20:25:54 +0000</pubDate>
      <link>https://dev.to/sudokar/seamless-cjs-and-esm-building-dual-format-packages-with-nx-lda</link>
      <guid>https://dev.to/sudokar/seamless-cjs-and-esm-building-dual-format-packages-with-nx-lda</guid>
      <description>&lt;h2&gt;
  
  
  🚀 Intro
&lt;/h2&gt;

&lt;p&gt;The JavaScript module ecosystem is in transition. ESM (ECMAScript Modules) has become the modern standard, but CommonJS (CJS) remains deeply entrenched in the ecosystem.&lt;/p&gt;

&lt;p&gt;ECMAScript Modules (ESM), introduced in ES6 (2015), is the standardised JavaScript module system designed to work seamlessly across both browsers and servers, unifying how JavaScript handles modularity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core advantages of ESM:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Enables better performance and optimisation&lt;/strong&gt; through asynchronous loading and built-in tree-shaking via static analysis, allowing browsers and bundlers to eliminate unused code automatically.&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Provides universal compatibility&lt;/strong&gt; with native browser support (no bundlers required) while also working seamlessly on the server-side, making it suitable for both environments.&lt;/li&gt;
&lt;li&gt;✨ &lt;strong&gt;Offers modern developer features&lt;/strong&gt;, including top-level await support and dynamic imports in both module systems, giving developers more flexibility in how they structure and load their code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a library author, it’s important to support both legacy CommonJS projects and modern ESM projects. The solution is to publish a single npm package compatible with both module formats. This guide demonstrates how to build and publish a universal NPM package using the &lt;a href="https://nx.dev" rel="noopener noreferrer"&gt;Nx Dev Toolkit&lt;/a&gt; and &lt;a href="https://rollupjs.org" rel="noopener noreferrer"&gt;Rollup&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Setting up Nx Workspace
&lt;/h2&gt;

&lt;p&gt;Let’s start! Setting up an Nx workspace is the first step to building a supercharged, scalable project structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a publishable library (with rollup bundler)&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;bunx create-nx-workspace@22 dual-modules-workspace

cd dual-modules-workspace
bunx nx g @nx/js:lib packages/my-library --name my-library --importPath @dual-modules-workspace/my-library --bundler rollup --linter eslint --publishable --unitTestRunner vitest --setParserOptionsProject --useProjectJson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
packages
└── my-library
    ├── eslint.config.mjs
    ├── package.json
    ├── project.json
    ├── README.md
    ├── rollup.config.cjs
    ├── src/
    ├── tsconfig.json
    ├── tsconfig.lib.json
    ├── tsconfig.spec.json
    └── vite.config.ts
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔧 Setting up configurations
&lt;/h2&gt;

&lt;p&gt;Now that the workspace is ready, it’s time to fine-tune the engine—let’s tweak the configs so everything runs smoothly and efficiently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollup configuration&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;// packages/my-library/rollup.config.cjs

const { withNx } = require('@nx/rollup/with-nx');

module.exports = withNx(
  {
    main: './src/index.ts',
    outputPath: './dist',
    tsConfig: './tsconfig.lib.json',
    compiler: 'swc',
    format: ['esm', 'cjs'],
  },
  {
    output: {
      preserveModules: true, // Better tree-shaking
      preserveModulesRoot: '.', // Preserve module structure in output
    },
  }
);

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Package configuration&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;// packages/my-library/package.json

{
  "name": "@dual-modules-workspace/my-library",
  "version": "0.0.1",
  "main": "./dist/index.cjs.js", // generated esm to cjs
  "module": "./dist/index.esm.js",
  "types": "./dist/index.d.ts", // generated index.esm.d.ts to index.d.ts
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "types": "./dist/index.d.ts", // generated index.esm.d.ts to index.d.ts
      "require": "./dist/index.cjs.js", // Support for common js
      "import": "./dist/index.esm.js",
      "default": "./dist/index.esm.js" // Keep ESM as default
    }
  },
  "sideEffects": false // // Better tree-shaking
  ...
}

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;exports&lt;/code&gt; field in &lt;strong&gt;package.json&lt;/strong&gt; routes &lt;code&gt;import&lt;/code&gt; to ESM and &lt;code&gt;require&lt;/code&gt; to CJS automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typescript declarations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Optionally, we can disable source maps added to the package by setting the property &lt;code&gt;sourceMap&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; in the tsconfig.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tsconfig.base.json (not tsconfig.json)

{
  "compilerOptions": {
  ...
  "sourceMap": false,
  ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 Build
&lt;/h2&gt;

&lt;p&gt;It’s showtime! Let’s run &lt;code&gt;nx build&lt;/code&gt; and watch out library come to life after all that setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bun nx build my-library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/my-library/dist/
├── index.cjs.js
├── index.d.ts
├── index.esm.js
└── src
    ├── index.d.ts
    └── lib
        ├── my-library.cjs.js
        ├── my-library.d.ts
        └── my-library.esm.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🚢 Publishing
&lt;/h2&gt;

&lt;p&gt;Nx comes with a built-in command to seamlessly publish our packages to the npm registry. It also natively supports semantic versioning with conventional commits, making release management smooth and predictable. To get started, follow the step-by-step guide &lt;a href="https://nx.dev/docs/guides/nx-release/release-npm-packages" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and explore additional release configuration options &lt;a href="https://nx.dev/docs/guides/nx-release" rel="noopener noreferrer"&gt;here&lt;/a&gt; to tailor the setup to our project’s needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎉 Outro
&lt;/h2&gt;

&lt;p&gt;Building universal npm packages with Nx and Rollup becomes straightforward once you grasp the key concepts. By setting up dual-format outputs, organising our package.json exports correctly, and validating our builds with the right tools, we ensure our library runs smoothly across the entire JavaScript ecosystem.&lt;/p&gt;

&lt;p&gt;With our library now ready to serve both legacy and modern JavaScript consumers, you’ve crafted a package that’s versatile, robust, and future-proof.&lt;/p&gt;

&lt;p&gt;If you found this guide helpful, don’t forget to leave a reaction or comment on this post — it helps me understand what content resonates with you and motivates me to create more like this!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building Well-Architected AWS AppSync GraphQL APIs</title>
      <dc:creator>sudokar</dc:creator>
      <pubDate>Mon, 14 Oct 2024 08:10:51 +0000</pubDate>
      <link>https://dev.to/sudokar/building-well-architected-aws-appsync-graphql-apis-1a6c</link>
      <guid>https://dev.to/sudokar/building-well-architected-aws-appsync-graphql-apis-1a6c</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/appsync/" rel="noopener noreferrer"&gt;AWS AppSync&lt;/a&gt; is a fully managed serverless service from AWS for building scalable and resilient GraphQL APIs.&lt;/p&gt;

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

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/framework/welcome.html" rel="noopener noreferrer"&gt;AWS Well-Architected Framework&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/serverless-applications-lens/welcome.html" rel="noopener noreferrer"&gt;Serverless Application Lens&lt;/a&gt; offer best practices for building serverless applications in the cloud. This post will show how to apply these practices when creating GraphQL APIs with AWS AppSync, aligned with the Well-Architected pillars.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational Excellence
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Focuses on running and monitoring systems to deliver business value, and continuously improving processes and procedures.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Enable Observability
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Observability is the ability to understand the internal state of a system based on the data it generates, which includes logs, metrics, and traces. Together, these components enable you to monitor, troubleshoot, and optimise system behaviour efficiently.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AWS AppSync has built-in integration with &lt;a href="https://aws.amazon.com/cloudwatch/" rel="noopener noreferrer"&gt;AWS Cloudwatch&lt;/a&gt; for logs and metrics and &lt;a href="https://aws.amazon.com/xray/" rel="noopener noreferrer"&gt;X-Ray&lt;/a&gt; for tracing.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync supports two types of logging: Request-level logging and Field-level logging. Field-level logging supports five logging levels (&lt;code&gt;None&lt;/code&gt;, &lt;code&gt;Error&lt;/code&gt;, &lt;code&gt;Info&lt;/code&gt;, &lt;code&gt;Debug&lt;/code&gt; and &lt;code&gt;All&lt;/code&gt;), providing flexibility in the amount of detail captured for each GraphQL query.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync captures a variety of default and enhanced metrics with detailed data points to monitor API performance and usage.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync uses X-Ray to capture traces, allowing us to track the lifecycle of API requests, measure execution times for detailed performance analysis, and identify errors for easier troubleshooting.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When using Lambda resolvers, implement logging and manually instrument traces using AWS X-Ray SDK. Utilise libraries like &lt;a href="https://github.com/aws-powertools/" rel="noopener noreferrer"&gt;Powertools for lambda&lt;/a&gt; and &lt;a href="https://middy.js.org/" rel="noopener noreferrer"&gt;Middy&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Use Infrastructure as Code (IaC)
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;For prototyping and deploying, consider using Infrastructure as Code (IaC) to enable tracking of changes using version control and releases. Cloudformation, Terraform, AWS SAM, and AWS CDK are a few examples.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;If you are using AWS CDK, this &lt;a href="https://constructs.dev/packages/cdk-appsync-typescript-resolver/v/0.0.27?lang=typescript" rel="noopener noreferrer"&gt;CDK construct&lt;/a&gt; simplifies writing AppSync resolvers using Typescript.&lt;/p&gt;

&lt;h4&gt;
  
  
  Test, Test, Test
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Testing is essential for building and releasing a bug-free system. From Day 0, create and implement a testing strategy for APIs built using AppSync.&lt;/p&gt;




&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Ensures data protection, confidentiality, and integrity through identity management, security monitoring, and incident response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Set up Authorisation
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Unprotected APIs are easy targets for attackers seeking valuable data. AppSync offers built-in authorization support with API keys, IAM, OIDC, Cognito user pools, and Lambda authorizers.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Use Lambda authoriser for any custom/proprietary authorisation logic.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync also has Cognito group-based authorisation that can be easily defined in GraphQL schema.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When using API keys, consider rotating them for better security and set up monitoring API key usage to identify any unusual activity, as they are less secure when compared to other types.&lt;/p&gt;

&lt;h4&gt;
  
  
  Use Private API
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;For applications running in AWS VPC or on-premises that need access to AppSync APIs, Create private APIs to reduce the attack surface.&lt;/p&gt;

&lt;h4&gt;
  
  
  Set up Web Application Firewall (WAF)
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a href="https://aws.amazon.com/waf/" rel="noopener noreferrer"&gt;AWS WAF&lt;/a&gt; (Web Application Firewall) is a managed service that helps protect web applications from common threats and vulnerabilities&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Configuring AWS Web Application Firewall (WAF) for AWS AppSync helps to enhance the security of your GraphQL APIs by providing multiple layers of protection.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create roles with the least permissions
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Roles created for resolvers should be set up with the most limited permissions to adhere to the principle of least privilege.&lt;/p&gt;

&lt;h4&gt;
  
  
  Keep secrets away from resolver code
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;You should not hard code API keys or secrets in resolver code; Instead, store it in Secrets Manager or Parameter Store and retrieve it. &lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;With a pipeline resolver, the operation to fetch secrets can be executed before processing the external request. One downside would be it will impact the performance as the resolver needs to fetch a secret for each API request.&lt;/p&gt;

&lt;h4&gt;
  
  
  Disable Introspection
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync supports GraphQL introspection, a feature that allows clients to query the schema of a GraphQL API. It enables users to discover the types, fields, queries, mutations, and subscriptions available in the API, as well as the relationships between them.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Disable introspection for public users when applicable to enhance security and protect against vulnerabilities and data breaches.&lt;/p&gt;

&lt;h4&gt;
  
  
  Configure query depth limits
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;In AWS AppSync, query depth limit refers to a safeguard mechanism that restricts the maximum depth of nested queries allowed in a single GraphQL request. This limit helps prevent excessively deep or complex queries that could lead to performance issues or denial-of-service (DoS) attacks on your API.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Enforce reasonable limits on how deep queries can go, ensuring better overall stability and security of the application.&lt;/p&gt;




&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Involves designing systems that recover from failures and dynamically meet demand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Leverage Caching
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync supports both full request and per-resolver caching. Enable caching to reduce the load on backend systems.&lt;/p&gt;

&lt;h4&gt;
  
  
  Build reliable Lambdas
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When using a Lambda resolver, implement retry logic to handle transient errors, allowing recovery and improving the function's reliability.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Allocate appropriate memory and set reasonable timeout limits based on the lambda's needs to prevent unnecessary failures.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;As we all know, &lt;em&gt;Everything fails all the time&lt;/em&gt;, implement error handling in Lambdas&lt;/p&gt;

&lt;h4&gt;
  
  
  Enable Pagination
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync uses token-based pagination; a token is used to specify the record after which additional items should be fetched, along with the page size.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Implement pagination to reduce the load on backend systems, improving API stability and reliability.&lt;/p&gt;




&lt;h3&gt;
  
  
  Performance Efficiency
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Emphasises using computing resources efficiently to meet system requirements and maintain responsiveness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Leverage Caching
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Caching boosts the performance and reduces the latency of APIs built with AppSync.&lt;/p&gt;

&lt;h4&gt;
  
  
  Avoid Lambda resolver
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AWS AppSync connects directly with data sources outside of Lambda. Using these connections directly reduces latency by eliminating one network hop, which enhances performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Optimise Lambda
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;When using Lambda resolvers, seek optimisations to enhance performance if Lambda is affecting overall latency.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Some optimisation techniques include reducing cold starts, keeping Lambda functions warm, enabling keep-alive for HTTP calls, configuring the required memory, using alternate runtimes like &lt;a href="https://github.com/awslabs/llrt" rel="noopener noreferrer"&gt;LLRT&lt;/a&gt; &amp;amp; &lt;a href="https://www.rust-lang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; and using connection pooling.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Consider trade-offs when optimising Lambdas, as premature optimisations may not be necessary and could offer a low Return on Investment (ROI).&lt;/p&gt;

&lt;h4&gt;
  
  
  Utilise Subscriptions Effectively
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AWS AppSync subscriptions are a feature that allows clients to receive real-time updates from a GraphQL API.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;With AppSync subscriptions, ensure that the payload size is minimal and implement filtering on subscriptions to send only relevant data to clients.&lt;/p&gt;




&lt;h3&gt;
  
  
  Cost Optimisation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Helps control costs by using the right resources, eliminating waste, and scaling effectively.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Leverage Caching
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Caching reduces the need to invoke resolvers for repeated requests, which in turn lowers the cost of Lambda invocations, DynamoDB queries, and other operations.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Setting up caching incurs initial costs since it relies on instances rather than being serverless. However, as AppSync serves more requests using the cache, it reduces overall costs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Avoid Lambda resolver
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;As stated in the performance pillar above, Directly interacting with data sources reduces the cost of using Lambda.&lt;/p&gt;

&lt;h4&gt;
  
  
  Log what you need
&lt;/h4&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Enabling verbose and full logging in AppSync can quickly increase your CloudWatch costs. Choose what to log carefully for troubleshooting.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Set a retention period for CloudWatch log groups, as application logs are usually needed for 2-4 weeks for troubleshooting. For longer storage, use AWS S3 with lifecycle policies to reduce costs.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppSync does not support log sampling by default. Implement a custom sampling strategy to log detailed data every X minutes over Y hour(s).&lt;/p&gt;




&lt;h3&gt;
  
  
  Sustainability
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Minimises environmental impact through efficient resource management and sustainable practices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;There are no sustainability practices to implement as the serverless paradigm continuously increases efficiency and reduces energy consumption as stated in the Serverless Architecture Lens&lt;/p&gt;

&lt;h2&gt;
  
  
  Outro
&lt;/h2&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Building well-architected AWS AppSync GraphQL APIs requires a comprehensive approach that considers all aspects of the Serverless Architecture Lens. By following best practices and utilizing AWS AppSync's integration capabilities, you can create scalable, secure, and efficient GraphQL APIs for modern applications.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Remember, building a well-architected solution is an ongoing process. Regularly review your architecture against these pillars and make improvements as your application evolves.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>appsync</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Simplify AWS Appsync GraphQL API creation with strongly typed Typescript resolvers</title>
      <dc:creator>sudokar</dc:creator>
      <pubDate>Sun, 30 Jul 2023 07:20:46 +0000</pubDate>
      <link>https://dev.to/sudokar/simplify-aws-appsync-graphql-api-creation-with-strongly-typed-typescript-resolvers-g7b</link>
      <guid>https://dev.to/sudokar/simplify-aws-appsync-graphql-api-creation-with-strongly-typed-typescript-resolvers-g7b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This post assumes familiarity with &lt;a href="https://aws.amazon.com/appsync/"&gt;AWS Appsync&lt;/a&gt; and &lt;a href="https://aws.amazon.com/cdk/"&gt;AWS CDK&lt;/a&gt; in general&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this post, we'll look into creating an AWS Appsync GraphQL API with TypeScript using &lt;a href="https://constructs.dev/packages/cdk-appsync-typescript-resolver"&gt;cdk-appsync-typescript-resolver&lt;/a&gt; AWS CDK construct. This powerful construct simplifies the creation of AWS AppSync resolvers in your CDK project, making it easier and more efficient for developers to work with AppSync.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Complete code is available in &lt;a href="https://github.com/sudokar/cdk-appsync-typescript-resolver-demo"&gt;Github&lt;/a&gt; for reference&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Codegen
&lt;/h2&gt;

&lt;p&gt;We can generate Typescript types using &lt;a href="https://the-guild.dev/graphql/codegen"&gt;GraphQL Code Gen&lt;/a&gt; easily.&lt;/p&gt;

&lt;p&gt;These &lt;a href="https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/src/types/appsync.ts"&gt;types&lt;/a&gt; are generated using Codegen CLI command &lt;code&gt;graphql-codegen&lt;/code&gt; for this &lt;a href="https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/schema.graphql"&gt;graphql schema&lt;/a&gt; using this &lt;a href="https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/codegen.ts"&gt;codegen&lt;/a&gt; config file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Alternatively, We can use &lt;a href="https://github.com/aws-amplify/amplify-codegen"&gt;Amplify codegen&lt;/a&gt; as well&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolver in Typescript
&lt;/h2&gt;

&lt;p&gt;We can start writing our resolvers in Typescript using the Codegen generated types and the Appsync's &lt;a href="https://www.npmjs.com/package/@aws-appsync/utils"&gt;utils package&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets look at an example resolver. &lt;a href="https://github.com/sudokar/cdk-appsync-typescript-resolver-demo/blob/main/src/resolvers/addTodo.ts"&gt;Link to File&lt;/a&gt;&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;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBPutItemRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-appsync/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MutationAddTodoArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../types/appsync&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&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;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MutationAddTodoArgs&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;DynamoDBPutItemRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;attrValues&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PutItem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toDynamoDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;attributeValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toMapValues&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;attrValues&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MutationAddTodoArgs&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;object&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;Todo&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&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 looks a lot better than writing resolvers in Javascript or VTL 😓 &lt;/p&gt;

&lt;p&gt;We can use types for context, various data sources supported by Appsync and &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/built-in-util-js.html"&gt;built-in utilities&lt;/a&gt; along with generated types for GraphQL schema, which enhances the reliability and maintainability code through static type checking&lt;/p&gt;

&lt;h2&gt;
  
  
  Transpiling and Bundling
&lt;/h2&gt;

&lt;p&gt;Unfortunately, we can't just sent the resolvers written in Typescript to Appsync yet. Like we transpile Typescirpt to Javascript for AWS Lambda, We will have to do the same for Appsync.&lt;/p&gt;

&lt;p&gt;Unfortunately, as of today there is no built-in capability in CDK to do this for us. We will have to do it ourself using &lt;a href="https://esbuild.github.io/"&gt;Esbuild&lt;/a&gt; or something similar, which transpiles and bundles code from Typescript to Javascript.&lt;/p&gt;

&lt;p&gt;Using Esbuild's &lt;a href="https://esbuild.github.io/api/#js-sync"&gt;Javascript API&lt;/a&gt;, We have a custom CDK construct &lt;a href="https://constructs.dev/packages/cdk-appsync-typescript-resolver"&gt;cdk-appsync-typescript-resolver&lt;/a&gt; that will automate the transpilation and bundling process for us.&lt;/p&gt;

&lt;p&gt;An example of creating Typescript function using the custom construct &lt;a href="https://constructs.dev/packages/cdk-appsync-typescript-resolver/v/0.0.4/api/AppsyncTypescriptFunction?lang=typescript"&gt;AppsyncTypescriptFunction&lt;/a&gt;. This abstracts the transpiling and bundling process and setting default properties.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addTodo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AppsyncTypescriptFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AddTodoFunction&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="s2"&gt;addTodo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamoDataSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addTodo.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;replaceStrings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JS resolvers doesn't support unit resolvers, supports only &lt;a href="https://docs.aws.amazon.com/appsync/latest/devguide/resolver-reference-overview-js.html#anatomy-of-a-pipeline-resolver-js"&gt;pipeline resolvers&lt;/a&gt;. When we want to create a unit resolver, we will have to use bit of boilerplate code and settings to make the pipeline resolver as unit resolver.&lt;/p&gt;

&lt;p&gt;An added functionality to the construct is to define a property &lt;code&gt;replaceStrings&lt;/code&gt;, which takes a map of from and to strings, used to replace strings in the transpiled and bundled code.&lt;/p&gt;

&lt;p&gt;We have another CDK construct &lt;a href="https://constructs.dev/packages/cdk-appsync-typescript-resolver/v/0.0.4/api/TSExpressPipelineResolver?lang=typescript"&gt;TSExpressPipelineResolver&lt;/a&gt; to use the &lt;a href="https://constructs.dev/packages/cdk-appsync-typescript-resolver/v/0.0.4/api/AppsyncTypescriptFunction"&gt;AppsyncTypescriptFunction&lt;/a&gt; and sets up required boilerplate for us.&lt;/p&gt;

&lt;p&gt;An example of using &lt;code&gt;TSExpressPipelineResolver&lt;/code&gt; construct.&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;new&lt;/span&gt; &lt;span class="nx"&gt;TSExpressPipelineResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AddTodoResolver&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;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;typeName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mutation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addTodo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tsFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;addTodo&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We can use instance of &lt;code&gt;AppsyncTypescriptFunction&lt;/code&gt; construct, which extends &lt;code&gt;appsync.AppsyncFunction&lt;/code&gt;, in a regular &lt;code&gt;appsync.Resolver&lt;/code&gt; construct too&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploying the stack
&lt;/h2&gt;

&lt;p&gt;You can find the complete code of this demo on &lt;a href="https://github.com/sudokar/cdk-appsync-typescript-resolver-demo"&gt;GitHub&lt;/a&gt;.&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="nx"&gt;cdk&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tip
&lt;/h2&gt;

&lt;p&gt;Catch invalid syntax (things not supported by Appsync's JS Resolver) while writing resolvers using the Eslint plugin &lt;a href="https://www.npmjs.com/package/@aws-appsync/eslint-plugin"&gt;@aws-appsync/eslint-plugin&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;By using the constructs from &lt;a href="https://constructs.dev/packages/cdk-appsync-typescript-resolver"&gt;cdk-appsync-typescript-resolver&lt;/a&gt;, we can now take advantage of static typing and code editor's IntelliSense &amp;amp; inlay hints to make Appsync's resolvers easy and efficient. Generation of types from GraphQL schema makes it even more efficient. And also, With &lt;code&gt;@aws-appsync/eslint-plugin&lt;/code&gt; we can get the benefit of finding invalid syntax while writing code.&lt;/p&gt;

&lt;p&gt;Hope this was helpful!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>appsync</category>
      <category>typescript</category>
      <category>cdk</category>
    </item>
  </channel>
</rss>
