<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~files/atom-premium.xsl"?>
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedpress="https://feed.press/xmlns" xmlns:media="http://search.yahoo.com/mrss/" xmlns:podcast="https://podcastindex.org/namespace/1.0">
  <feedpress:locale>en</feedpress:locale>
  <feedpress:newsletterId>telerik-blogs-web</feedpress:newsletterId>
  <link rel="hub" href="https://feedpress.superfeedr.com/"/>
  <logo>https://static.feedpress.com/logo/telerik-blogs--web-5ab52bef0a72f.jpg</logo>
  <title type="text">Telerik Blogs | Web</title>
  <subtitle type="text">The official blog of Progress Telerik - expert articles and tutorials for developers.</subtitle>
  <id>uuid:18c5732e-962a-446a-b0a2-adab95033deb;id=2473</id>
  <updated>2026-05-31T08:53:02Z</updated>
  <link rel="alternate" href="https://www.telerik.com/"/>
  <link rel="self" type="application/atom+xml" href="https://feeds.telerik.com/blogs/web"/>
  <entry>
    <id>urn:uuid:5c3a107b-0444-4d8f-a104-97f53edd0760</id>
    <title type="text">How a 6-Person Team Shipped an AI-First Platform with KendoReact</title>
    <summary type="text">For teams building AI-driven applications with complex workflows, treating the UI layer as infrastructure can dramatically reduce friction as the product evolves. For Icanpreneur, KendoReact became that foundation.</summary>
    <published>2026-05-28T14:47:38Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Kathryn Grayson Nanz </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17350156/how-a-6-person-team-shipped-an-ai-first-platform-with-kendoreact"/>
    <content type="text"><![CDATA[<p>Wiring up an LLM endpoint takes an afternoon; the harder engineering problem is building a UI layer that can absorb the inherent unpredictability of AI without introducing layout thrashing, inconsistent interaction patterns or sprawl that a small team can't maintain. </p><p>That's the problem Icanpreneur solved with KendoReact. </p><blockquote>&ldquo;The hard part with AI is not just calling a model &ndash; it&rsquo;s designing complex, trustworthy workflows around it. That&rsquo;s where KendoReact helped a lot.&rdquo; </blockquote><p><a href="https://www.icanpreneur.com/">Icanpreneur&rsquo;s platform</a> orchestrates guided, AI-assisted workflows that blend business logic, structured data and real-time feedback into a familiar, approachable experience. Users move through Lean Canvas modeling, validation flows and strategic planning steps with AI augmenting their thinking along the way. They&rsquo;re now used not only by early-stage founders but also by accelerators, innovation hubs and product teams inside organizations such as Founder Institute, Campus X, Science Park Graz, ABLE Activator, Sofia Tech Park, Visa Innovation Program Europe and Telerik Academy&rsquo;s Upskill Product Management program. </p><p>Raw AI output can be unpredictable. Without a stable, consistent UI foundation, that translates into friction and mistrust. The consistency, predictability and performance of <a href="https://www.telerik.com/kendo-react-ui">KendoReact</a> in the UI layer turned the output of Icanpreneur&rsquo;s AI assistant, IVA, into something usable and trustworthy. For Icanpreneur&rsquo;s six-person team, KendoReact was the infrastructure that made AI usable at scale. </p><h2>Icanpreneur&rsquo;s Architecture </h2><p>At a high level, Icanpreneur&rsquo;s platform is structured as a layered system that separates UI composition and user interaction, server / API management and LLM orchestration.</p><img sf-image-responsive="true" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-05-28-at-9-44-37-am.png?sfvrsn=127900_2" height="844" style="max-width:100%;height:auto;" title="Screenshot 2026-05-28 at 9.44.37 AM" width="1016" alt="A graphic depiction of the frontend architecture " sf-size="411556" /><p>KendoReact is the core of the Icanpreneur UI layer, allowing them to guide users through complex multi-pane screens, support long-running workflows with consistent UI patterns and handle advanced multi-step journeys without overwhelm. Every major module (Lean Canvas, conversational interview console, go-to-market editor, persona builder) is composed from the same KendoReact primitive set, themed consistently and governed by the same layout contracts. That decision paid compounding dividends as the product scaled. </p><p>In an AI-first platform, it can be tempting to start thinking about the UI as set dressing; just a thin layer over the APIs to make things &ldquo;look pretty&rdquo; for the users. However, AI interaction patterns are still very new and unfamiliar to many users. A UI that naturally folds AI into the user experience can be a real differentiator in the competitive market. </p><blockquote>&ldquo;Founders in partner programs started telling us that the interview, insights and go-to-market flow &lsquo;feels like one tool &ndash; simple and intuitive, not five stitched together.'&rdquo; </blockquote><p>AI technology is impressive but UI engineers are still the ones who translate that potential into true value for the user. In Icanpreneur&rsquo;s case, they needed stable layout primitives, reliable form controls and high-performance data visualization components &ndash; all of which had to present AI output reliably (while responses were streaming, partial or evolving) without triggering unnecessary re-renders or layout shifts. KendoReact provided that and more, empowering the team to focus their effort on user experience, business logic and AI orchestration. </p><h3>Composability as a Force Multiplier </h3><p>One of the most important architectural decisions was treating KendoReact not as a collection of finished widgets or mere building blocks to be combined but as true UI infrastructure. KendoReact powers everything from research dashboards and conversational interview consoles to mini-CRMs and multi-step go-to-market editors.</p><img sf-image-responsive="true" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-05-28-at-9-45-10-am.png?sfvrsn=1b568e58_2" height="802" style="max-width:100%;height:auto;" title="Screenshot 2026-05-28 at 9.45.10 AM" width="1370" alt="A screenshot of the Icanpreneur interface, built with KendoReact components" sf-size="418054" /><p>Foundation pieces were combined to create higher-order workflows that guide users through their interactions with IVA. Rather than building custom UI for each flow, the team defined composable patterns built on KendoReact primitives. For example, the multi-step validation flow reused the same layout + navigation structure, AI feedback panels reused consistent container and typography patterns and interview summaries reused standardized layout and card structures. Because the Icanpreneur team didn&rsquo;t need to design complex new interaction patterns for each additional feature, they were able to implement quickly and iterate fast &ndash; smoothly layering their AI workflows on top of KendoReact&rsquo;s component system. </p><h3>Consistent UX for Novel AI Workflows </h3><p>For Icanpreneur users, this meant that they never had to open a page and feel unsure of where to go or what to do next &ndash; even though the AI-powered technology may be new, it leveraged familiar and consistent patterns to guide them through the experience. </p><blockquote>&ldquo;Because all the AI-driven experiences reuse the same KendoReact components as the rest of the app, they behave in a predictable way. Users don&rsquo;t have to &lsquo;learn' a new interface just because AI is involved &ndash; it feels like one coherent workspace.&rdquo; </blockquote><h3>Design-to-Code Fidelity </h3><p>With KendoReact, Icanpreneur designers and engineers worked from the same component language, which meant no translation layer between design and implementation, no pixel-chasing and no divergence between what's mocked and what ships. Designers also created a custom design system using the <a href="https://www.telerik.com/figma-kits">Kendo UI Figma Kits</a> and the <a href="https://www.telerik.com/design-system/docs/">Progress Design System Kit</a>, which greatly reduced the time needed to create mockups and new pages. </p><blockquote>&ldquo;Using the Kendo UI Figma Kits and a custom Kendo theme, we aligned design and development from day one. Most new features now start as a quick sketch in our KendoReact-based design system and turn into a working screen in days instead of weeks.&rdquo; </blockquote><h3>Increased Development Speed </h3><p>AI-assisted workflows evolve quickly: new steps get added, feedback formats change and validation criteria expand. Because KendoReact components are extensible and <a href="https://www.telerik.com/kendo-react-ui/components/styling">themeable</a>, the Icanpreneur team could meet these challenges while still preserving UX consistency. As workflows grew, the UI layer remained adaptable; less time debugging or re-writing UI logic meant faster revision cycles and more shipped features. </p><blockquote>&ldquo;New workflow-style features (such as a new research flow or AI-assisted editor) now typically go from idea to shipped version in days instead of weeks, because we mostly compose existing KendoReact patterns instead of building UI from scratch.&rdquo; </blockquote><h2>The UX of AI </h2><p>Some of the biggest challenges in AI-first applications are handling uncertainty (usually in the form of partial / still evolving responses) and guiding users through new workflows. The Icanpreneur team leveraged KendoReact&rsquo;s design tools to create UX patterns that integrate AI feedback into existing, structured UI flows, so users are never left wondering &ldquo;what now?&rdquo;.</p><img sf-image-responsive="true" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-05-28-at-9-46-59-am.png?sfvrsn=e6e4d053_2" height="836" style="max-width:100%;height:auto;" title="Screenshot 2026-05-28 at 9.46.59 AM" width="1370" alt="A screenshot of the Icanpreneur interface, built with KendoReact components" sf-size="477986" /><p>One of the most unique features of Icanpreneur is their Synthetic Customer Interview feature, which allows their AI assistant, IVA, to answer questions as though it was a potential customer in their target market. Not all teams have easy access to customers to run interviews with, so this allows founders to &ldquo;stress-test&rdquo; their hypotheses quickly across multiple scenarios. That output helps them refine their questions and assumptions before talking to real people. Afterwards, IVA reviews all the data (across both real and synthetic customer interviews) to summarize, highlight patterns and extract quotes and evidence that can be leveraged in personas and further market research. </p><blockquote>&ldquo;When we designed IVA, the conversational interview console and the research workspace, we could prototype and ship quickly because we already had chat-style layouts built from existing KendoReact <a href="https://www.telerik.com/kendo-react-ui/components/layout">Layout</a> and <a href="https://www.telerik.com/kendo-react-ui/components/form">Form</a> components, multi-step flows for things like research setup and go-to-market editors and reusable <a href="https://www.telerik.com/kendo-react-ui/components/layout/expansionpanel">Panels</a>, <a href="https://www.telerik.com/kendo-react-ui/components/layout/drawer">Drawers</a>, <a href="https://www.telerik.com/kendo-react-ui/components/dialogs/dialog">Dialogs</a> and <a href="https://www.telerik.com/kendo-react-ui/components/grid">Data Grids</a> for displaying AI outputs, suggestions and insights aggregation&rdquo; </blockquote><p>Using KendoReact meant that not only could they leverage these familiar user patterns &ndash; but also that common AI concerns (such as slow, partial or unexpected responses) could be handled with UI structures and error responses that users already knew how to interact with. </p><h2>Performance and Scalability </h2><p>Icanpreneur workflows are complex, multi-stage journeys. Moving from idea to hypothesis, validating with AI or human-led interviews, identifying patterns and extracting valuable feedback, generating personas and finally creating landing pages or pitch decks &ndash; any one of these alone would be demanding but all together they offer a true development challenge. Each step builds upon the previous and the context must be preserved as the user moves between them. Without careful performance engineering, rendering and re-rendering these views would quickly degrade the user experience. </p><p>AI workflows can introduce frequent state updates as responses stream in or evolve - for example, when IVA synthesizes interview insights or drafts positioning. In poorly structured UIs, this can lead to layout thrashing or lag. However, these updates render inside structured, well-optimized KendoReact components, so the Icanpreneur UI remains stable.</p><img sf-image-responsive="true" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/.net-maui-aiprompt/screenshot-2026-05-28-at-9-47-32-am.png?sfvrsn=c831f8dc_2" height="818" style="max-width:100%;height:auto;" title="Screenshot 2026-05-28 at 9.47.32 AM" width="1380" alt="A screenshot of the Icanpreneur interface, built with KendoReact components" sf-size="752777" /><p>As features accumulate, custom CSS and one-off component implementations are a common source of bundle bloat and regression risk. At Icanpreneur, new features and modules all plug into the same KendoReact UI system (rather than introducing new patterns and components). That allows even a small team to control UI sprawl and manage bundle growth. Custom CSS &ndash; a common pain point for fast-growing applications &ndash; is significantly reduced and centralized, because the UI is themed consistently. This kept initial load times reasonable even as the feature surface expanded. </p><blockquote>&ldquo;When we introduce new AI-driven experiences, they use the same KendoReact components, so we see far fewer UI regressions. That made it much easier to roll out new AI features without exploding our QA surface.&rdquo; </blockquote><p>That smaller, bounded QA surface meant faster turnaround and faster time to ship, while the lower complexity means that the small team was able to manage the expedited growth without being overwhelmed. New features that reuse existing KendoReact components inherit known-good behavior, so regression risk didn't scale with feature additions. </p><p>KendoReact&rsquo;s components are designed for high-density, data-heavy enterprise applications; no reinvention (or re-optimization) was required even for complex components like grids, forms and dialogs. KendoReact reduced the need to solve hard performance problems manually, allowing Icanpreneur to access enterprise-level quality with a startup-size team. </p><h2>Icanpreneur: Powered by KendoReact </h2><p>Icanpreneur began as a structured way to guide founders from idea to product-market fit. Today it operates as a full AI co-founder, with IVA empowering entrepreneurs to ideate, validate and grow their companies as a trusted partner. </p><p>By treating the UI layer as infrastructure and building on KendoReact from day one, the team was able to: </p><ul><li>Scale complex, AI-driven workflows with consistency </li></ul><ul><li>Ship new features rapidly without fragmenting UX </li></ul><ul><li>Deliver a coherent experience across classic and AI-powered screens </li></ul><p>For teams building AI-driven workflows, the UI layer is crucial: it's what determines whether AI output becomes a usable, trustworthy product or a source of friction. Icanpreneur's architecture is a case study in treating that layer seriously from day one. Or, as the Icanpreneur team said themselves: </p><blockquote>&ldquo;A six-person core team is able to maintain and evolve a fairly large, AI-first product (research, interviews, personas, positioning, landing pages, sales decks, etc.) without a separate &ldquo;component team&rdquo; or design system squad &ndash; KendoReact is that system for us.&rdquo; </blockquote><p>For teams building AI-driven applications with complex workflows, treating the UI layer as infrastructure can dramatically reduce friction as the product evolves. For Icanpreneur, KendoReact became that foundation: explore how KendoReact could become that foundation for your team, as well.</p><img src="https://feeds.telerik.com/link/10827/17350156.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:9941dce1-d02d-4fcd-9297-f3cd46f3ea42</id>
    <title type="text">Querying Reliable AI Resources with Telerik Agent Tools API</title>
    <summary type="text">Telerik Document Processing Libraries not only let you create RAG resources for your LLMs. They let you integrate those libraries into Microsoft’s latest agent-based tools.</summary>
    <published>2026-05-27T14:04:51Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17349392/querying-reliable-ai-resources-telerik-agent-tools-api"/>
    <content type="text"><![CDATA[<p><span class="featured">Telerik Document Processing Libraries not only let you create RAG resources for your LLMs. They let you integrate those libraries into Microsoft&rsquo;s latest agent-based tools.</span></p><p>In my previous post, I showed how to use Progress <a href="https://www.telerik.com/blogs/creating-reliable-ai-resources-with-telerik-agent-tools-api" target="_blank">Telerik Agent Tools API to create a workflow</a>&nbsp;that loads content into a Resource-Augmented Generation (RAG) resource that can be used with Large Language Models (LLMs). That RAG resource, integrated with an LLM in your application, helps your users get reliable, grounded answers, driven by the content you load into your resource.</p><p>This post shows how to tie your RAG resource to an LLM and integrate the combination in an application that allows users to query the resource&mdash;or any combination of resources that you&rsquo;ve created. Specifically, I&rsquo;ll show how to use Microsoft&rsquo;s current technology for querying an AI resource, the <code>ChatClientAgent</code> object.</p><p>Telerik Agent Tools API provides a collection of toolsets that the chat client agent works with to query your RAG resources. You just have to load your RAG resource(s) into in-memory repositories, attach the appropriate toolsets and pass the resulting tools to a chat client agent. Once you&rsquo;ve done that, you can submit prompts to the agent and get back the results driven by the RAG resources you&rsquo;ve loaded.</p><h2 id="configuring-your-project">Configuring Your Project</h2><p>To create an application that can query your resources, you first need the <a target="_blank" href="http://Telerik.Documents.AI">Telerik.Documents.AI</a>.* NuGet packages that work with the documents in your RAG resource (I covered those <a href="https://www.telerik.com/blogs/creating-reliable-ai-resources-with-telerik-agent-tools-api#configuring-your-project" target="_blank">libraries in my previous post</a>).</p><p>After that, you also need two Microsoft AI NuGet packages:</p><ul><li>Azure.AI.OpenAI</li><li>Microsoft.Agents.AI.OpenAI</li></ul><p>I used an ASP.NET Web API project for my case study so I also needed to add the Microsoft.AspNetCore.OpenApi package.</p><p>As I write this, the Microsoft.Agents.AI.OpenAI package was in &ldquo;release candidate&rdquo; mode which means that, while its interfaces and functionality are fixed, I needed to use the &ldquo;prerelease&rdquo; option when adding the package. Having said that, by the time you read this, the package may have moved to &ldquo;latest stable&rdquo; status (this is a <em>very</em> agile environment).</p><h3 id="configuring-a-chat-client-agent">Configuring a Chat Client Agent</h3><p>Before you create your <code>ChatClientAgent</code> agent object, you need to assemble a set of tools, tied to one or more of your RAG resources. Your first step is to create a repository of the right type (PDF, spreadsheet, Word/Word-related) and load the file that holds your RAG resource into that repository.</p><p>For my case study, I&rsquo;m only working with PDF documents&mdash;what Progress Telerik calls &ldquo;Fixed&rdquo; documents&mdash;so I created a <code>IFixedDocumentRepository pdfRepo</code> repository (all the repositories share a common interface so they all look very much alike).</p><p>Once I created the repository, I loaded my RAG resource using the repository&rsquo;s <code>Import</code> method. The <code>Import</code> method must be passed a <code>FileStream</code> object pointing to the resource and its format which, in this case, was <code>DocumentFormat.PDF</code> (see my previous post for the details on creating that resource).</p><p>Here&rsquo;s the code that loads my case study&rsquo;s repository:</p><pre class=" language-csharp"><code class="prism  language-csharp">IFixedDocumentRepository pdfRepo <span class="token operator">=</span> 
               <span class="token keyword">new</span> <span class="token class-name">InMemoryFixedDocumentRepository</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">TimeSpan</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span> Path<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>repoPath<span class="token punctuation">)</span> <span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">string</span> name <span class="token operator">=</span>pdfRepo<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>
                                        <span class="token function">FileStream</span><span class="token punctuation">(</span>repoFilePath<span class="token punctuation">,</span> FileMode<span class="token punctuation">.</span>Open<span class="token punctuation">,</span> FileAccess<span class="token punctuation">.</span>Read<span class="token punctuation">)</span><span class="token punctuation">,</span>
                                       DocumentFormat<span class="token punctuation">.</span>PDF<span class="token punctuation">,</span>
         Path<span class="token punctuation">.</span><span class="token function">GetFileNameWithoutExtension</span><span class="token punctuation">(</span>repoFilePath<span class="token punctuation">)</span>
                                     <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>If you provide the name of the file as the third parameter, the <code>Import</code> method returns that name (and returns &ldquo;ImportedDocument&rdquo; if you don&rsquo;t provide the third parameter).</p><p>To support having the chat client agent query my PDF repository, I just need the <code>FixedDocumentContentAgentTools</code> toolset. To create that toolset, I have to pass two parameters: the repository itself and a folder that holds any images used in those PDF documents. When attaching a toolset to a repository, you&rsquo;ll always have to pass the repository parameter, but other toolsets will require different parameters (and, often, no other parameters).</p><p>Once I&rsquo;ve created that toolset, I extract its tools using the toolset&rsquo;s <code>GetTools</code> method and add those tools to a list of <code>AITool</code> objects that will, eventually, be passed to my chat client agent. The code to do that looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">pdfReadTools <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FixedDocumentContentAgentTools</span><span class="token punctuation">(</span>pdfRepo<span class="token punctuation">,</span> repoPath<span class="token punctuation">)</span><span class="token punctuation">;</span>
List<span class="token operator">&lt;</span>AITool<span class="token operator">&gt;</span> queryTools <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>AITool<span class="token operator">&gt;</span><span class="token punctuation">(</span> pdfReadTools<span class="token punctuation">.</span><span class="token function">GetTools</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>If I wanted to add more tools to my list, I would use the list&rsquo;s <code>AddRange</code> method. As an example, the following code:</p><ul><li>Creates a repository for holding spreadsheets</li><li>Imports a RAG resource file of spreadsheets into that repository using the repository&rsquo;s <code>Import</code> method</li><li>Attaches the <code>SpreadProcessingReadAgentTools</code> toolset (which only needs to be passed a reference to the repository)</li><li>Extracts the toolset&rsquo;s tools and adds them to my list of <code>AITools</code></li></ul><pre class=" language-csharp"><code class="prism  language-csharp">IWorkbookRepository workbookRepo <span class="token operator">=</span> 
              <span class="token keyword">new</span> <span class="token class-name">InMemoryWorkbookRepository</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">TimeSpan</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
workbookRepo<span class="token punctuation">.</span><span class="token function">Import</span><span class="token punctuation">(</span>workbookRepoPath<span class="token punctuation">,</span> DocumentFormat<span class="token punctuation">.</span>XLSX<span class="token punctuation">)</span><span class="token punctuation">;</span>

SpreadProcessingReadAgentTools workbookTools <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>workbookRepo<span class="token punctuation">)</span><span class="token punctuation">;</span>

queryTools<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span> workbookTools<span class="token punctuation">.</span><span class="token function">GetTools</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><h2 id="querying-your-repositories">Querying Your Repositories</h2><p>Now that I have a list of tools, I&rsquo;m ready to create an agent. There are three steps to doing that (but you can do it in one line of code).</p><p>First, you need to create a <code>AzureOpenAIClient</code> object which, in its simplest form, just requires passing the URL for the <a target="_blank" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama">LLM deployment you&rsquo;ve created</a> and the authorization key for that deployment.</p><p>One note: Using an authorization key is probably fine for development but, in production, you should be authorizing access using something more robust (e.g., <a target="_blank" href="https://www.telerik.com/blogs/coding-azure-4-securing-web-service-app-service-access-azure-sql-database">Managed Identities in Entra ID</a>). If you&rsquo;re using an authentication key, then you shouldn&rsquo;t hardcode into your application as I do here but move that key to some more secure location (e.g. An <a target="_blank" href="https://www.telerik.com/blogs/coding-azure-12-configuring-azure-key-vault-adding-secrets">Azure Key Vault</a>).</p><p>Once you&rsquo;ve created your <code>AzureOpenAIClient</code> object, your second step is to call its <code>GetChatCient</code> method to configure and return a <code>ChatClient</code> object. The <code>GetChatClient</code> method just needs to be passed the name of the deployment you created for your LLM.</p><p>Finally, you need to call your <code>ChatClient</code> object&rsquo;s <code>AsAIAgent</code> method to configure your agent. You can pass a variety of parameters as part of configuring your agent. I settled for specifying a name for my agent, some instructions on how my agent is to answer questions, and my list of tools:</p><pre class=" language-csharp"><code class="prism  language-csharp">AIAgent agent <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AzureOpenAIClient</span><span class="token punctuation">(</span>
                                      <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">,</span>
                                      <span class="token keyword">new</span> <span class="token class-name">AzureKeyCredential</span><span class="token punctuation">(</span>apiKey<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">GetChatClient</span><span class="token punctuation">(</span>deploymentName<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">AsAIAgent</span><span class="token punctuation">(</span>
                instructions<span class="token punctuation">:</span> <span class="token string">"You provide guidance to Azure software developers"</span><span class="token punctuation">,</span>
                name<span class="token punctuation">:</span> <span class="token string">"Async App Expert"</span><span class="token punctuation">,</span>
                tools<span class="token punctuation">:</span> queryTools<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>With your chat client agent in hand, you process your user&rsquo;s prompts by calling the agent&rsquo;s <code>RunAsync</code> method and passing the prompt. The agent will return an <code>AgentResponse</code> object which has a <code>Messages</code> collection holding a list of responses drawn from the repositories associated with your tools (you probably want the first message). The <code>Text</code> property on a message will give you the agent&rsquo;s response.</p><p>Typical code would look like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">AgentResponse response <span class="token operator">=</span> <span class="token keyword">await</span> agent<span class="token punctuation">.</span><span class="token function">RunAsync</span><span class="token punctuation">(</span>"What <span class="token keyword">do</span> NuGet packages <span class="token keyword">do</span> I need&rdquo;<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">string</span> responseText <span class="token operator">=</span> response<span class="token punctuation">.</span>Messages<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>Text<span class="token punctuation">;</span>
</code></pre><h2 id="interacting-with-your-rag-enabled-sources">Interacting with Your RAG-Enabled Sources</h2><p>Of course, you&rsquo;ll also want to create a UI for your users to interact with when querying your RAG resource. In earlier posts, I showed how to leverage Telerik tools to create dedicated user-friendly <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-4-crafting-interactive-blazor-ui" target="_blank">UIs in Blazor</a>&nbsp;or <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-5-creating-interactive-ui-javascript" target="_blank">JavaScript</a>. Alternatively, instead of creating a UI dedicated to your RAG-enabled resource, you might want to more tightly <a href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-6-embedding-conversational-invisible-agents" target="_blank">integrate your resource into a JavaScript application&rsquo;s UI</a>.&nbsp;</p><p>But, really, it&rsquo;s up to you how you&rsquo;ll use your RAG resource to support your users.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Get access to these tools and more</h4></div><div class="col-8"><p class="u-fs16 u-mb0">The free 30-day trial of <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> lets you really kick the tires for yourself. <a target="_blank" href="https://www.telerik.com/try/devcraft-ultimate">Try it today!</a></p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17349392.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:58c27fcc-97ec-47bb-b8ed-a6bfff50ad27</id>
    <title type="text">Deploying Containerized NestJS Applications on GCP Using Cloud Run</title>
    <summary type="text">While there are numerous ways to deploy an app to Cloud Run, see how to containerize and deploy a NestJS API. We will use a few products on GCP, such as Buildpacks and Artifact Registry, to build and deploy our image, and then finally deploy it to Cloud Run.</summary>
    <published>2026-05-21T14:35:45Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Christian Nwamba </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17345725/deploying-containerized-nestjs-applications-gcp-using-cloud-run"/>
    <content type="text"><![CDATA[<p><span class="featured">While there are numerous ways to deploy an app to Cloud Run, see how to containerize and deploy a NestJS API. We will use a few products on GCP, such as Buildpacks and Artifact Registry, to build and deploy our image, and then finally deploy it to Cloud Run.</span></p><p><a target="_blank" href="https://cloud.google.com/run">Google Cloud Run</a> is a serverless platform that allows developers to deploy and scale a wide range of applications, from web, server-side and functions to AI/ML workloads. Internally, it runs all applications as containerized payloads.</p><p>While there are numerous ways to deploy an app to Cloud Run, in this article, we will see how to containerize and deploy a NestJS API. We will use a few products on GCP, such as <a target="_blank" href="https://docs.cloud.google.com/docs/buildpacks/overview">Buildpacks</a> and <a target="_blank" href="https://docs.cloud.google.com/artifact-registry/docs">Artifact Registry</a>, to build and deploy our image, and then finally deploy it to Cloud Run.</p><h2 id="prerequisites">Prerequisites</h2><p>To proceed with this guide, it is assumed you are comfortable with TypeScript and have basic knowledge of building a web server with the NestJS framework.</p><h2 id="setting-up-a-nestjs-project">Setting Up a NestJS Project</h2><p>Assuming you have the <a target="_blank" href="https://docs.nestjs.com/cli/overview#installation">NestJS CLI</a> installed, open your terminal and run the following command to set up a basic NestJS project:</p><pre class=" language-shell"><code class="prism  language-shell">nest new sample-project
</code></pre><p>Follow the prompt to set up the project in a folder called sample-project. Feel free to choose your preferred name. We will be making changes to our project as we proceed.</p><h2 id="setting-up-our-project-on-gcp">Setting Up Our Project on GCP</h2><p>Let&rsquo;s now set up a project on GCP. To achieve this, you can do one of the following:</p><ul><li>Create a project using the Firebase console UI or CLI</li><li>Directly create it on the GCP console UI or using the Google Cloud CLI</li></ul><p>Regardless of which method we choose, we will get the same result. However, in our case, we will be using the second option and will mostly be working with the Google Cloud CLI in our terminal to create resources.</p><p>Assuming you have the Google Cloud CLI installed, open your terminal and run the following commands to set up the CLI. Skip these steps if you have already configured it.</p><p>Start by logging in:</p><pre class=" language-shell"><code class="prism  language-shell">gcloud auth login
</code></pre><p>To verify the logged-in account, run this command: <code>gcloud auth list</code>.</p><p>Next, run the following command to create a project:</p><pre class=" language-shell"><code class="prism  language-shell">gcloud projects create dummy-nest-swish0062 --name dummy-nest-project --set-as-default
</code></pre><p>We specify the project ID and the project name, and set it as the default project in our CLI.</p><p>We will also need to enable billing on the project to be able to use Cloud Run. Run the following command:</p><pre class=" language-shell"><code class="prism  language-shell">gcloud billing accounts list 
</code></pre><p>The command above lists the available billing accounts, which will return a list that looks like so:</p><p><img title="List of billing account" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/list-of-billing-account.png?sfvrsn=7ca2978e_2" alt="List of billing account" /></p><p>Next, link the billing account by running this command:</p><pre class=" language-shell"><code class="prism  language-shell">gcloud billing projects link dummy-nest-swish0062 --billing-account=017F58-A35A34-XXXXXX
</code></pre><p>The command above is similar to clicking create project on GCP and filling the form as shown below:</p><p><img title="Creating a project on GCP console" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/creating-a-project-on-gcp-console.png?sfvrsn=197f9625_2" alt="Creating a project on GCP console" /></p><h2 id="google-cloud-run-knative-and-kubernetes">Google Cloud Run, Knative and Kubernetes</h2><p>Google Cloud Run is built on top of Knative, and Knative is built on top of Kubernetes. These tools are designed to simplify the deployment of containerized applications.</p><p><img title="Google Cloud Run, Knative, and Kubernetes" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/google-cloud-run-knative-kubernetes.png?sfvrsn=e3ea634_2" alt="Google Cloud Run, Knative, and Kubernetes" /></p><p>As we move from top to bottom, there is increased experience and expertise required, more control, more knowledge gaps to be filled, and, of course, a greater margin for error.</p><p>The opposite applies when moving from bottom to top, with Google Cloud Run at the apex. Cloud Run requires the least experience from developers and gives them the best deployment experience while doing all the heavy lifting.</p><p>To understand the benefits of Cloud Run, let&rsquo;s do a basic walkthrough from bottom to top, outlining the struggles and benefits at each level.</p><h3 id="containers-and-kubernetes">Containers and Kubernetes</h3><p>It all starts with having a project written in some language that needs to be deployed live to users. To proceed, the developer may need to understand how to use a container runtime like Docker or Podman to build images. Next, they need to put the images on some registry (e.g., Docker Hub).</p><p>To deploy the images, that is, run them as containers in production, a lot of things can go wrong. They need to determine how many instances of the container to run, which ports to expose, how to route traffic, and much more. To achieve this, learning how to use a tool like Kubernetes might help, since it allows the developer to define the desired state of the application. The developer needs to understand how Kubernetes works, understanding concepts like the control plane, data plane, workloads, pods, deployments, services and ingresses.</p><h3 id="knative">Knative</h3><p>To save developers the stress, Knative was built. It abstracts all the inner complexities of working directly with Kubernetes and makes it easy to build and scale serverless applications with zero knowledge of containers, Kubernetes or any of its concepts. Since it is based on Kubernetes, it is highly portable and can run on any cloud platform.</p><p>Knative consists of three main components:</p><ol><li><p><strong>Functions Framework:</strong> A framework that allows developers to write HTTP-triggerable serverless functions in their preferred programming language. As of the time of writing, four languages are supported (Go, Python, Java and TypeScript/JavaScript). After writing their functions, developers can test them locally. The Functions Framework handles containerizing the function code, storing it in a registry and then passing it to Knative Serving for deployment.</p></li><li><p><strong>Knative Serving:</strong> This is responsible for deploying and running containers on top of Kubernetes. Containers can hold the logic for any HTTP-triggerable workload&mdash;for example, one using the Functions Framework, our NestJS web server, or an AI/ML workload. Under the hood, it interacts with Kubernetes to create service definitions, which house all the configurations required to run the containers, route incoming traffic to them and handle scaling as well. For AI/ML workloads, it verifies containers have access to GPUs. Service definitions also maintain revisions of the service, which are snapshots or versions of the configurations and the state of our application, allowing developers to roll back to previous states in case of errors or failures.</p></li><li><p><strong>Knative Eventing:</strong> Provides APIs for developers to employ an event-driven architecture suitable for building loosely coupled services. These APIs allow developers to route events between services via HTTP. Events are usually represented in the form of JSON. They originate from an event source and are then moved to a message broker, which routes the payload to an event consumer. An event source or consumer could be services running on Knative Serving or Kubernetes; event sources may also be external services and systems, such as databases.</p></li></ol><h3 id="cloud-run">Cloud Run</h3><p>Finally, we have Cloud Run, which, in the simplest terms, is Knative for Google Cloud Platform with extra superpowers:</p><ul><li>Deployment directly from source code or using containers, with or without knowledge of container runtimes like Docker or Podman</li><li>Runs containers in an isolated environment where running containers can still access your other cloud resources</li><li>Multiple options to run containers: as services, jobs or worker pools</li><li>Functions Framework with support for more languages, allowing you to write and deploy application logic in minutes</li><li>Flexible payment options&mdash;either pay per request or pay per instance&mdash;with autoscaling handled automatically</li><li>CI/CD tools to automatically deploy new versions of your application to production</li></ul><h2 id="deployment-options">Deployment Options</h2><p>Regardless of the method we choose to deploy an application, we already know that it ends up running as a container. Generally, we can deploy our application as one of the following:</p><ul><li><strong>Service:</strong> Used for HTTP-triggerable resources.</li><li><strong>Job:</strong> Jobs are typically used when we want our code to perform expensive computations that we trigger manually. Jobs are not called via HTTP.</li><li><strong>Worker pool:</strong> These are used to run resources that serve as consumers for jobs managed by a broker or queue service. Worker pools are always running and constantly listening for new data to process.</li></ul><p>Since our NestJS application is HTTP-triggerable and listens on a port, we will be deploying it as a service.</p><p>When deploying our application as a service, we have two main options:</p><ul><li><strong>Deploy from source:</strong> Here, you simply point Cloud Run to a repository (e.g., on GitHub), and it takes care of containerizing the application and deploying it. This option is available only for services.</li><li><strong>Deploy from container:</strong> Here, you point it to a container (e.g., on Docker Hub or Artifact Registry), and then it takes care of the rest. This is available for all resource types.</li></ul><p>We will be using the second option, since this is the goal of this article.</p><h2 id="deploy-from-container">Deploy from Container</h2><p>When deploying from containers, we can choose to build and publish the image either locally or remotely:</p><ul><li><strong>Locally:</strong> Here we install Docker, Podman or some other container runtime on a PC or VM, then build an image. To build the image locally, we can either write our build configuration in a Dockerfile or skip using a Dockerfile completely and install a local buildpack like the Pack CLI to build the image. We then publish it on Docker Hub, retrieve the URL to that image, and feed it to the Cloud Run console to deploy our app.</li><li><strong>Remotely:</strong> This is the option we will be using. Here, we don&rsquo;t need to install any tools. We will leverage Google Cloud Buildpacks to build the image remotely, then proceed to push the built image to Google&rsquo;s Artifact Registry and use the URL to the image to deploy the service.</li></ul><h2 id="preparing-our-nestjs-project-before-deployment">Preparing Our NestJS Project Before Deployment</h2><p>We will also need to make a few changes to our NestJS project before we can proceed with deployment.</p><h3 id="defining-the-project-descriptor">Defining the Project Descriptor</h3><p>In the root of your NestJS project, open your terminal and run the following command:</p><pre class=" language-shell"><code class="prism  language-shell">touch project.toml
</code></pre><p>Update its contents to match the following</p><pre class=" language-js"><code class="prism  language-js"><span class="token punctuation">[</span><span class="token punctuation">[</span>build<span class="token punctuation">.</span>env<span class="token punctuation">]</span><span class="token punctuation">]</span>
name <span class="token operator">=</span> <span class="token string">"GOOGLE_ENTRYPOINT"</span>
value <span class="token operator">=</span> <span class="token string">"node dist/main"</span>
</code></pre><p>Earlier, we said we will be using Google&rsquo;s Cloud Build service and the buildpacks it provides to build our application&rsquo;s image. A project descriptor is a file used to guide the repository (i.e., the Cloud Build service) on how to build the image. Think of it as informing the Google Cloud Build service on how to update the contents of the Dockerfile before it builds the image and when it runs the image as a container.</p><p>There are special environment variable names, some of which are specific to Google Cloud Build (i.e., for any runtime such as Node or Java) and others specific to our project&rsquo;s runtime (i.e., Node.js in our case).</p><p>In the <code>project.toml</code> file, we specified the one called <code>GOOGLE_ENTRYPOINT</code>. Without this variable, Cloud Run will not know how to execute our container. The value of this variable is synonymous with the CMD block in a Dockerfile.</p><p>It is important to note that the environment variables in the <code>project.toml</code> file are only for building the image and running the container. Later, we will describe how to add runtime-specific environment variables like database secrets that will be used in our NestJS app.</p><h2 id="configuring-the-port">Configuring the Port</h2><p>Update the <code>main.ts</code> file to look like this:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> NestFactory <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AppModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.module'</span><span class="token punctuation">;</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">bootstrap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">await</span> NestFactory<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>AppModule<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>PORT <span class="token operator">||</span> <span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">bootstrap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The PORT environment variable is a special reserved variable that will be injected by Cloud Run when it runs our NestJS backend in a container. We updated the app to listen to the value of the PORT variable or fall back to the default value of 3000.</p><h2 id="building-and-deploying-our-nestjs-app-as-a-service">Building and Deploying Our NestJS App as a Service</h2><p>In this section, we will be doing the following:</p><ul><li>Creating an artifact repository</li><li>Building and publishing the image to the artifact repository</li><li>Creating a service on Cloud Run using the image URL</li></ul><h2 id="creating-an-artifact-repository">Creating an Artifact Repository</h2><p>Open your terminal, and run the following command to create a repository:</p><pre class=" language-shell"><code class="prism  language-shell">gcloud artifacts repositories create dummy-nest-repo\
    --repository-format=docker \
    --location=europe-west2 \
    --description=" this "repo will hold my nestjs app" \
    --immutable-tags 
</code></pre><p>A repository holds one or more images. In the command above, we created one called &ldquo;dummy-nest-repo&rdquo; in the europe-west2 region with the Docker repository format.<br />If the command executes successfully, we get the newly created repository.</p><p>Here is a snapshot of the GCP console UI showing the newly created repository:</p><p><img title="Newly created repo on GCP console" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/newly-created-repo-on-gcp-console.png?sfvrsn=a38ed974_2" alt="Newly created repo on GCP console" /></p><h2 id="building-and-publishing-the-image-to-the-artifact-repository">Building and Publishing the Image to the Artifact Repository</h2><p>With our repository available, run the following command to trigger a build and publish it to the repository:</p><pre class=" language-shell"><code class="prism  language-shell">gcloud builds submit --pack image=europe-west2-docker.pkg.dev/dummy-nest-swish0062/dummy-nest-repo/dummy-app:0.0001 
</code></pre><p>The command above triggers the Cloud Build service to build and deploy our image to the dummy-nest-repo repository.</p><p>The image URL is a string that takes the following form:</p><pre class=" language-js"><code class="prism  language-js">REGION<span class="token operator">-</span>docker<span class="token punctuation">.</span>pkg<span class="token punctuation">.</span>dev<span class="token operator">/</span>PROJECT<span class="token operator">-</span>ID<span class="token operator">/</span>REPOSITORY_NAME<span class="token operator">/</span>IMAGE_NAME<span class="token punctuation">:</span>TAG
</code></pre><p><img title="Image built and uploaded in repo" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/image-built-and-uploaded.png?sfvrsn=22dfb8ed_2" alt="Image built and uploaded in repo" /></p><h2 id="creating-a-service-on-cloud-run-using-the-image-url">Creating a Service on Cloud Run Using the Image URL</h2><p>Open your terminal and run the following command to deploy the image as a service:</p><pre class=" language-shell"><code class="prism  language-shell">gcloud run deploy my-nestjs-app  --image europe-west2-docker.pkg.dev/dummy-nest-swish0062/dummy-nest-repo/dummy-app:0.0001
</code></pre><p>The command above deploys a service called &ldquo;my-nestjs-app&rdquo; using the URL to the image we just uploaded.</p><p>The screenshot below shows the result of running the command.</p><p><img title="Deploying our NestJS application as a service on google cloud run using the image URL" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/deploying-our-nestjs-application.png?sfvrsn=e21c81ce_2" alt="Deploying our NestJS application as a service on google cloud run using the image URL" /></p><p>As seen above, we get a URL which we can use to connect to the application. We can visit this endpoint in our browser and receive a &ldquo;Hello World&rdquo; response from our NestJS server, as shown below.</p><p><img title="Testing the service URL in the browser" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/testing-the-service-url.png?sfvrsn=e7223305_2" alt="Testing the service URL in the browser" /></p><p>Also, on the GCP console, we can see the service, its revisions, configurations, routing, etc., similar to the definitions of the Knative Serving component described earlier.</p><p><img title="GCP console" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/gcp-console.png?sfvrsn=d91513eb_2" alt="GCP console" /></p><h2 id="adding-runtime-specific-environment-variables">Adding Runtime-Specific Environment Variables</h2><p>Server-side applications may use one or more environment variables to connect to a database, communicate with third-party APIs, etc. In this section, we will see how to include runtime environment variables when deploying services. We will be doing the following:</p><ul><li>Adding .env files to our NestJS app</li><li>Setting up and configuring the Config Module</li><li>Deploying a new version of the service referencing the environment variable files</li></ul><h3 id="adding-.env-files-to-our-nestjs-app">Adding .env files to our NestJS app</h3><p>Assuming you are in the project&rsquo;s root, open your terminal and run the following command to create two files: <code>.env.dev</code> and <code>.env.prod</code>.</p><pre class=" language-shell"><code class="prism  language-shell">touch .env.dev prod.env
</code></pre><p>These files will hold environment variables for development and production, respectively. Let&rsquo;s proceed to update their contents.</p><p>Update the <code>.env.dev</code> file with the following:</p><pre class=" language-js"><code class="prism  language-js">MESSAGE<span class="token operator">=</span><span class="token string">"this is dev message"</span>
</code></pre><p>Update the <code>prod.env</code> file with the following:</p><pre class=" language-js"><code class="prism  language-js">MESSAGE<span class="token operator">=</span><span class="token string">"this is production message"</span>
</code></pre><p>We included a variable named <code>MESSAGE</code> in each file, which holds a dummy message.</p><h3 id="setting-up-and-configuring-the-config-module">Setting Up and Configuring the Config Module</h3><p>Usually, when working with environment variables in a NestJS application, the <code>ConfigModule</code> is the recommended way to use and access them. That is what we will be doing in this guide.</p><p>Let&rsquo;s now install it in our app.</p><pre class=" language-shell"><code class="prism  language-shell">pnpm install -save @nestjs/config
</code></pre><p>Next, let&rsquo;s update the <code>app.module.ts</code> file to use this module:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AppController <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.controller'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AppService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>

    ConfigModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        isGlobal<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
        ignoreEnvFile<span class="token punctuation">:</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>NODE_ENV <span class="token operator">===</span> <span class="token string">'production'</span><span class="token punctuation">,</span>
        envFilePath<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token string">'.env.dev'</span>
        <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">,</span>
    controllers<span class="token punctuation">:</span> <span class="token punctuation">[</span>AppController<span class="token punctuation">]</span><span class="token punctuation">,</span>
    providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>AppService<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppModule</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
</code></pre><p>The <code>envFilePath</code> points to files where we want to load environment variables from. We only specified <code>.env.dev</code> since we will be using that during development. However, in production, we ignore all <code>.env</code> files since they will be injected for us automatically in our container.</p><p>Note that the <code>process.env.NODE_ENV</code> variable will be automatically injected by the Cloud Build service when building the image and will default to production. We can override this value in the <code>project.toml</code> file in case we want different behavior in different environments.</p><p>Let&rsquo;s now update the <code>app.service.ts</code> file and update its <code>getHello</code> method:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppService</span> <span class="token punctuation">{</span>
    <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly configService<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token punctuation">{</span>

    <span class="token punctuation">}</span>
    <span class="token function">getHello</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span>getOrThrow<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'MESSAGE'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>getHello</code> method is now updated to return the contents of the <code>MESSAGE</code> environment variable.</p><p>Start the application locally by running:</p><pre class=" language-shell"><code class="prism  language-shell">pnpm run start:dev
</code></pre><p>If we visit localhost:3000 in our browser, we see the contents of the message from the <code>.env.dev</code> variable, as shown below:</p><p><img src="https://www.telerik.com/sfimages/default-source/blogs/2026/2026-05/message-from-env-dev-variable.png" alt="Message from the .env.dev variable" title="Message from the .env.dev variable" /></p><p>Let&rsquo;s now proceed to build and deploy a new version of our service.</p><pre class=" language-shell"><code class="prism  language-shell">gcloud builds submit --pack image=europe-west2-docker.pkg.dev/dummy-nest-swish0062/dummy-nest-repo/dummy-app:0.0002
</code></pre><p>The only notable change in the build command is that the new image has a tag of 0002. Next, let&rsquo;s deploy it.</p><pre class=" language-shell"><code class="prism  language-shell">gcloud run deploy my-nestjs-app  --image europe-west2-docker.pkg.dev/dummy-nest-swish0062/dummy-nest-repo/dummy-app:0.0002 --env-vars-file prod.env 
</code></pre><p>Notice we included the <code>&ndash;env-vars-file</code> option and set it to the <code>prod.env</code>.</p><blockquote><p>As a side note, verify that the file extension of the file holding environment variables that you intend to ship to production ends with <code>.env</code> (e.g., prod.env in our case). Setting it to <code>.env.prod</code> will not work.</p></blockquote><p>On the GCP console, look at our service and click on <strong>Edit and deploy new revision</strong>.<br />In the <strong>Variables and Secrets</strong> tab under <strong>Edit Container</strong>, we see the newly added variable, as shown below:</p><p><img title="Viewing runtime environment variables for servicee" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/viewing-runtime-environment-variables.png?sfvrsn=19e97179_2" alt="Viewing runtime environment variables for service" /></p><p>When we visit our service URL, we get the new message, as shown below:</p><p><img title="Production service url outputs contents of the injected runtime environment variable" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/production-service-url-output.png?sfvrsn=55f065b_2" alt="Production service url outputs contents of the injected runtime environment variable" /></p><h2 id="best-practices">Best Practices</h2><p>In this section, we will discuss a few best practices to keep in mind when deploying containerized applications on Google Cloud Run. We have already established the fact that when deploying services, we can have billing set either per request (default) or per instance. We will go over some general guidelines for all services to be deployed on Cloud Run and runtime-specific guidelines for Node.js.</p><ol><li>If services are configured to run as request-based, they should not perform background tasks since their runtime is short-lived (i.e., limited to only when there are incoming requests). If you need services to perform background tasks, run them as instance-based and set at least one running instance.</li><li>Start containers quickly. This can be done by keeping containers lightweight and removing unnecessary dependencies in your code. Also, if you are building the image yourself, use stable and community-maintained base images.</li><li>For Node.js applications, minimize the number of dependencies. When using many dependencies, you should employ lazy loading to only load them when necessary to minimize startup time. Preferably, start your containers using <code>node</code> instead of <code>npm</code>. For example, in our case we started our container using <code>node dist/main</code> instead of <code>npm run start</code>, which is slower.</li></ol><h2 id="next-steps">Next Steps</h2><p>When it comes to deploying applications, there is no one-size-fits-all approach. We have covered one of the numerous ways to deploy our application on GCP using the Cloud Run service. However, we can still make some improvements in our approach. The obvious one is the fact that we have to manually type each command.</p><p>Also, to streamline our workflow, we need to enable CI/CD to provide a better experience when deploying our code. We can use GitHub Actions and any of the Google Cloud Run provided workflows to automate that process.</p><p><img title="Google Cloud Run workflows" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-05/google-cloud-run-workflows.png?sfvrsn=1024a270_2" alt="Google Cloud Run workflows" /></p><h2 id="conclusion">Conclusion</h2><p>While there are numerous options for deploying applications, in this guide we focused on using Google Cloud and the Cloud Run service. Hopefully, this will serve as a potential option for deploying your applications in future projects.</p><hr /><p><strong>Read more:</strong> <a href="https://www.telerik.com/blogs/build-nestjs-ai-chatbot-google-gemini" target="_blank">How to Build a NestJS AI Chatbot with Google Gemini</a></p><img src="https://feeds.telerik.com/link/10827/17345725.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:ce866956-4550-4c69-a546-7ae35e4fdbc6</id>
    <title type="text">Creating Reliable AI Resources with Telerik Agent Tools API</title>
    <summary type="text">The RAG resources you create to use with your LLMs are strategic resources, just like your organization’s databases. Telerik Agent Tools let you create the custom tools for managing those resources.</summary>
    <published>2026-05-20T13:36:52Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Peter Vogel </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17344837/creating-reliable-ai-resources-telerik-agent-tools-api"/>
    <content type="text"><![CDATA[<p><span class="featured">The RAG resources you create to use with your LLMs are strategic resources, just like your organization&rsquo;s databases. Telerik Agent Tools let you create the custom tools for managing those resources.</span></p><p>Like your organization&rsquo;s databases, you should regard your AI resources as strategic, <em>organizational</em> resources. And, like the data in your organization&rsquo;s databases, you need to manage the content used by your AI resources to enable those resources give you reliable, grounded answers. Telerik Agent Tools API lets gives you the tools for automating the process of creating and maintaining the content in your AI resources.</p><p>In an earlier post, I showed how to use Progress Telerik <a target="_blank" href="https://www.telerik.com/document-processing-libraries">Document Processing Libraries</a> (DPL) to create an <a target="_blank" href="https://www.telerik.com/blogs/creating-custom-ai-agent-telerik-tools-1-configuring-llm-azure-ollama">AI resource with content from existing documents and then tie that resource to a Large Language Model</a> (LLM) that your users could query.</p><p>Tying an LLM to your content creates a <a target="_blank" href="https://www.telerik.com/blogs/understanding-rag-retrieval-augmented-generation">Resource Augmented Generation</a> (RAG) resource that helps get your users grounded answers driven by the content you load into your resource. And using the DPL toolset is a great solution when you&rsquo;re building an application that creates a RAG resource &ldquo;as needed.&rdquo;</p><p>But if you want to reliably create/update a RAG resource that will be used (and reused) by multiple applications and multiple users, then you need the Telerik Agent Tools API. Once you have a reliable process for creating your RAG resource, the Agent Tools API also lets you then use that resource to support your users by integrating with Microsoft&rsquo;s latest toolset for responding to your user&rsquo;s prompts.</p><p>In this post, I&rsquo;m going to cover how to construct an automated workflow for creating a RAG resource that can be used across applications and users. In my next post, I&rsquo;ll cover how to use that resource with the LLM of your choice.</p><h2 id="building-a-rag-resource-workflow">Building a RAG Resource Workflow</h2><p>You can use the Tools API to create an interactive application that lets you manage the documents that make up your resource&rsquo;s content. However, for this post, I&rsquo;m going to assume a simpler process than that (though I&rsquo;ll cover all the tools you&rsquo;d need for an interactive solution).</p><p>For this post, I&rsquo;m going to assume there&rsquo;s a folder that holds all the documents with the content that should be in my RAG resource (which might be Word documents, spreadsheets, PDF, text files&mdash;basically, all the content that you can load with Telerik DPL tools). Users control what goes into my resource by adding and removing documents from that folder.</p><p>In this case study, then, I build my RAG resource by running a batch program that reads all the documents in the folder, adds them all to a RAG resource in memory, and then saves the new resource, replacing any existing version with a new, updated version.</p><h2 id="configuring-your-project">Configuring Your Project</h2><p>To start building an application to manage your RAG resource, you must first add the Telerik.Documents.AI.Tools.Core NuGet package to your project. After that, you need the specific Telerik packages that support the document types that you&rsquo;ll add to your RAG resource.</p><p>For my case study, I&rsquo;m just going to use PDF documents, so I just need the &ldquo;Fixed&rdquo; packages:</p><pre><code>- Telerik.Documents.AI.Tools.Fixed.Core
- Telerik.Documents.AI.AgentTools.Fixed 
</code></pre><p>If you&rsquo;re working with spreadsheets, you&rsquo;ll want to add the equivalent Spreadsheet packages:<br />- Telerik.Documents.AI.Tools.Spreadsheet.Core<br />-Telerik.Documents.AI.AgentTools.Spreadsheet)</p><p>To support Microsoft Word, HTML and text documents, you also need to add the Telerik.Documents.AI.Tools.Flow.Core package.</p><p>And, of course, there&rsquo;s no reason you couldn&rsquo;t make your life simpler by adding all the <a target="_blank" href="http://Telerik.Documents.AI">Telerik.Documents.AI</a>.* libraries to your application, even if you don&rsquo;t need to use them all right now. In this case study, I also didn&rsquo;t take advantage of the some of the other packages available&mdash;the Telerik.Documents.AI.AgentTools.Conversion package that handles conversions between document types, for example.</p><h2 id="building-your-rag-resource">Building Your RAG Resource</h2><p>The process for creating a RAG resource begins with creating an in-memory repository which holds your documents. A repository can hold one of three categories of documents: spreadsheets, PDF documents or Word and Word-related documents.</p><p>In my case study, I&rsquo;m only going to work with PDF documents (which Progress Telerik classifies as &ldquo;Fixed&rdquo; documents), so I created an <code>inMemoryFixedDocumentRepository</code> object and then stored a reference to that repository in an <code>IFixedDocumentRepository</code> variable.</p><p>The code to do that looks like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">IFixedDocumentRepository pdfRepo <span class="token operator">=</span> 
          <span class="token keyword">new</span> <span class="token class-name">InMemoryFixedDocumentRepository</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">TimeSpan</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>All of the three types of repositories look very much alike though (they all share a common <code>IDocumentRepository</code> interface, for example). As a result, working with other document types is similar.</p><p>If, for example, you were loading spreadsheet files (&ldquo;Workbook&rdquo; documents), you&rsquo;d use the <code>InMemoryWorkbookRepository</code> object and the <code>IWorkbookRepository</code> interface. For Word and Word-related documents (&ldquo;Flow&rdquo; documents), you&rsquo;d use the <code>InMemoryFlowDocumentRepository</code> object and the <code>IFlowDocumentRepository</code> interface.</p><p>Repositories have some built-in functionality (they all include a <code>ListDocuments</code> method that returns a list of documents in the repository, for example). You will, however, want to extend your repository by attaching one or more toolsets containing tools that add additional functionality to your repository.</p><p>For my case study, I just needed to be able to import documents into an empty repository so the only toolset I attached was the <code>FixedFileManagementAgentTools</code> toolset that supports working with a PDF repository and has import (and export) methods for individual documents. To that toolset looks like this, I pass a reference to my repository, I pass a reference to my repository and the path to the folder containing the documents that the tools will work with:</p><pre class=" language-csharp"><code class="prism  language-csharp">FixedFileManagementAgentTools pdfFileTools <span class="token operator">=</span> 
            <span class="token keyword">new</span> <span class="token class-name">FixedFileManagementAgentTools</span><span class="token punctuation">(</span>pdfRepo<span class="token punctuation">,</span> repoFolderPath<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>You can add additional toolsets if you need additional functionality or need to work with more folders. If, for example, I wanted to work with PDF forms, I could add the <code>FixedDocumentFormAgentTools</code> toolset to my repository. When creating a toolset, you&rsquo;ll always need to pass a reference to the repository the toolset will be attached to, but other parameters (if any) will vary from one toolset to another.</p><h2 id="managing-repositories-with-registries">Managing Repositories with Registries</h2><p>If you&rsquo;re going to be working with multiple repositories in your workflow, you might want to create a registry to simplify managing your repositories. In my case study, for example, where I&rsquo;m potentially loading several different kinds of files, I could end up supporting all three types of repositories (PDF files, spreadsheets, Word and Word-related documents)&mdash;creating a registry simplifies switching between the different types of repositories.</p><p>Registries are created using the <code>DocumentRepositoryRegistry</code> object and support registering one repository of each document category using their <code>RegisterRespository</code> method.</p><p>You can then retrieve the repository you want from a registry by using the registry&rsquo;s <code>TryGetRepository</code> method, passing two parameters: the document type of the repository you want and an <code>out</code> parameter of type <code>IDocumentRepository</code>. Like other TryGet* methods, the <code>TryGetRepository</code> method returns return true if the method finds the repository you want and false if the method does not (the actual repository, if found, is loaded into the method&rsquo;s second, <code>out</code> parameter).</p><p>The following code creates a registry, registers my PDF repository using the <code>RegisterRepository</code> method and then immediately retrieves the repository using the registry&rsquo;s <code>TryGetRepository</code> method. Because the repository is returned using the common <code>IDocumentRepository</code> interface type, I cast the returned reference to the <code>IFixedDocumentRepository</code> type before working with it:</p><pre class=" language-csharp"><code class="prism  language-csharp">DocumentRepositoryRegistry registry <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

registry<span class="token punctuation">.</span><span class="token function">RegisterRepository</span><span class="token punctuation">(</span>DocumentType<span class="token punctuation">.</span>FixedDocument<span class="token punctuation">,</span> pdfRepo<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">if</span> <span class="token punctuation">(</span>registry<span class="token punctuation">.</span><span class="token function">TryGetRepository</span><span class="token punctuation">(</span>DocumentType<span class="token punctuation">.</span>FixedDocument<span class="token punctuation">,</span> 
                                                           <span class="token keyword">out</span> IDocumentRepository<span class="token operator">?</span> repo<span class="token punctuation">)</span> <span class="token punctuation">)</span>
<span class="token punctuation">{</span>
     IFixedDocumentRepository fixedRepo <span class="token operator">=</span> <span class="token punctuation">(</span>IFixedDocumentRepository<span class="token punctuation">)</span> repo<span class="token punctuation">;</span>
    <span class="token comment">//&hellip;work with the fixedRepo repository</span>
<span class="token punctuation">}</span>
</code></pre><p>You&rsquo;ll also want to create a registry if, as part of your workflow, you want to support converting documents between formats or merging multiple documents. The toolsets that support that are <code>ConvertDocumentsAgentTool</code> and <code>MergeDocumentsAgentTool</code> toolsets and they attach to registries rather than repositories.</p><h2 id="loading-saving-and-retrieving-your-repository">Loading, Saving and Retrieving Your Repository</h2><p>Adding a document to a repository is easy: Just call the appropriate import method on the appropriate toolset that&rsquo;s tied to the repository you want to update.</p><p>To add a PDF document to my repository, for example, I&rsquo;d use the <code>ImportFixedDocument</code> method on my <code>FixedFileManagementAgentTools</code> toolset, tied to my PDF repository. To use the <code>ImportFixedDocument</code> method, I pass the path to the document I want to load and its document format (e.g., PDF, XLSX, etc.). Optionally, I can pass a name for the document which will be returned as part of the <code>ListDocuments</code> method.</p><p>Which means that importing a PDF document into my repository would look like this:</p><pre class=" language-csharp"><code class="prism  language-csharp">CallToolResponse res <span class="token operator">=</span>
          pdfRepoTools<span class="token punctuation">.</span><span class="token function">ImportFixedDocument</span><span class="token punctuation">(</span>documentPath<span class="token punctuation">,</span> 
                                                                                         DocumentFormat<span class="token punctuation">.</span>PDF<span class="token punctuation">,</span>
                                                                                          Path<span class="token punctuation">.</span><span class="token function">GetFileNameWithoutExtension</span><span class="token punctuation">(</span>documentPath<span class="token punctuation">)</span>
                                                                                        <span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>The <code>CallToolResponse</code> object&rsquo;s <code>IsError</code> property will be set to <code>true</code> if your import succeeds (and the <code>Message</code> property will contain additional information regardless of whether the call succeeds or fails).</p><p>Once I&rsquo;ve added all the documents from my folder to my repository, I can save my repository with all of its content to a single file using my repository&rsquo;s <code>MergeAndExport</code> method. The <code>MergeAndExport</code> method requires three parameters:</p><ul><li>An array of the <code>id</code> property for each of the documents in the repository. You can use that array both to control which documents are exported and the merge order of the documents. I didn&rsquo;t bother and just used a LINQ <code>Select</code> method to retrieve all the <code>id</code> property values.</li><li>A <code>FileStream</code> object that points to your repository file (I set this up to overwrite my repository every time).</li><li>The document type for the file (<code>PDF</code>, in my case).</li></ul><p>You must close your <code>FileStream</code> after you finish exporting your documents. As a result, the code to export my repository of PDF documents would look like this:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> ids <span class="token operator">=</span> pdfRepo<span class="token punctuation">.</span><span class="token function">ListDocuments</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span> doc <span class="token operator">=</span><span class="token operator">&gt;</span> doc<span class="token punctuation">.</span>Id <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
FileStream fs <span class="token operator">=</span>  <span class="token keyword">new</span> <span class="token class-name">FileStream</span><span class="token punctuation">(</span>repoFilePath<span class="token punctuation">,</span> FileMode<span class="token punctuation">.</span>CreateNew<span class="token punctuation">)</span><span class="token punctuation">,</span>
                                 
repo<span class="token punctuation">.</span><span class="token function">MergeAndExport</span><span class="token punctuation">(</span>ids<span class="token punctuation">,</span>
                                                fs<span class="token punctuation">,</span>  
                                                DocumentFormat<span class="token punctuation">.</span>PDF<span class="token punctuation">)</span><span class="token punctuation">;</span>
fs<span class="token punctuation">.</span><span class="token function">Close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre><p>You can now tie that file to an LLM using Microsoft <code>ChatClientAgent</code> object to let your users query your RAG resource. I&rsquo;ll walk through how to do that in my next post.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Get Access to These Tools and More</h4></div><div class="col-8"><p class="u-fs16 u-mb0">The free 30-day trial of <a target="_blank" href="https://www.telerik.com/devcraft">Telerik DevCraft</a> lets you really kick the tires for yourself. <a target="_blank" href="https://www.telerik.com/try/devcraft-ultimate">Try it today!</a></p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17344837.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:c37f48f7-cf9d-4e8d-95b3-010546859051</id>
    <title type="text">Protect Your Entities with Domain Validation</title>
    <summary type="text">Learn about error-prone domain validations in ASP.NET Core and how to correctly model them.</summary>
    <published>2026-05-19T13:32:48Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17344160/protect-entities-domain-validation"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn about error-prone domain validations in ASP.NET Core and how to correctly model them.</span></p><p>In ASP.NET Core applications, developers often underestimate the importance of validating objects and classes. An <code>if</code> statement in the controller, a <code>FluentValidation</code> in the request or some <code>DataAnnotations</code> in the model, and that&rsquo;s it. The problem is that, if care isn&rsquo;t taken when implementing domain validations, responsibility ends up leaking, which can compromise the entire system&rsquo;s evolution.</p><p>In this article, we will analyze common examples of domain validation that are highly prone to errors and how these validations should be correctly modeled according to the principles of Domain-Driven Design (DDD).</p><h2 id="poorly-structured-validations">Poorly Structured Validations</h2><h3 id="validation-in-the-controller">1. Validation in the Controller</h3><p>Although it speeds up development, implementing validations in API controllers is discouraged, as it makes the controller highly coupled to business rules. Furthermore, the rules created there are impossible to reuse. Finally, validations in controllers allow other parts of the code to execute the same actions, bypassing the rules.</p><p>Consider the following example:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span><span class="token function">Route</span><span class="token punctuation">(</span><span class="token string">"api/[controller]"</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
<span class="token punctuation">[</span>ApiController<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderController</span> <span class="token punctuation">:</span> ControllerBase
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> OrderRepository _orderRepository<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">OrderController</span><span class="token punctuation">(</span>OrderRepository orderRepository<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _orderRepository <span class="token operator">=</span> orderRepository<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span>HttpPost<span class="token punctuation">]</span>
    <span class="token keyword">public</span> IActionResult <span class="token function">Create</span><span class="token punctuation">(</span>CreateOrderDto dto<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>dto<span class="token punctuation">.</span>Total <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token function">BadRequest</span><span class="token punctuation">(</span><span class="token string">"Total must be greater than zero"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>dto<span class="token punctuation">.</span>Items <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> <span class="token operator">!</span>dto<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token function">BadRequest</span><span class="token punctuation">(</span><span class="token string">"Order must have at least one item"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            Total <span class="token operator">=</span> dto<span class="token punctuation">.</span>Total<span class="token punctuation">,</span>
            Items <span class="token operator">=</span> dto<span class="token punctuation">.</span>Items
        <span class="token punctuation">}</span><span class="token punctuation">;</span>

        _orderRepository<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> <span class="token function">Ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The code above is a common example of validations in the Controller, and it&rsquo;s a bad example because it has all the flaws mentioned earlier. Note that the <code>_orderRepository.Add(order);</code> method is called at the end, meaning there are loopholes here.</p><p>Imagine that the parameter <code>CreateOrderDto dto</code> has a total greater than zero, and the Items list has one item, but the rest of the properties are null. Even so, the <code>Add</code> method will be called and will create problematic entities or generate a bug.</p><h3 id="validation-in-the-service-class">2. Validation in the Service Class</h3><p>Although common, the use of validations in the Service class should also be avoided because they place business rules in the wrong place. Considering the previous example, they only changed location but still remain a problem.</p><p>When a business rule is coupled to a Service, the domain model becomes passive, and entities can be created or modified in invalid states because there is nothing to prevent them from doing so. The result is a fragile system, as object validation ceases to be a priority and becomes solely dependent on the execution flow.</p><p>Another problem is rule duplication. In large systems, the same rule is often needed in more than one use case. When it is in the Service, it ends up being copied to other services, handlers or jobs, which increases the risk of inconsistency and hinders business evolution.</p><p>Finally, the Service Layer is responsible for orchestrating use cases, coordinating repositories, transactions and external calls. When it validates domain rules, it mixes responsibilities and becomes a central point of complexity, bloated with if statements and exceptions that don&rsquo;t belong to it.</p><p>The example below shows what a Service class looks like with business rules defined in it:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderService</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> OrderRepository _orderRepository<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">OrderService</span><span class="token punctuation">(</span>OrderRepository orderRepository<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _orderRepository <span class="token operator">=</span> orderRepository<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Create</span><span class="token punctuation">(</span>CreateOrderDto dto<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>dto<span class="token punctuation">.</span>Total <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"Total must be greater than zero"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dto<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"Order must have at least one item"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span><span class="token punctuation">(</span>dto<span class="token punctuation">.</span>Total<span class="token punctuation">,</span> dto<span class="token punctuation">.</span>Items<span class="token punctuation">)</span><span class="token punctuation">;</span>
        _orderRepository<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that the logic of <code>if</code> and <code>else</code> statements remains the same, it has only changed location, allowing objects to change state and create corrupted states.</p><h3 id="using-data-annotations">3. Using Data Annotations</h3><p>Another common form of validation is through the Data Annotations Model Binder, which is implemented using attributes placed above the properties of an entity class.</p><p>The problem is that, as they make the domain dependent on a framework, the validation only works in binding (MVC), and they may not work in non-web scenarios such as workers, messaging and tests.</p><pre class=" language-csharp"><code class="prism  language-csharp"> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span>
  <span class="token punctuation">{</span>
      <span class="token punctuation">[</span>Required<span class="token punctuation">]</span>
      <span class="token punctuation">[</span><span class="token function">Range</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token keyword">double</span><span class="token punctuation">.</span>MaxValue<span class="token punctuation">)</span><span class="token punctuation">]</span>
      <span class="token keyword">public</span> <span class="token keyword">decimal</span> Total <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
      <span class="token keyword">public</span> List<span class="token operator">&lt;</span>OrderItem<span class="token operator">&gt;</span> Items <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
</code></pre><p>Note that we are requiring the Total property to have a minimum value of 1. But even so, it is not yet a secure validation because it is still possible to create an entity with an invalid state.</p><h2 id="domain-validation-with-ddd">Domain Validation with DDD</h2><p>Domain-Driven Design emphasizes the importance of keeping validations within the domain. The main reason is that this way, the domain becomes self-protecting.</p><p>Entities and aggregates cease to be passive structures (as seen in previous examples) and ensure that their rules are always respected, regardless of where or how they are used.</p><p>Another positive aspect of this approach is the model&rsquo;s coherence. When business rules are in the domain, they are closer to the concept they represent, making the code more expressive and easier to understand. Reading an entity or a behavioral method reveals the rules governing that concept. In other words, you understand the intention behind that behavior.</p><p>Finally, implementing validations in the domain makes system evolution safer. New use cases can be added without fear of breaking existing rules because the domain itself acts as a safety barrier. This reduces maintenance costs and makes the system more resilient to business growth and changes.</p><p>Next, we&rsquo;ll look at an example of a domain guided by DDD principles and analyze each point. You can access the source code in this GitHub repository: <a target="_blank" href="https://github.com/zangassis/domain-validations">Domain Validations code</a>.</p><p>Order class with Domain Validations:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> List<span class="token operator">&lt;</span>OrderItem<span class="token operator">&gt;</span> _items <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">public</span> Guid Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> DateTime CreatedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> IReadOnlyCollection<span class="token operator">&lt;</span>OrderItem<span class="token operator">&gt;</span> Items <span class="token operator">=</span><span class="token operator">&gt;</span> _items<span class="token punctuation">.</span><span class="token function">AsReadOnly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Total <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token function">Order</span><span class="token punctuation">(</span>IEnumerable<span class="token operator">&lt;</span>OrderItem<span class="token operator">&gt;</span> items<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>items <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> <span class="token operator">!</span>items<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order must have at least one item."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Id <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">;</span>

        <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> item <span class="token keyword">in</span> items<span class="token punctuation">)</span>
            <span class="token function">AddItem</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">ValidateItemsQuantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">AddItem</span><span class="token punctuation">(</span>OrderItem item<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>item <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order item cannot be null."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        _items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">RecalculateTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">RecalculateTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Total <span class="token operator">=</span> _items<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>i <span class="token operator">=</span><span class="token operator">&gt;</span> i<span class="token punctuation">.</span>Subtotal<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>Total <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order total must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">ValidateItemsQuantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>_items<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order cannot exist without items."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Item class with Domain Validations:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderItem</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> Guid ProductId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Price <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Quantity <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Subtotal <span class="token operator">=</span><span class="token operator">&gt;</span> Price <span class="token operator">*</span> Quantity<span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token function">OrderItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment">// EF</span>

    <span class="token keyword">public</span> <span class="token function">OrderItem</span><span class="token punctuation">(</span>Guid productId<span class="token punctuation">,</span> <span class="token keyword">decimal</span> price<span class="token punctuation">,</span> <span class="token keyword">int</span> quantity<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>productId <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"ProductId is required."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>price <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Price must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>quantity <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Quantity must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        ProductId <span class="token operator">=</span> productId<span class="token punctuation">;</span>
        Price <span class="token operator">=</span> price<span class="token punctuation">;</span>
        Quantity <span class="token operator">=</span> quantity<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="private-setters-and-readonly-properties">Private Setters and ReadOnly Properties</h3><p>The first aspect we can notice in this new version of the Order class is that the Items property is private, which means it is inaccessible outside the class: <code>private readonly List&lt;OrderItem&gt; _items = new();</code>.</p><p>We also have a public list of items: <code>public IReadOnlyCollection&lt;OrderItem&gt; Items =&gt; _items.AsReadOnly();</code>, but note that it is defined as read-only. This means that it can be accessed by external sources, but only for reading the data and never for modification.</p><p>Another factor protecting the class properties is that, despite being public, they have private setters: <code>public Guid Id { get; private set; }</code>, preventing external sources from modifying their states.</p><h3 id="protected-constructor">Protected Constructor</h3><p>Protecting the constructor method means allowing only valid states of an entity to be created. In our example, we are defining rules within the constructor:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token function">Order</span><span class="token punctuation">(</span>IEnumerable<span class="token operator">&lt;</span>OrderItem<span class="token operator">&gt;</span> items<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>items <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> <span class="token operator">!</span>items<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order must have at least one item."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Id <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">;</span>

    <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> item <span class="token keyword">in</span> items<span class="token punctuation">)</span>
        <span class="token function">AddItem</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">ValidateItemsQuantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">AddItem</span><span class="token punctuation">(</span>OrderItem item<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>item <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order item cannot be null."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    _items<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">RecalculateTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">RecalculateTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    Total <span class="token operator">=</span> _items<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>i <span class="token operator">=</span><span class="token operator">&gt;</span> i<span class="token punctuation">.</span>Subtotal<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>Total <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order total must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">ValidateItemsQuantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>_items<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">DomainException</span><span class="token punctuation">(</span><span class="token string">"Order cannot exist without items."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that the item quantity validation is performed within the constructor. That is, when creating a new object state, values are also set for the <code>Id</code> and <code>CreatedAt</code> properties. Finally, the <code>ValidateItemsQuantity</code> method verifies if the private property <code>_items</code> has actually been loaded with items, adding an extra layer of validation. In this way, the database will only receive valid states, a corrupted or incomplete entity will never be inserted, enabling data consistency.</p><h3 id="domain-exceptions">Domain Exceptions</h3><p>Domain exceptions are errors thrown when a business rule (invariant) is violated. Unlike technical failures such as database outages or timeouts, domain exceptions represent violations of the system&rsquo;s business rules, such as an order without items, for example.</p><p>Domain exceptions are important because they help prevent an entity or aggregate from existing in an invalid state. If a rule is broken, the domain needs to react immediately, and the exception is a suitable mechanism to keep inconsistent data from reaching the database or being manipulated in any way.</p><p>In the previous example, we validated whether the value was less than zero and whether the list of items was empty. If either condition is met, a <code>DomainException</code> will be thrown, which is a custom exception. The code below shows how the exception is implemented:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> DomainValidation<span class="token punctuation">;</span>

<span class="token punctuation">[</span>Serializable<span class="token punctuation">]</span>
<span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">DomainException</span> <span class="token punctuation">:</span> Exception
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">DomainException</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token function">DomainException</span><span class="token punctuation">(</span><span class="token keyword">string</span><span class="token operator">?</span> message<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token function">DomainException</span><span class="token punctuation">(</span><span class="token keyword">string</span><span class="token operator">?</span> message<span class="token punctuation">,</span> Exception<span class="token operator">?</span> innerException<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> innerException<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="is-fluentvalidation-still-useful">Is FluentValidation Still Useful?</h3><p>FluentValidation is a widely used .NET library for validations, and even in scenarios where we use the domain validation approach, it certainly remains useful.</p><p>The main functionality of FluentValidation is to validate input data, not business rules. Therefore, by using it in the application, we can obtain an extra layer of validation, preventing corrupted data from reaching the domain.</p><p>The code below demonstrates how FluentValidation can be used to validate input data:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> FluentValidation<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateOrderRequest</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>CreateOrderItemRequest<span class="token operator">&gt;</span> Items <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateOrderRequestValidator</span> <span class="token punctuation">:</span> AbstractValidator<span class="token operator">&lt;</span>CreateOrderRequest<span class="token operator">&gt;</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">CreateOrderRequestValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">RuleFor</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>Items<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">NotNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">WithMessage</span><span class="token punctuation">(</span><span class="token string">"Items cannot be null."</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Must</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">WithMessage</span><span class="token punctuation">(</span><span class="token string">"Order must contain at least one item."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">RuleForEach</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>Items<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">SetValidator</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">CreateOrderItemRequestValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateOrderItemRequest</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> Guid ProductId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Price <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Quantity <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateOrderItemRequestValidator</span> <span class="token punctuation">:</span> AbstractValidator<span class="token operator">&lt;</span>CreateOrderItemRequest<span class="token operator">&gt;</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">CreateOrderItemRequestValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token function">RuleFor</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">NotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">WithMessage</span><span class="token punctuation">(</span><span class="token string">"ProductId is required."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">RuleFor</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>Price<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">GreaterThan</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">WithMessage</span><span class="token punctuation">(</span><span class="token string">"Price must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token function">RuleFor</span><span class="token punctuation">(</span>x <span class="token operator">=</span><span class="token operator">&gt;</span> x<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">GreaterThan</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">WithMessage</span><span class="token punctuation">(</span><span class="token string">"Quantity must be greater than zero."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h2 id="when-out-of-domain-validations-make-sense">When Out-of-Domain Validations Make Sense</h2><p>Validations in the Controller and Service classes make sense when they are not business rules, but rather validations such as security, flow or format.</p><p>It is common to perform authentication/authorization validations in the Controller, because this is the responsibility of the edge (API), and if a user or system is not properly authenticated/authorized, it should not have access to the domain or the rest of the system; it should be blocked at the entrance.</p><p>The Service class, on the other hand, can have validations used to manage operational flows, for example, checking if the customer exists before creating an order, checking if the product exists, checking if there is already an open order for the customer.</p><p>In this way, we do not validate the internal state of the entity. Instead, we validate the interactions between aggregates or external systems.</p><h2 id="conclusion">Conclusion</h2><p>Implementing domain validations in an application means explicitly stating the reason for that domain&rsquo;s existence and the rules that determine its behavior. Furthermore, domain validations keep an invalid object from reaching the database, preventing future bugs and the resulting damage.</p><p>Throughout this post, we&rsquo;ve seen examples of incorrectly implemented validations, scattered across controllers or services, resulting in fragile and difficult-to-maintain code. In contrast, we implemented validations directly on the entities, applying DDD principles.</p><p>I hope this post has helped you see the domain as the heart of the application and the importance of keeping it consistent, protected and expressive.</p><hr /><p><strong>More on DDD: </strong><a href="https://www.telerik.com/blogs/getting-started-domain-driven-design-aspnet-core" target="_blank">Getting Started with Domain-Driven Design in ASP.NET Core</a></p><img src="https://feeds.telerik.com/link/10827/17344160.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:6474f4bb-8be6-4855-88f7-d84c8ef0da59</id>
    <title type="text">How to Build a Multi-Tenant SaaS API with NestJS and Postgres Row-Level Security</title>
    <summary type="text">We’ll build a multi-tenant task management API where customer data is isolated automatically at the database level. We’ll use Postgres Row-Level Security to enforce tenant isolation, with NestJS as our application framework and TypeORM to interact with the database.</summary>
    <published>2026-05-14T12:50:00Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Christian Nwamba </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17341162/how-to-build-multi-tenant-saas-api-nestjs-postgres-row-level-security"/>
    <content type="text"><![CDATA[<p><span class="featured">We&rsquo;ll build a multi-tenant task management API where customer data is isolated automatically at the database level. We&rsquo;ll use Postgres Row-Level Security to enforce tenant isolation, with NestJS as our application framework and TypeORM to interact with the database.</span></p><p>In this post, we will build a multi-tenant task management API where customer data is isolated automatically at the database level. We&rsquo;ll use <a target="_blank" href="https://www.postgresql.org/docs/current/ddl-rowsecurity.html">Postgres Row-Level Security</a> to enforce tenant isolation, with <a target="_blank" href="https://nestjs.com/">NestJS</a> as our application framework and <a target="_blank" href="https://typeorm.io/">TypeORM</a> to interact with the database.</p><p>You&rsquo;ll learn how to set up RLS policies that filter queries by tenant, extract tenant information from JWT tokens, manage tenant onboarding and test that isolation works even against direct ID access attempts. By the end, you&rsquo;ll have a working multi-tenant SaaS API where tenants only see their own data, enforced at the database layer rather than in application code.</p><h2 id="prerequisites">Prerequisites</h2><p>To follow along, you&rsquo;ll need:</p><ul><li>Node.js installed</li><li><a target="_blank" href="https://www.postgresql.org/">Postgres</a> installed locally</li><li>Basic knowledge of NestJS and <a target="_blank" href="https://www.typescriptlang.org/">TypeScript</a></li><li>Basic knowledge of HTTP, RESTful APIs and cURL</li><li>Basic knowledge of JWT authentication</li></ul><aside><hr /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">Introduction to JSON Web Tokens (JWT)
            </h4></div><div class="col-8"><p class="u-fs16 u-mb0"><a target="_blank" href="https://www.telerik.com/blogs/introduction-json-web-tokens-jwt">Learn the basics about using JWTs</a> to implement authorization in web applications.
            </p></div></div><hr class="u-mb3" /></aside><h2 id="what-is-multi-tenancy">What Is Multi-Tenancy?</h2><p>Multi-tenancy is an architecture where one application instance serves multiple customers (tenants). Each tenant&rsquo;s data is completely isolated from other tenants. If Company A creates a task, Company B should not be able to see it.</p><p>There are three main ways to achieve this:</p><ul><li><strong>Database per tenant:</strong> Each tenant gets their own database. This provides maximum isolation but is expensive and difficult to maintain at scale.</li><li><strong>Schema per tenant:</strong> All tenants share one database, but each gets their own schema. This is cheaper than separate databases, but migration management becomes complex.</li><li><strong>Shared schema with RLS:</strong> All tenants share the same database and schema. Isolation is enforced by the database itself using Row-Level Security policies.</li></ul><p>For a quick MVP build, the shared schema approach with RLS provides strong isolation without operational complexity.</p><h2 id="what-is-row-level-security">What Is Row-Level Security?</h2><p>Row-Level Security (RLS) is a Postgres feature that lets you define policies controlling which rows users can see or modify.</p><p>When RLS is enabled on a table, Postgres automatically filters query results based on the policies you define. Even if our application code doesn&rsquo;t add <code>WHERE tenant_id = ?</code>, the database allows tenants to only see their own data.</p><p>When a query runs, Postgres checks the policy conditions and rewrites the query. If our policy says <code>USING (tenant_id = current_setting('app.tenant_id'))</code>, then <code>SELECT * FROM tasks</code> becomes <code>SELECT * FROM tasks WHERE tenant_id = current_setting('app.tenant_id')</code>.<br />This moves security from the application layer (where programmers might make mistakes) to the database layer (where it is enforced automatically).</p><h2 id="project-setup">Project Setup</h2><p>First, create a NestJS project:</p><pre class=" language-shell"><code class="prism  language-shell">nest new task-management-api
cd task-management-api
</code></pre><p>Next, run the following command in your terminal to install our dependencies:</p><pre class=" language-shell"><code class="prism  language-shell">npm install @nestjs/config @nestjs/typeorm typeorm pg bcrypt @nestjs/jwt @nestjs/passport passport passport-jwt nestjs-cls uuid
npm install --save-dev @types/bcrypt @types/uuid @types/passport-jwt
</code></pre><p>Here&rsquo;s what each package does:</p><ul><li><strong>@nestjs/config:</strong> Manages environment variables</li><li><strong>@nestjs/typeorm and typeorm:</strong> ORM for Postgres</li><li><strong>pg:</strong> Postgres driver</li><li><strong>bcrypt:</strong> Password hashing</li><li><strong>@nestjs/jwt and @nestjs/passport:</strong> Create and validate JWT tokens</li><li><strong>passport and passport-jwt:</strong> Provide authentication strategies</li><li><strong>nestjs-cls:</strong> Request-scoped context storage for tracking tenants</li><li><strong>uuid:</strong> Creates unique IDs</li></ul><p>Next, create a <code>.env</code> file at the root of your project and add the following to it:</p><pre class=" language-js"><code class="prism  language-js">DB_HOST<span class="token operator">=</span>localhost
DB_PORT<span class="token operator">=</span><span class="token number">5432</span>
DB_USERNAME<span class="token operator">=</span>app_user
DB_PASSWORD<span class="token operator">=</span>newpassword123
DB_NAME<span class="token operator">=</span>task_management

JWT_SECRET<span class="token operator">=</span>your<span class="token operator">-</span>secret<span class="token operator">-</span>key<span class="token operator">-</span>change<span class="token operator">-</span><span class="token keyword">this</span><span class="token operator">-</span><span class="token keyword">in</span><span class="token operator">-</span>production
JWT_EXPIRATION<span class="token operator">=</span>24h
</code></pre><p>The variables above configure our database connection and JWT settings.</p><p>Now update your app.module.ts file to load these variables:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    ConfigModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      isGlobal<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      envFilePath<span class="token punctuation">:</span> <span class="token string">'.env'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token comment">// ... we will add other modules here later</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h2 id="database-setup">Database Setup</h2><p>Let&rsquo;s set up our Postgres database with the tables and RLS policies needed for tenant isolation.</p><h3 id="connect-to-postgres">Connect to Postgres</h3><p>Open the psql shell (search for &ldquo;SQL Shell (psql)&rdquo; in your Start menu). When prompted:</p><ul><li><strong>Server:</strong> localhost</li><li><strong>Database:</strong> postgres</li><li><strong>Port:</strong> 5432</li><li><strong>Username:</strong> postgres</li><li><strong>Password:</strong> [your postgres password]</li></ul><h3 id="create-database-and-user">Create Database and User</h3><p>Run these commands in the psql shell:</p><pre class=" language-shell"><code class="prism  language-shell">CREATE DATABASE task_management;
CREATE USER app_user WITH PASSWORD 'newpassword123' NOSUPERUSER;
GRANT CONNECT ON DATABASE task_management TO app_user;
\c task_management
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
GRANT USAGE ON SCHEMA public TO app_user;
GRANT CREATE ON SCHEMA public TO app_user;
\q
</code></pre><p>We create <code>app_user</code> with <code>NOSUPERUSER</code> because superusers bypass RLS policies, which would completely break our isolation.</p><h3 id="create-tables-and-rls-policies">Create Tables and RLS Policies</h3><p>Open psql again and connect as app_user:</p><pre class=" language-shell"><code class="prism  language-shell">Server: localhost
Database: task_management
Port: 5432
Username: app_user
Password: newpassword123
</code></pre><p>Now, run the complete SQL setup:</p><pre class=" language-shell"><code class="prism  language-shell">-- Create tenants table (no RLS needed here)
CREATE TABLE tenants (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  name VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Create users table (NO RLS - needed for login)
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  email VARCHAR(255) NOT NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Create tasks table
CREATE TABLE tasks (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
  title VARCHAR(255) NOT NULL,
  description TEXT,
  status VARCHAR(50) DEFAULT 'pending',
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Enable RLS ONLY on tasks table
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Force RLS even for table owner
ALTER TABLE tasks FORCE ROW LEVEL SECURITY;

-- Create RLS policy for tasks with both USING and WITH CHECK
CREATE POLICY tenant_isolation_tasks ON tasks
  USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid)
  WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Create indexes for performance
CREATE INDEX idx_users_tenant_id ON users(tenant_id);
CREATE INDEX idx_tasks_tenant_id ON tasks(tenant_id);
CREATE INDEX idx_tasks_tenant_status ON tasks(tenant_id, status);

-- Exit psql
\q
</code></pre><p>The <code>ENABLE ROW LEVEL SECURITY</code> command turns on RLS for the tasks table. The <code>FORCE ROW LEVEL SECURITY</code> command applies RLS even to the table owner, helping prevent unintended data exposure during manual database work.</p><p>The <code>USING</code> clause in our policy checks if the row&rsquo;s tenant_id corresponds to the session variable app.current_tenant_id. The <code>WITH CHECK</code> clause validates INSERT and UPDATE operations to ensure the tenant_id matches. The <code>true</code> parameter in current_setting tells Postgres to return NULL instead of throwing an error if the variable isn&rsquo;t set.</p><p>We&rsquo;re using UUID primary keys instead of sequential integers because with integers, a malicious tenant could guess other tenants&rsquo; IDs and probe for data leaks through foreign key violations.</p><blockquote><p>The users table doesn&rsquo;t have RLS enabled. This is intentional because we need to query users across tenants during login, before we have a tenant context.</p></blockquote><h2 id="project-structure">Project Structure</h2><p>Our project structure will look like this:</p><pre class=" language-js"><code class="prism  language-js">src<span class="token operator">/</span>
├── auth<span class="token operator">/</span>
│   ├── guards<span class="token operator">/</span>
│   │   └── jwt<span class="token operator">-</span>auth<span class="token punctuation">.</span>guard<span class="token punctuation">.</span>ts
│   ├── strategies<span class="token operator">/</span>
│   │   └── jwt<span class="token punctuation">.</span>strategy<span class="token punctuation">.</span>ts
│   ├── auth<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>ts
│   ├── auth<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
│   ├── auth<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   ├── auth<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
│   └── auth<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
├── database<span class="token operator">/</span>
│   ├── database<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   └── tenant<span class="token operator">-</span>context<span class="token punctuation">.</span>interceptor<span class="token punctuation">.</span>ts
├── middleware<span class="token operator">/</span>
│   └── tenant<span class="token punctuation">.</span>middleware<span class="token punctuation">.</span>ts
├── tasks<span class="token operator">/</span>
│   ├── entities<span class="token operator">/</span>
│   │   └── task<span class="token punctuation">.</span>entity<span class="token punctuation">.</span>ts
│   ├── tasks<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>ts
│   ├── tasks<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
│   ├── tasks<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   └── tasks<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
├── tenants<span class="token operator">/</span>
│   ├── entities<span class="token operator">/</span>
│   │   └── tenant<span class="token punctuation">.</span>entity<span class="token punctuation">.</span>ts
│   ├── tenants<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>ts
│   ├── tenants<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
│   ├── tenants<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   └── tenants<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
├── users<span class="token operator">/</span>
│   ├── entities<span class="token operator">/</span>
│   │   └── user<span class="token punctuation">.</span>entity<span class="token punctuation">.</span>ts
│   ├── users<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   ├── users<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
│   └── users<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
├── app<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
└── main<span class="token punctuation">.</span>ts
</code></pre><p>Use the commands below to create the basic structure:</p><pre class=" language-shell"><code class="prism  language-shell">nest g module tenants &amp;&amp; \
nest g controller tenants &amp;&amp; \
nest g service tenants &amp;&amp; \
nest g module auth &amp;&amp; \
nest g controller auth &amp;&amp; \
nest g service auth &amp;&amp; \
nest g module tasks &amp;&amp; \
nest g controller tasks &amp;&amp; \
nest g service tasks &amp;&amp; \
nest g module users &amp;&amp; \
nest g service users &amp;&amp; \
nest g module database
</code></pre><p>We&rsquo;ll also need to manually create the entities, guards, strategies, middleware and interceptor files later on.</p><h2 id="configure-typeorm">Configure TypeORM</h2><p>TypeORM needs to connect to Postgres and recognize our entities.</p><p>Update the <code>src/app.module.ts</code> file to add TypeORM:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module<span class="token punctuation">,</span> MiddlewareConsumer<span class="token punctuation">,</span> RequestMethod <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigModule<span class="token punctuation">,</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TypeOrmModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ClsModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'nestjs-cls'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TenantMiddleware <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./middleware/tenant.middleware'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TenantsModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tenants/tenants.module'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AuthModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./auth/auth.module'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> UsersModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./users/users.module'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TasksModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tasks/tasks.module'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DatabaseModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./database/database.module'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    ConfigModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      isGlobal<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      envFilePath<span class="token punctuation">:</span> <span class="token string">'.env'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    TypeOrmModule<span class="token punctuation">.</span><span class="token function">forRootAsync</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      inject<span class="token punctuation">:</span> <span class="token punctuation">[</span>ConfigService<span class="token punctuation">]</span><span class="token punctuation">,</span>
      useFactory<span class="token punctuation">:</span> <span class="token punctuation">(</span>config<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'postgres'</span><span class="token punctuation">,</span>
        host<span class="token punctuation">:</span> config<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'DB_HOST'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        port<span class="token punctuation">:</span> config<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'DB_PORT'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        username<span class="token punctuation">:</span> config<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'DB_USERNAME'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        password<span class="token punctuation">:</span> config<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'DB_PASSWORD'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        database<span class="token punctuation">:</span> config<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'DB_NAME'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        entities<span class="token punctuation">:</span> <span class="token punctuation">[</span>__dirname <span class="token operator">+</span> <span class="token string">'/**/*.entity{.ts,.js}'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        synchronize<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    ClsModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      global<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      middleware<span class="token punctuation">:</span> <span class="token punctuation">{</span> mount<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    AuthModule<span class="token punctuation">,</span>
    TenantsModule<span class="token punctuation">,</span>
    UsersModule<span class="token punctuation">,</span>
    TasksModule<span class="token punctuation">,</span>
    DatabaseModule<span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppModule</span> <span class="token punctuation">{</span>
  <span class="token function">configure</span><span class="token punctuation">(</span>consumer<span class="token punctuation">:</span> MiddlewareConsumer<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    consumer
      <span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>TenantMiddleware<span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">exclude</span><span class="token punctuation">(</span>
        <span class="token punctuation">{</span> path<span class="token punctuation">:</span> <span class="token string">'tenants/register'</span><span class="token punctuation">,</span> method<span class="token punctuation">:</span> RequestMethod<span class="token punctuation">.</span>POST <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">{</span> path<span class="token punctuation">:</span> <span class="token string">'auth/login'</span><span class="token punctuation">,</span> method<span class="token punctuation">:</span> RequestMethod<span class="token punctuation">.</span>POST <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">forRoutes</span><span class="token punctuation">(</span><span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>We set <code>synchronize: false</code> because we created our tables manually with RLS policies. If we let TypeORM auto-generate tables, it won&rsquo;t add the RLS policies.</p><p>The ClsModule provides request-scoped storage that persists across async operations. When we set a value in the CLS store, it is only accessible within that specific request&rsquo;s execution context. The ClsModule is configured with <code>global: true</code>, so it is available throughout the entire application without importing it into every module. The <code>middleware: { mount: true }</code> setting tells CLS to automatically track the request context as it passes through our application.</p><p>The configure method at the bottom sets up our tenant middleware to run on all routes except registration and login. These two endpoints need to be public because users don&rsquo;t have tokens yet when they&rsquo;re signing up or logging in. We&rsquo;ll create the middleware file and add the logic later.</p><h2 id="creating-entities">Creating Entities</h2><h3 id="tenant-entity">Tenant Entity</h3><p>Create a file named <code>tenant.entity.ts</code> in the <code>src/tenants/entities/</code> folder:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Entity<span class="token punctuation">,</span> PrimaryGeneratedColumn<span class="token punctuation">,</span> Column<span class="token punctuation">,</span> CreateDateColumn <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'typeorm'</span><span class="token punctuation">;</span>

@<span class="token function">Entity</span><span class="token punctuation">(</span><span class="token string">'tenants'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Tenant</span> <span class="token punctuation">{</span>
  @<span class="token function">PrimaryGeneratedColumn</span><span class="token punctuation">(</span><span class="token string">'uuid'</span><span class="token punctuation">)</span>
  id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span> length<span class="token punctuation">:</span> <span class="token number">255</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  name<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">CreateDateColumn</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'created_at'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  createdAt<span class="token punctuation">:</span> Date<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="user-entity">User Entity</h3><p>Create a file named <code>user.entity.ts</code> in the <code>src/users/entities/</code> folder:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Entity<span class="token punctuation">,</span> PrimaryGeneratedColumn<span class="token punctuation">,</span> Column<span class="token punctuation">,</span> CreateDateColumn <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'typeorm'</span><span class="token punctuation">;</span>

@<span class="token function">Entity</span><span class="token punctuation">(</span><span class="token string">'users'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span>
  @<span class="token function">PrimaryGeneratedColumn</span><span class="token punctuation">(</span><span class="token string">'uuid'</span><span class="token punctuation">)</span>
  id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'tenant_id'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'uuid'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  tenantId<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span> length<span class="token punctuation">:</span> <span class="token number">255</span><span class="token punctuation">,</span> unique<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'password_hash'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span> length<span class="token punctuation">:</span> <span class="token number">255</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  passwordHash<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">CreateDateColumn</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'created_at'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  createdAt<span class="token punctuation">:</span> Date<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="task-entity">Task Entity</h3><p>Create a file named <code>task.entity.ts</code> in the <code>src/tasks/entities/</code> folder:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Entity<span class="token punctuation">,</span> PrimaryGeneratedColumn<span class="token punctuation">,</span> Column<span class="token punctuation">,</span> CreateDateColumn<span class="token punctuation">,</span> UpdateDateColumn <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'typeorm'</span><span class="token punctuation">;</span>

@<span class="token function">Entity</span><span class="token punctuation">(</span><span class="token string">'tasks'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Task</span> <span class="token punctuation">{</span>
  @<span class="token function">PrimaryGeneratedColumn</span><span class="token punctuation">(</span><span class="token string">'uuid'</span><span class="token punctuation">)</span>
  id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'tenant_id'</span><span class="token punctuation">,</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'uuid'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  tenantId<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span> length<span class="token punctuation">:</span> <span class="token number">255</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'text'</span><span class="token punctuation">,</span> nullable<span class="token punctuation">:</span> <span class="token keyword">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  description<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">Column</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token keyword">type</span><span class="token punctuation">:</span> <span class="token string">'varchar'</span><span class="token punctuation">,</span> length<span class="token punctuation">:</span> <span class="token number">50</span><span class="token punctuation">,</span> <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">'pending'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  status<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  @<span class="token function">CreateDateColumn</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'created_at'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  createdAt<span class="token punctuation">:</span> Date<span class="token punctuation">;</span>

  @<span class="token function">UpdateDateColumn</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'updated_at'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
  updatedAt<span class="token punctuation">:</span> Date<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h2 id="building-the-tenant-context-system">Building the Tenant Context System</h2><p>The main challenge in using RLS with Node.js is propagating the tenant ID through asynchronous operations.</p><p>Since Node.js handles multiple requests concurrently, we can&rsquo;t use global variables, as they would be overwritten. We need request-scoped storage that persists across async boundaries.</p><h3 id="creating-the-tenant-middleware">Creating the Tenant Middleware</h3><p>The middleware extracts the tenant ID from the JWT and stores it in the CLS context.</p><p>Create a file named <code>tenant.middleware.ts</code> in the <code>src/middleware/</code> folder:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable<span class="token punctuation">,</span> NestMiddleware<span class="token punctuation">,</span> UnauthorizedException <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Request<span class="token punctuation">,</span> Response<span class="token punctuation">,</span> NextFunction <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'express'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ClsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'nestjs-cls'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> JwtService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/jwt'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TenantMiddleware</span> <span class="token keyword">implements</span> <span class="token class-name">NestMiddleware</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> readonly cls<span class="token punctuation">:</span> ClsService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> readonly jwtService<span class="token punctuation">:</span> JwtService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token function">use</span><span class="token punctuation">(</span>req<span class="token punctuation">:</span> Request<span class="token punctuation">,</span> res<span class="token punctuation">:</span> Response<span class="token punctuation">,</span> next<span class="token punctuation">:</span> NextFunction<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> authHeader <span class="token operator">=</span> req<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>authorization<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>authHeader <span class="token operator">||</span> <span class="token operator">!</span>authHeader<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'Bearer '</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnauthorizedException</span><span class="token punctuation">(</span><span class="token string">'Missing or invalid authorization header'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> token <span class="token operator">=</span> authHeader<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>jwtService<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>
      
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>payload<span class="token punctuation">.</span>tenantId<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnauthorizedException</span><span class="token punctuation">(</span><span class="token string">'Token missing tenant context'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">this</span><span class="token punctuation">.</span>cls<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token string">'TENANT_ID'</span><span class="token punctuation">,</span> payload<span class="token punctuation">.</span>tenantId<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>cls<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token string">'USER_ID'</span><span class="token punctuation">,</span> payload<span class="token punctuation">.</span>sub<span class="token punctuation">)</span><span class="token punctuation">;</span>
      
      <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnauthorizedException</span><span class="token punctuation">(</span><span class="token string">'Invalid token'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
</code></pre><p>This middleware runs on every request except the excluded routes (registration and login). It extracts the JWT from the Authorization header, verifies it and stores the tenantId in the CLS context. Any service can then retrieve this value using <code>this.cls.get('TENANT_ID')</code>.</p><h2 id="typeorm-with-rls-the-challenge">TypeORM with RLS: The Challenge</h2><p>TypeORM manages a pool of database connections that are reused across requests. When you call <code>repository.find()</code>, TypeORM borrows a connection, runs our query, then returns that connection to the pool for the next request to use.</p><p>The issue is there&rsquo;s no way to tell TypeORM &ldquo;before you run this query, first execute <code>SET app.current_tenant_id = ...</code> on this specific connection.&rdquo; By the time our service code runs, TypeORM has already grabbed the connection and is ready to execute.</p><p>The solution is to bypass TypeORM&rsquo;s automatic connection management and use QueryRunner instead. This gives us manual control. We can grab a connection, set the tenant context, run queries and then release it&mdash;all within a single transaction.</p><h3 id="creating-the-tenant-context-interceptor">Creating the Tenant Context Interceptor</h3><p>Let&rsquo;s create an interceptor that wraps every request in a transaction and sets the tenant context.</p><p>Create a file called <code>tenant-context.interceptor.ts</code> in the <code>src/database/</code>:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span>
  Injectable<span class="token punctuation">,</span>
  NestInterceptor<span class="token punctuation">,</span>
  ExecutionContext<span class="token punctuation">,</span>
  CallHandler<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Observable<span class="token punctuation">,</span> <span class="token keyword">from</span><span class="token punctuation">,</span> lastValueFrom <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'rxjs'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DataSource <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ClsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'nestjs-cls'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TenantContextInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">NestInterceptor</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> dataSource<span class="token punctuation">:</span> DataSource<span class="token punctuation">,</span>
    <span class="token keyword">private</span> cls<span class="token punctuation">:</span> ClsService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token function">intercept</span><span class="token punctuation">(</span>context<span class="token punctuation">:</span> ExecutionContext<span class="token punctuation">,</span> next<span class="token punctuation">:</span> CallHandler<span class="token punctuation">)</span><span class="token punctuation">:</span> Observable<span class="token operator">&lt;</span><span class="token keyword">any</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> tenantId <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cls<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'TENANT_ID'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>tenantId<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> next<span class="token punctuation">.</span><span class="token function">handle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token keyword">from</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setupTransaction</span><span class="token punctuation">(</span>tenantId<span class="token punctuation">,</span> next<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">setupTransaction</span><span class="token punctuation">(</span>tenantId<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> next<span class="token punctuation">:</span> CallHandler<span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">any</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> queryRunner <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>dataSource<span class="token punctuation">.</span><span class="token function">createQueryRunner</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> queryRunner<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> queryRunner<span class="token punctuation">.</span><span class="token function">startTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">await</span> queryRunner<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span>
        <span class="token template-string"><span class="token string">`SELECT set_config('app.current_tenant_id', $1, TRUE)`</span></span><span class="token punctuation">,</span>
        <span class="token punctuation">[</span>tenantId<span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">this</span><span class="token punctuation">.</span>cls<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token string">'QUERY_RUNNER'</span><span class="token punctuation">,</span> queryRunner<span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">lastValueFrom</span><span class="token punctuation">(</span>next<span class="token punctuation">.</span><span class="token function">handle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">await</span> queryRunner<span class="token punctuation">.</span><span class="token function">commitTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> result<span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">await</span> queryRunner<span class="token punctuation">.</span><span class="token function">rollbackTransaction</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">throw</span> error<span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
      <span class="token keyword">await</span> queryRunner<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>cls<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token string">'QUERY_RUNNER'</span><span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>This interceptor creates a new transaction for every request, sets the tenant ID using <code>set_config</code>, and stores the QueryRunner in CLS so services can access it.</p><p>The <code>TRUE</code> parameter in <code>set_config</code> is very important; it makes the setting local to this transaction. When the transaction commits or rolls back, the setting is automatically cleared. This prevents the &ldquo;leaky tenant&rdquo; problem, where one request&rsquo;s tenant ID bleeds into another request using the same pooled connection.</p><p>Update the <code>src/database/database.module.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TenantContextInterceptor <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tenant-context.interceptor'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>TenantContextInterceptor<span class="token punctuation">]</span><span class="token punctuation">,</span>
  exports<span class="token punctuation">:</span> <span class="token punctuation">[</span>TenantContextInterceptor<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DatabaseModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h2 id="building-services-with-rls">Building Services with RLS</h2><p>Now, let&rsquo;s set up our services to use the QueryRunner from CLS instead of the standard repository.</p><h3 id="tenants-service">Tenants Service</h3><p>Update the <code>src/tenants/tenants.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable<span class="token punctuation">,</span> ConflictException <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> InjectRepository <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Repository <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Tenant <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./entities/tenant.entity'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TenantsService</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    @<span class="token function">InjectRepository</span><span class="token punctuation">(</span>Tenant<span class="token punctuation">)</span>
    <span class="token keyword">private</span> tenantsRepo<span class="token punctuation">:</span> Repository<span class="token operator">&lt;</span>Tenant<span class="token operator">&gt;</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">create</span><span class="token punctuation">(</span>name<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Tenant<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> existing <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tenantsRepo<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> where<span class="token punctuation">:</span> <span class="token punctuation">{</span> name <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>existing<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ConflictException</span><span class="token punctuation">(</span><span class="token string">'Tenant name already exists'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> tenant <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tenantsRepo<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tenantsRepo<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>tenant<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">findById</span><span class="token punctuation">(</span>id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Tenant <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tenantsRepo<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> where<span class="token punctuation">:</span> <span class="token punctuation">{</span> id <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The tenants table doesn&rsquo;t have RLS enabled, so we can use the standard repository here.</p><h3 id="users-service">Users Service</h3><p>Update the <code>src/users/users.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> InjectRepository <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Repository <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> User <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./entities/user.entity'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> bcrypt <span class="token keyword">from</span> <span class="token string">'bcrypt'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">UsersService</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    @<span class="token function">InjectRepository</span><span class="token punctuation">(</span>User<span class="token punctuation">)</span>
    <span class="token keyword">private</span> usersRepo<span class="token punctuation">:</span> Repository<span class="token operator">&lt;</span>User<span class="token operator">&gt;</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">create</span><span class="token punctuation">(</span>tenantId<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> password<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>User<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> passwordHash <span class="token operator">=</span> <span class="token keyword">await</span> bcrypt<span class="token punctuation">.</span><span class="token function">hash</span><span class="token punctuation">(</span>password<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>usersRepo<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      tenantId<span class="token punctuation">,</span>
      email<span class="token punctuation">,</span>
      passwordHash<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>usersRepo<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">findByEmail</span><span class="token punctuation">(</span>email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>User <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>usersRepo<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> where<span class="token punctuation">:</span> <span class="token punctuation">{</span> email <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Update the <code>src/users/users.module.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TypeOrmModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> UsersService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./users.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> User <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./entities/user.entity'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>TypeOrmModule<span class="token punctuation">.</span><span class="token function">forFeature</span><span class="token punctuation">(</span><span class="token punctuation">[</span>User<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>UsersService<span class="token punctuation">]</span><span class="token punctuation">,</span>
  exports<span class="token punctuation">:</span> <span class="token punctuation">[</span>UsersService<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">UsersModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h3 id="tasks-service">Tasks Service</h3><p>Update the <code>src/tasks/tasks.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable<span class="token punctuation">,</span> NotFoundException <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> InjectRepository <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Repository <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ClsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'nestjs-cls'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Task <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./entities/task.entity'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TasksService</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    @<span class="token function">InjectRepository</span><span class="token punctuation">(</span>Task<span class="token punctuation">)</span>
    <span class="token keyword">private</span> tasksRepo<span class="token punctuation">:</span> Repository<span class="token operator">&lt;</span>Task<span class="token operator">&gt;</span><span class="token punctuation">,</span>
    <span class="token keyword">private</span> cls<span class="token punctuation">:</span> ClsService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">getManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> queryRunner <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cls<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'QUERY_RUNNER'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> queryRunner <span class="token operator">?</span> queryRunner<span class="token punctuation">.</span>manager <span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tasksRepo<span class="token punctuation">.</span>manager<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">create</span><span class="token punctuation">(</span>title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> description<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Task<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> tenantId <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cls<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'TENANT_ID'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> manager <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> task <span class="token operator">=</span> manager<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>Task<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      tenantId<span class="token punctuation">,</span>
      title<span class="token punctuation">,</span>
      description<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> manager<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Task<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> manager <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> manager<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>Task<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">findOne</span><span class="token punctuation">(</span>id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Task<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> manager <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> task <span class="token operator">=</span> <span class="token keyword">await</span> manager<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>Task<span class="token punctuation">,</span> <span class="token punctuation">{</span> where<span class="token punctuation">:</span> <span class="token punctuation">{</span> id <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>task<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NotFoundException</span><span class="token punctuation">(</span><span class="token string">'Task not found'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> task<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">update</span><span class="token punctuation">(</span>id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> title<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> description<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> status<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Task<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> manager <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> task <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>title<span class="token punctuation">)</span> task<span class="token punctuation">.</span>title <span class="token operator">=</span> title<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>description <span class="token operator">!==</span> undefined<span class="token punctuation">)</span> task<span class="token punctuation">.</span>description <span class="token operator">=</span> description<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>status<span class="token punctuation">)</span> task<span class="token punctuation">.</span>status <span class="token operator">=</span> status<span class="token punctuation">;</span>

    <span class="token keyword">return</span> manager<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">remove</span><span class="token punctuation">(</span>id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Task<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> manager <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> task <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">await</span> manager<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> task<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>getManager()</code> helper checks if we have a QueryRunner in CLS. If we do, we use its manager (which has the tenant context set). If not, we fall back to the standard repository manager.</p><p>Note: RLS automatically filters all queries, so <code>findAll()</code> only returns tasks belonging to the current tenant even though we don&rsquo;t explicitly filter by tenant_id. If the standard repository manager is used, however, we&rsquo;ll get null (zero rows) instead of an error because of the <code>true</code> parameter in <code>current_setting</code>.</p><p>Update the <code>src/tasks/tasks.module.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TypeOrmModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TasksController <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tasks.controller'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TasksService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tasks.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Task <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./entities/task.entity'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DatabaseModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../database/database.module'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>TypeOrmModule<span class="token punctuation">.</span><span class="token function">forFeature</span><span class="token punctuation">(</span><span class="token punctuation">[</span>Task<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> DatabaseModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
  controllers<span class="token punctuation">:</span> <span class="token punctuation">[</span>TasksController<span class="token punctuation">]</span><span class="token punctuation">,</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>TasksService<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TasksModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h2 id="tenant-registration">Tenant Registration</h2><p>The tenant registration endpoint creates a new tenant and its first user.</p><p>Update the <code>src/tenants/tenants.controller.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Controller<span class="token punctuation">,</span> Post<span class="token punctuation">,</span> Body <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TenantsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tenants.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> UsersService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../users/users.service'</span><span class="token punctuation">;</span>

@<span class="token function">Controller</span><span class="token punctuation">(</span><span class="token string">'tenants'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TenantsController</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> tenantsService<span class="token punctuation">:</span> TenantsService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> usersService<span class="token punctuation">:</span> UsersService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  @<span class="token function">Post</span><span class="token punctuation">(</span><span class="token string">'register'</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">register</span><span class="token punctuation">(</span>
    @<span class="token function">Body</span><span class="token punctuation">(</span><span class="token punctuation">)</span> body<span class="token punctuation">:</span> <span class="token punctuation">{</span> tenantName<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> password<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> tenant <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tenantsService<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span>tenantName<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>usersService<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>
      tenant<span class="token punctuation">.</span>id<span class="token punctuation">,</span>
      body<span class="token punctuation">.</span>email<span class="token punctuation">,</span>
      body<span class="token punctuation">.</span>password<span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      tenant<span class="token punctuation">:</span> <span class="token punctuation">{</span> id<span class="token punctuation">:</span> tenant<span class="token punctuation">.</span>id<span class="token punctuation">,</span> name<span class="token punctuation">:</span> tenant<span class="token punctuation">.</span>name <span class="token punctuation">}</span><span class="token punctuation">,</span>
      user<span class="token punctuation">:</span> <span class="token punctuation">{</span> id<span class="token punctuation">:</span> user<span class="token punctuation">.</span>id<span class="token punctuation">,</span> email<span class="token punctuation">:</span> user<span class="token punctuation">.</span>email <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Update the <code>TenantsModule</code> to import <code>UsersModule</code>:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TypeOrmModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/typeorm'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TenantsController <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tenants.controller'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> TenantsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tenants.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Tenant <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./entities/tenant.entity'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> UsersModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../users/users.module'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>TypeOrmModule<span class="token punctuation">.</span><span class="token function">forFeature</span><span class="token punctuation">(</span><span class="token punctuation">[</span>Tenant<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> UsersModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
  controllers<span class="token punctuation">:</span> <span class="token punctuation">[</span>TenantsController<span class="token punctuation">]</span><span class="token punctuation">,</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>TenantsService<span class="token punctuation">]</span><span class="token punctuation">,</span>
  exports<span class="token punctuation">:</span> <span class="token punctuation">[</span>TenantsService<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TenantsModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h2 id="authentication-with-jwt">Authentication with JWT</h2><p>Our JWT tokens need to include the tenant ID so the middleware can extract it.</p><h3 id="jwt-strategy">JWT Strategy</h3><p>Create the <code>src/auth/strategies/jwt.strategy.ts</code> file and add the following to it:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> PassportStrategy <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/passport'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ExtractJwt<span class="token punctuation">,</span> Strategy <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'passport-jwt'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">JwtStrategy</span> <span class="token keyword">extends</span> <span class="token class-name">PassportStrategy</span><span class="token punctuation">(</span>Strategy<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>configService<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      jwtFromRequest<span class="token punctuation">:</span> ExtractJwt<span class="token punctuation">.</span><span class="token function">fromAuthHeaderAsBearerToken</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      ignoreExpiration<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
      secretOrKey<span class="token punctuation">:</span> configService<span class="token punctuation">.</span><span class="token function">getOrThrow</span><span class="token punctuation">(</span><span class="token string">'JWT_SECRET'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">validate</span><span class="token punctuation">(</span>payload<span class="token punctuation">:</span> <span class="token keyword">any</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      userId<span class="token punctuation">:</span> payload<span class="token punctuation">.</span>sub<span class="token punctuation">,</span>
      tenantId<span class="token punctuation">:</span> payload<span class="token punctuation">.</span>tenantId<span class="token punctuation">,</span>
      email<span class="token punctuation">:</span> payload<span class="token punctuation">.</span>email<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="jwt-auth-guard">JWT Auth Guard</h3><p>Create a <code>src/auth/guards/jwt-auth.guard.ts</code> file and add the following to it:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AuthGuard <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/passport'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">JwtAuthGuard</span> <span class="token keyword">extends</span> <span class="token class-name">AuthGuard</span><span class="token punctuation">(</span><span class="token string">'jwt'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h3 id="auth-service">Auth Service</h3><p>Update the <code>src/auth/auth.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable<span class="token punctuation">,</span> UnauthorizedException <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> JwtService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/jwt'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> UsersService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../users/users.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> bcrypt <span class="token keyword">from</span> <span class="token string">'bcrypt'</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AuthService</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> usersService<span class="token punctuation">:</span> UsersService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> jwtService<span class="token punctuation">:</span> JwtService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">login</span><span class="token punctuation">(</span>email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> password<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>usersService<span class="token punctuation">.</span><span class="token function">findByEmail</span><span class="token punctuation">(</span>email<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnauthorizedException</span><span class="token punctuation">(</span><span class="token string">'Invalid credentials'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> isPasswordValid <span class="token operator">=</span> <span class="token keyword">await</span> bcrypt<span class="token punctuation">.</span><span class="token function">compare</span><span class="token punctuation">(</span>password<span class="token punctuation">,</span> user<span class="token punctuation">.</span>passwordHash<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isPasswordValid<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnauthorizedException</span><span class="token punctuation">(</span><span class="token string">'Invalid credentials'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token punctuation">{</span>
      sub<span class="token punctuation">:</span> user<span class="token punctuation">.</span>id<span class="token punctuation">,</span>
      tenantId<span class="token punctuation">:</span> user<span class="token punctuation">.</span>tenantId<span class="token punctuation">,</span>
      email<span class="token punctuation">:</span> user<span class="token punctuation">.</span>email<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      access_token<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>jwtService<span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">,</span>
      user<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        id<span class="token punctuation">:</span> user<span class="token punctuation">.</span>id<span class="token punctuation">,</span>
        email<span class="token punctuation">:</span> user<span class="token punctuation">.</span>email<span class="token punctuation">,</span>
        tenantId<span class="token punctuation">:</span> user<span class="token punctuation">.</span>tenantId<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="auth-controller">Auth Controller</h3><p>Update the <code>src/auth/auth.controller.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Controller<span class="token punctuation">,</span> Post<span class="token punctuation">,</span> Body <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AuthService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./auth.service'</span><span class="token punctuation">;</span>

@<span class="token function">Controller</span><span class="token punctuation">(</span><span class="token string">'auth'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AuthController</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> authService<span class="token punctuation">:</span> AuthService<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  @<span class="token function">Post</span><span class="token punctuation">(</span><span class="token string">'login'</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">login</span><span class="token punctuation">(</span>@<span class="token function">Body</span><span class="token punctuation">(</span><span class="token punctuation">)</span> body<span class="token punctuation">:</span> <span class="token punctuation">{</span> email<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> password<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>authService<span class="token punctuation">.</span><span class="token function">login</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span>email<span class="token punctuation">,</span> body<span class="token punctuation">.</span>password<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="auth-module">Auth Module</h3><p>Update the <code>src/auth/auth.module.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> JwtModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/jwt'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> PassportModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/passport'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigModule<span class="token punctuation">,</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AuthController <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./auth.controller'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AuthService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./auth.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> JwtStrategy <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./strategies/jwt.strategy'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> UsersModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../users/users.module'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    UsersModule<span class="token punctuation">,</span>
    PassportModule<span class="token punctuation">,</span>
    JwtModule<span class="token punctuation">.</span><span class="token function">registerAsync</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>ConfigModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
      inject<span class="token punctuation">:</span> <span class="token punctuation">[</span>ConfigService<span class="token punctuation">]</span><span class="token punctuation">,</span>
      useFactory<span class="token punctuation">:</span> <span class="token punctuation">(</span>config<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
        secret<span class="token punctuation">:</span> config<span class="token punctuation">.</span><span class="token function">getOrThrow</span><span class="token punctuation">(</span><span class="token string">'JWT_SECRET'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        signOptions<span class="token punctuation">:</span> <span class="token punctuation">{</span> expiresIn<span class="token punctuation">:</span> config<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">'JWT_EXPIRATION'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
  controllers<span class="token punctuation">:</span> <span class="token punctuation">[</span>AuthController<span class="token punctuation">]</span><span class="token punctuation">,</span>
  providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>AuthService<span class="token punctuation">,</span> JwtStrategy<span class="token punctuation">]</span><span class="token punctuation">,</span>
  exports<span class="token punctuation">:</span> <span class="token punctuation">[</span>JwtModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AuthModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h2 id="building-the-tasks-api">Building the Tasks API</h2><p>Now we can build the actual task management endpoints.</p><p>Update the <code>src/tasks/tasks.controller.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span>
    Controller<span class="token punctuation">,</span>
    Get<span class="token punctuation">,</span>
    Post<span class="token punctuation">,</span>
    Patch<span class="token punctuation">,</span>
    Delete<span class="token punctuation">,</span>
    Body<span class="token punctuation">,</span>
    Param<span class="token punctuation">,</span>
    UseGuards<span class="token punctuation">,</span>
    UseInterceptors<span class="token punctuation">,</span>
  <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
  <span class="token keyword">import</span> <span class="token punctuation">{</span> TasksService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./tasks.service'</span><span class="token punctuation">;</span>
  <span class="token keyword">import</span> <span class="token punctuation">{</span> JwtAuthGuard <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../auth/guards/jwt-auth.guard'</span><span class="token punctuation">;</span>
  <span class="token keyword">import</span> <span class="token punctuation">{</span> TenantContextInterceptor <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../database/tenant-context.interceptor'</span><span class="token punctuation">;</span>
  
  @<span class="token function">Controller</span><span class="token punctuation">(</span><span class="token string">'tasks'</span><span class="token punctuation">)</span>
  @<span class="token function">UseGuards</span><span class="token punctuation">(</span>JwtAuthGuard<span class="token punctuation">)</span>
  @<span class="token function">UseInterceptors</span><span class="token punctuation">(</span>TenantContextInterceptor<span class="token punctuation">)</span>
  <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">TasksController</span> <span class="token punctuation">{</span>
    <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> tasksService<span class="token punctuation">:</span> TasksService<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  
    @<span class="token function">Get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">async</span> <span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tasksService<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  
    @<span class="token function">Get</span><span class="token punctuation">(</span><span class="token string">':id'</span><span class="token punctuation">)</span>
    <span class="token keyword">async</span> <span class="token function">findOne</span><span class="token punctuation">(</span>@<span class="token function">Param</span><span class="token punctuation">(</span><span class="token string">'id'</span><span class="token punctuation">)</span> id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tasksService<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  
    @<span class="token function">Post</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">async</span> <span class="token function">create</span><span class="token punctuation">(</span>@<span class="token function">Body</span><span class="token punctuation">(</span><span class="token punctuation">)</span> body<span class="token punctuation">:</span> <span class="token punctuation">{</span> title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> description<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tasksService<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span>title<span class="token punctuation">,</span> body<span class="token punctuation">.</span>description<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  
    @<span class="token function">Patch</span><span class="token punctuation">(</span><span class="token string">':id'</span><span class="token punctuation">)</span>
    <span class="token keyword">async</span> <span class="token function">update</span><span class="token punctuation">(</span>
      @<span class="token function">Param</span><span class="token punctuation">(</span><span class="token string">'id'</span><span class="token punctuation">)</span> id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
      @<span class="token function">Body</span><span class="token punctuation">(</span><span class="token punctuation">)</span> body<span class="token punctuation">:</span> <span class="token punctuation">{</span> title<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> description<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span> status<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tasksService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> body<span class="token punctuation">.</span>title<span class="token punctuation">,</span> body<span class="token punctuation">.</span>description<span class="token punctuation">,</span> body<span class="token punctuation">.</span>status<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    @<span class="token function">Delete</span><span class="token punctuation">(</span><span class="token string">':id'</span><span class="token punctuation">)</span> <span class="token keyword">async</span> <span class="token function">remove</span><span class="token punctuation">(</span>@<span class="token function">Param</span><span class="token punctuation">(</span><span class="token string">'id'</span><span class="token punctuation">)</span> id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tasksService<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>@UseGuards(JwtAuthGuard)</code> ensures all requests are authenticated. The <code>@UseInterceptors(TenantContextInterceptor)</code> wraps each request in a transaction with the tenant context set.</p><h2 id="testing-tenant-isolation">Testing Tenant Isolation</h2><p>Start the server:</p><pre class=" language-shell"><code class="prism  language-shell">npm run start:dev
</code></pre><h3 id="register-two-tenants">Register Two Tenants</h3><p>Register the first tenant (Company A):</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/tenants/register \
  -H "Content-Type: application/json" \
  -d "{\"tenantName\": \"Company A\", \"email\": \"admin@companyA.com\", \"password\": \"password123\"}"
</code></pre><p>You should get a response like this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/company-a-registration-response.png?sfvrsn=7d1f476c_2" title="Company A registration response" alt="Company A registration response" /></p><p>Register the second tenant (Company B):</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/tenants/register \
  -H "Content-Type: application/json" \
  -d "{\"tenantName\": \"Company B\", \"email\": \"admin@companyB.com\", \"password\": \"password123\"}"
</code></pre><h3 id="login-and-get-tokens">Login and Get Tokens</h3><p>Log in as Company A:</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"admin@companyA.com\", \"password\": \"password123\"}"
</code></pre><p>You should get a response like this with an access token:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/company-a-logs-in-with-jwt-token.png?sfvrsn=221520cd_2" title="Company A logs in with JWT token" alt="Company A logs in with JWT token" /></p><p>Copy the token; we&rsquo;ll call it TOKEN_A. Do the same for Company B, and we&rsquo;ll call its token TOKEN_B.</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"admin@companyB.com\", \"password\": \"password123\"}"
</code></pre><h2 id="create-tasks">Create Tasks</h2><p>Create a task for Company A:</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/tasks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer TOKEN_A" \
  -d "{\"title\": \"Company A Task\", \"description\": \"This belongs to Company A\"}"
</code></pre><p>Create a task for Company B:</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/tasks \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer TOKEN_B" \
  -d "{\"title\": \"Company B Task\", \"description\": \"This belongs to Company B\"}"
</code></pre><h2 id="verify-isolation">Verify Isolation</h2><p>List tasks as Company A:</p><pre class=" language-shell"><code class="prism  language-shell">curl http://localhost:3000/tasks \
  -H "Authorization: Bearer TOKEN_A"
</code></pre><p>You should only see Company A&rsquo;s task as seen below:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/company-a-task-list.png?sfvrsn=fbab1757_2" title="Company A’s task list" alt="Company A’s task list" /></p><p>List tasks as Company B:</p><pre class=" language-shell"><code class="prism  language-shell">curl http://localhost:3000/tasks \
  -H "Authorization: Bearer TOKEN_B"
</code></pre><p>You should only see Company B&rsquo;s task. This confirms that RLS is working, as each tenant only sees their own data.</p><h3 id="test-direct-id-access">Test Direct ID Access</h3><p>Now try to access Company A&rsquo;s task using Company B&rsquo;s token. First, get Company A&rsquo;s task ID from the previous response, then:</p><pre class=" language-shell"><code class="prism  language-shell">curl http://localhost:3000/tasks/ed396863-255f-49b4-a4c8-906f71465c9e \
  -H "Authorization: Bearer TOKEN_B"
</code></pre><p>You should get a 404 Not Found error like this:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/company-b-blocked.png?sfvrsn=8655cd8e_2" title="Company B blocked from accessing Company A&#39;s task" alt="Company B blocked from accessing Company A&#39;s task" /></p><p>Even though the task exists in the database, RLS prevents Company B from seeing it. This is the power of database-level isolation. Even if our application code has a bug, the database ensures tenants can&rsquo;t access each other&rsquo;s data.</p><h3 id="verify-company-a-can-access-its-own-task">Verify Company A Can Access Its Own Task</h3><p>Confirm the task exists and Company A can access it:</p><pre class=" language-shell"><code class="prism  language-shell">curl http://localhost:3000/tasks/ed396863-255f-49b4-a4c8-906f71465c9e \
  -H "Authorization: Bearer TOKEN_A"
</code></pre><p>You should get the full task details as shown below:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/company-a-can-access-their-task.png?sfvrsn=16fe441f_2" title="Company A can access their task successfully" alt="Company A can access their task successfully" /></p><h2 id="conclusion">Conclusion</h2><p>Now you can build a multi-tenant SaaS API using NestJS and Postgres Row-Level Security. We&rsquo;ve covered how to set up RLS policies at the database level, extract tenant context from JWT tokens, use TypeORM with transaction-scoped session variables, and verify that data isolation works even against direct ID access attempts.</p><p>This approach provides strong security guarantees without complex application logic. The database enforces isolation automatically, reducing the risk of accidentally exposing data across tenants.</p><p>Possible next steps include adding tenant-specific rate limiting, implementing admin endpoints for cross-tenant reporting (using <code>SECURITY DEFINER</code> functions to bypass RLS safely), or exploring performance optimizations for high-scale scenarios with connection pooling strategies.</p><hr /><p>&nbsp;</p><p>Read more:&nbsp;<a href="https://www.telerik.com/blogs/how-build-semantic-search-documentation-nestjs-qdrant-xenova" target="_blank">How to Build Semantic Search for Documentation with NestJS, Qdrant and Xenova</a></p><img src="https://feeds.telerik.com/link/10827/17341162.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:1feea2d1-2fa0-48fa-a5fa-f1d611bdf2d5</id>
    <title type="text">Streaming Server Events with SSE and Blazor</title>
    <summary type="text">Understand the SSE standard, the .NET 10  changes to simplify SSE endpoints and how to add real-time events to your Blazor clients.</summary>
    <published>2026-05-12T13:18:03Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17339193/streaming-server-events-sse-blazor"/>
    <content type="text"><![CDATA[<p><span class="featured">Understand the SSE standard, the .NET 10 changes to simplify SSE endpoints and how to add real-time events to your Blazor clients.</span></p><p>In the world of web development, there are scenarios where you need to receive real-time information, such as notifications about an event, server metrics or live logs. One possible solution to achieve this is the use of Server-Sent Events (SSE). With the arrival of .NET 10, implementing this type of solution has become much easier, so let&rsquo;s see how to integrate this web standard into your projects.</p><h2 id="what-are-server-sent-events">What Are Server-Sent Events?</h2><p>The <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events">Server-Sent Events (SSE)</a> are not a new technology. Their origins date back to around 2006, and they are a web standard that allows data to be sent to clients continuously using a persistent HTTP connection.</p><p>If you wonder the difference compared to other similar technologies, we can make a comparison:</p><ul><li><p><strong>SSE vs. Polling</strong>: In the case of polling, the direction goes from the client to the server, using the HTTP protocol. We can see it as a client asking the server if there are updates at certain intervals. It involves low implementation complexity.</p></li><li><p><strong>SSE vs. WebSockets</strong>: With WebSockets there is bidirectional communication. It involves high complexity and is ideal for scenarios like video games, chats, etc. It works over the WS protocol.</p></li><li><p><strong>SSE vs. SignalR</strong>: SignalR also establishes bidirectional communication, with medium implementation complexity. It allows the use of binary protocols, which makes it a very good option for scalable enterprise apps. It uses variable protocols.</p></li></ul><p>Analyzing the above, we can conclude that SSE is ideal when the data flow is unidirectional, you need it to work over the HTTP protocol and it must be easy to implement.</p><h2 id="how-does-the-sse-protocol-work">How Does the SSE Protocol Work?</h2><p>The workings behind the scenes of the SSE protocol, in broad terms, are as follows: a server SSE endpoint needs to send an HTTP response of the type <code>Content-Type: text/event-stream</code>. The response looks similar to the following:</p><pre class=" language-json"><code class="prism  language-json">event<span class="token punctuation">:</span> app<span class="token operator">-</span>event
data<span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token string">"id"</span><span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token string">"time"</span><span class="token punctuation">:</span><span class="token string">"10:30:45"</span><span class="token punctuation">,</span><span class="token string">"level"</span><span class="token punctuation">:</span><span class="token string">"Info"</span><span class="token punctuation">,</span><span class="token string">"source"</span><span class="token punctuation">:</span><span class="token string">"OrderService"</span><span class="token punctuation">,</span><span class="token string">"message"</span><span class="token punctuation">:</span><span class="token string">"Order #1234 placed successfully"</span><span class="token punctuation">}</span>
</code></pre><p>In the code above, there are some fields we should pay attention to:</p><ul><li><code>event</code>: Specifies the name of the event</li><li><code>data</code>: Is the content of the message</li><li><code>id</code>: Identifier used to perform a reconnection if needed</li></ul><h2 id="whats-new-in-.net-10-for-sse">What&rsquo;s New in .NET 10 for SSE</h2><p>The most important update in .NET 10 for working with SSE is that the ability to return a <code>ServerSentEvents</code> using the API <code>TypedResults.ServerSentEvents</code> has been implemented. This means that the method <code>TypedResults.ServerSentEvents&lt;T&gt;()</code> allows converting any <code>IAsyncEnumerable&lt;T&gt;</code> into a formatted SSE stream without needing to do anything else. Tasks like JSON serialization, HTTP headers, connection closing, etc. are handled automatically.</p><p>To better understand how it works, let&rsquo;s create a project using the SSE standard with ASP.NET 10.</p><h2 id="building-an-sse-project-using-asp.net-10">Building an SSE Project Using ASP.NET 10</h2><p>To practice the theoretical concepts covered so far, we&rsquo;ll create a page that shows a simulation of receiving events from backend services in real time. Using a Progress Telerik UI for <a target="_blank" href="https://www.telerik.com/blazor-ui/grid">Blazor Grid</a>, we&rsquo;ll perform tasks like filtering, grouping and analysis quickly.</p><h3 id="creating-and-configuring-the-project">Creating and Configuring the Project</h3><p>The first thing we&rsquo;ll do is create a project using the <strong>Blazor Web App</strong> template, selecting an <strong>Interactive render mode</strong> of <strong>Server</strong> and <strong>Interactivity location</strong> of <strong>Global</strong>. Next, we&rsquo;ll follow the official installation guide for <a target="_blank" href="https://www.telerik.com/blazor-ui">Telerik UI for Blazor</a> to configure the project and be able to use the Telerik components.</p><h3 id="creating-an-eventbroadcaster">Creating an EventBroadcaster</h3><p>For our project, we&rsquo;ll create an event broadcaster, which in simple terms will be a bus that SSE clients can connect to to consume events, while backend services will use it to publish events. First, we&rsquo;ll create a record that represents a system event:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> SSEDemo<span class="token punctuation">.</span>Services
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> record <span class="token function">AppEvent</span><span class="token punctuation">(</span><span class="token keyword">int</span> Id<span class="token punctuation">,</span> <span class="token keyword">string</span> Time<span class="token punctuation">,</span> <span class="token keyword">string</span> Level<span class="token punctuation">,</span> <span class="token keyword">string</span> Source<span class="token punctuation">,</span> <span class="token keyword">string</span> Message<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Next, we will create the class <code>EventBroadcaster</code> which will have the following definition:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventBroadcaster</span>
<span class="token punctuation">{</span>
    <span class="token comment">//1.</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> Channel<span class="token operator">&lt;</span>AppEvent<span class="token operator">&gt;</span> _channel <span class="token operator">=</span> Channel<span class="token punctuation">.</span><span class="token generic-method function">CreateBounded<span class="token punctuation">&lt;</span>AppEvent<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>
    <span class="token keyword">new</span> <span class="token class-name">BoundedChannelOptions</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> FullMode <span class="token operator">=</span> BoundedChannelFullMode<span class="token punctuation">.</span>DropOldest <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">int</span> _nextId<span class="token punctuation">;</span>

    <span class="token comment">// 2.</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Publish</span><span class="token punctuation">(</span><span class="token keyword">string</span> level<span class="token punctuation">,</span> <span class="token keyword">string</span> source<span class="token punctuation">,</span> <span class="token keyword">string</span> message<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> id <span class="token operator">=</span> Interlocked<span class="token punctuation">.</span><span class="token function">Increment</span><span class="token punctuation">(</span><span class="token keyword">ref</span> _nextId<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> evt <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AppEvent</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"HH:mm:ss"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> level<span class="token punctuation">,</span> source<span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span>
        _channel<span class="token punctuation">.</span>Writer<span class="token punctuation">.</span><span class="token function">TryWrite</span><span class="token punctuation">(</span>evt<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// 3.</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> IAsyncEnumerable<span class="token operator">&lt;</span>AppEvent<span class="token operator">&gt;</span> <span class="token function">Subscribe</span><span class="token punctuation">(</span>
        <span class="token punctuation">[</span>System<span class="token punctuation">.</span>Runtime<span class="token punctuation">.</span>CompilerServices<span class="token punctuation">.</span>EnumeratorCancellation<span class="token punctuation">]</span> CancellationToken ct<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>ct<span class="token punctuation">.</span>IsCancellationRequested<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">bool</span> hasData<span class="token punctuation">;</span>
            <span class="token keyword">try</span>
            <span class="token punctuation">{</span>
                hasData <span class="token operator">=</span> <span class="token keyword">await</span> _channel<span class="token punctuation">.</span>Reader<span class="token punctuation">.</span><span class="token function">WaitToReadAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">OperationCanceledException</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">yield</span> <span class="token keyword">break</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>

            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>hasData<span class="token punctuation">)</span> <span class="token keyword">yield</span> <span class="token keyword">break</span><span class="token punctuation">;</span>

            <span class="token keyword">while</span> <span class="token punctuation">(</span>_channel<span class="token punctuation">.</span>Reader<span class="token punctuation">.</span><span class="token function">TryRead</span><span class="token punctuation">(</span><span class="token keyword">out</span> <span class="token keyword">var</span> evt<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token punctuation">{</span>
                <span class="token keyword">yield</span> <span class="token keyword">return</span> evt<span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above, we have the following sections:</p><ol><li><p>Sets up the channel where events will be passed, with a queue limit of 100 messages. <code>FullMode = BoundedChannelFullMode.DropOldest</code> allows that if the queue fills up and a new message arrives, the oldest one is removed to make room for the new message.</p></li><li><p>The method <code>Publish</code> is the one that will be used to emit events. <code>Interlocked.Increment</code> allows generating sequential IDs in a safe manner, while <code>AppEvent</code> packages the received data together with the obtained id. Finally the method <code>TryWrite()</code> attempts to write the event to the channel asynchronously.</p></li><li><p>On the other hand, the method <code>Subscribe</code> returns a continuous stream of asynchronous data through the use of <code>IAsyncEnumerable&lt;AppEvent&gt;</code>. Inside its implementation an infinite loop is created that waits for data to be available in the channel. Once an event enters the channel, it is emitted to the consumer. This will happen until the <code>CancellationToken</code> called <code>ct</code> is canceled.</p></li></ol><h3 id="generating-test-events">Generating Test Events</h3><p>To test the bus defined above, we&rsquo;ll create a service that simulates the activity of multiple services:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DemoEventGenerator</span><span class="token punctuation">(</span>EventBroadcaster broadcaster<span class="token punctuation">)</span> <span class="token punctuation">:</span> BackgroundService
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Levels <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"Info"</span><span class="token punctuation">,</span> <span class="token string">"Warning"</span><span class="token punctuation">,</span> <span class="token string">"Error"</span><span class="token punctuation">,</span> <span class="token string">"Success"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Sources <span class="token operator">=</span>
        <span class="token punctuation">[</span><span class="token string">"OrderService"</span><span class="token punctuation">,</span> <span class="token string">"PaymentService"</span><span class="token punctuation">,</span> <span class="token string">"InventoryService"</span><span class="token punctuation">,</span> <span class="token string">"AuthService"</span><span class="token punctuation">,</span> <span class="token string">"ShippingService"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> Messages <span class="token operator">=</span>
    <span class="token punctuation">[</span>
        <span class="token punctuation">[</span><span class="token string">"Order #{0} placed successfully"</span><span class="token punctuation">,</span> <span class="token string">"New customer registered"</span><span class="token punctuation">,</span> <span class="token string">"Product viewed: SKU-{0}"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token punctuation">[</span><span class="token string">"High latency detected: {0}ms"</span><span class="token punctuation">,</span> <span class="token string">"Retry attempt #{0}"</span><span class="token punctuation">,</span> <span class="token string">"Queue depth above threshold"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token punctuation">[</span><span class="token string">"Payment failed for order #{0}"</span><span class="token punctuation">,</span> <span class="token string">"Database timeout after {0}ms"</span><span class="token punctuation">,</span> <span class="token string">"Service unreachable"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token punctuation">[</span><span class="token string">"Deployment completed v2.{0}"</span><span class="token punctuation">,</span> <span class="token string">"Health check passed"</span><span class="token punctuation">,</span> <span class="token string">"Cache refreshed ({0} items)"</span><span class="token punctuation">]</span>
    <span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">async</span> Task <span class="token function">ExecuteAsync</span><span class="token punctuation">(</span>CancellationToken stoppingToken<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> rng <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>stoppingToken<span class="token punctuation">.</span>IsCancellationRequested<span class="token punctuation">)</span>
        <span class="token punctuation">{</span>            
            <span class="token keyword">await</span> Task<span class="token punctuation">.</span><span class="token function">Delay</span><span class="token punctuation">(</span>rng<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1500</span><span class="token punctuation">,</span> <span class="token number">4000</span><span class="token punctuation">)</span><span class="token punctuation">,</span> stoppingToken<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">var</span> levelIdx <span class="token operator">=</span> rng<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Levels<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> level <span class="token operator">=</span> Levels<span class="token punctuation">[</span>levelIdx<span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> source <span class="token operator">=</span> Sources<span class="token punctuation">[</span>rng<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>Sources<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> templates <span class="token operator">=</span> Messages<span class="token punctuation">[</span>levelIdx<span class="token punctuation">]</span><span class="token punctuation">;</span>
            <span class="token keyword">var</span> message <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span>templates<span class="token punctuation">[</span>rng<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span>templates<span class="token punctuation">.</span>Length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">,</span> rng<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">,</span> <span class="token number">9999</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            broadcaster<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span>level<span class="token punctuation">,</span> source<span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The previous class is a random event generator, taking a random value from the arrays <code>Levels</code>, <code>Sources</code> and <code>Messages</code>. In addition, through the parameter <code>broadcaster</code>, the new event is published to the bus.</p><h3 id="creating-the-sse-endpoint">Creating the SSE Endpoint</h3><p>Now it&rsquo;s time to create the SSE endpoint, which will consume the shared <code>EventBroadcaster</code>, with the purpose of creating the Event Feed so clients can connect to receive events:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SseEndpoints</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">MapSseEndpoints</span><span class="token punctuation">(</span><span class="token keyword">this</span> WebApplication app<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>        
        app<span class="token punctuation">.</span><span class="token function">MapGet</span><span class="token punctuation">(</span><span class="token string">"/sse/events"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>EventBroadcaster broadcaster<span class="token punctuation">,</span> CancellationToken ct<span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span>
            TypedResults<span class="token punctuation">.</span><span class="token function">ServerSentEvents</span><span class="token punctuation">(</span>broadcaster<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">,</span> eventType<span class="token punctuation">:</span> <span class="token string">"app-event"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above you can notice how <code>EventBroadcaster</code> is injected as a parameter of <code>MapGet</code>. Likewise, <code>TypedResults.ServerSentEvent</code> is executed, which serializes the information and sends the correct SSE format to clients.</p><h3 id="registering-services-in-program.cs">Registering Services in Program.cs</h3><p>For everything to work as expected, we must register in <code>Program.cs</code> the different instances that will interact in the Blazor application. This means creating a singleton instance of <code>EventBroadcaster</code>, so that all systems have access to the same bus. Similarly, we will register <code>DemoEventGenerator</code> as a background service, through the method <code>AddHostedService</code>. This will allow simulated events to be generated in the background continuously:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">var</span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddSingleton<span class="token punctuation">&lt;</span>EventBroadcaster<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddHostedService<span class="token punctuation">&lt;</span>DemoEventGenerator<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">var</span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>

app<span class="token punctuation">.</span><span class="token function">MapSseEndpoints</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

app<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>In the previous code, in addition to registering the services, <code>MapSseEndpoints</code> is invoked to enable SSE communication in the project.</p><h3 id="creating-the-blazor-application">Creating the Blazor Application</h3><p>Once we have the application infrastructure ready, it&rsquo;s time to move on to the UI part. At this point, let&rsquo;s start by creating a JavaScript client, because the standard requires using the browser&rsquo;s <code>EventSource</code> API. Since the project is configured as <code>InteractiveServer</code>, we cannot add inline <code>script</code> tags. To work around this, I will add a new file inside the <code>wwwroot/js</code> folder called <code>sse-demos.js</code>:</p><pre class=" language-javascript"><code class="prism  language-javascript"><span class="token comment">//1.</span>
<span class="token keyword">let</span> eventFeedSource <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>

<span class="token number">2</span><span class="token punctuation">.</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">start</span><span class="token punctuation">(</span>dotNetRef<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 3.</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>eventFeedSource<span class="token punctuation">)</span> eventFeedSource<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">//4.</span>
    <span class="token keyword">const</span> src <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventSource</span><span class="token punctuation">(</span><span class="token string">'/sse/events'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 5.</span>
    src<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'app-event'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        dotNetRef<span class="token punctuation">.</span><span class="token function">invokeMethodAsync</span><span class="token punctuation">(</span><span class="token string">'OnEventReceived'</span><span class="token punctuation">,</span> JSON<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 6.</span>
    src<span class="token punctuation">.</span><span class="token function-variable function">onopen</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> dotNetRef<span class="token punctuation">.</span><span class="token function">invokeMethodAsync</span><span class="token punctuation">(</span><span class="token string">'OnConnectionChanged'</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    src<span class="token punctuation">.</span><span class="token function-variable function">onerror</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> dotNetRef<span class="token punctuation">.</span><span class="token function">invokeMethodAsync</span><span class="token punctuation">(</span><span class="token string">'OnConnectionChanged'</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>

    eventFeedSource <span class="token operator">=</span> src<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">//7.</span>
<span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>eventFeedSource<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        eventFeedSource<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        eventFeedSource <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The previous code does the following:</p><ol><li>A variable <code>eventFeedSource</code> is created to keep information about whether there is an active connection with the server.</li><li>A function named <code>start</code> should be called from C# code when you want to start receiving information&hellip;</li><li>It checks whether there is an open connection, in which case it is closed.</li><li>It tells the browser to connect to <code>/sse/events</code> and to listen for all events that the server sends.</li><li>It filters events named <code>app-event</code>.</li><li>We subscribe to the events <code>onopen</code> and <code>onerror</code>. Each will notify the method <code>OnConnectionChanged</code> about a change in the connection, which will allow showing a different state in the Blazor UI.</li><li>The function <code>stop</code> should be invoked to clean up memory when we want to close the connection.</li></ol><p>With the JS functions ready, the next step is to create the Blazor component. In this new component we will use a <a target="_blank" href="https://www.telerik.com/blazor-ui/grid">Blazor Data Grid</a> type component, because it is a highly configurable component that has built-in options to filter, group, etc., ideal for quickly obtaining information about events in the different systems.</p><p>To do the above, we will create a component called <code>EventFeed.razor</code>, which looks as follows:</p><pre class=" language-xml"><code class="prism  language-xml">@page "/events"
@rendermode InteractiveServer
@inject IJSRuntime JS
@implements IAsyncDisposable

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PageTitle</span><span class="token punctuation">&gt;</span></span>Live Event Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>PageTitle</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mb-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Live Event Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card shadow-sm<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-header d-flex align-items-center justify-content-between py-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>d-flex align-items-center gap-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            @if (isStreaming)
            {
                &lt;span class="badge rounded-pill @(isConnected ? "bg-success" : "bg-secondary") fs-6"&gt;
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>me-1<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>⬤<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>@(isConnected ? "Connected" : "Disconnected")
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
            }
            else
            {
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>badge rounded-pill bg-warning text-dark fs-6<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>me-1<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>⏸<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>Paused
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
            }
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-muted<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                Events received: <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-dark<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@totalEventsReceived<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>d-flex gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikButton</span> <span class="token attr-name">OnClick</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ToggleStreaming<span class="token punctuation">"</span></span>
                           <span class="token attr-name">ThemeColor</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@(isStreaming ? ThemeConstants.Button.ThemeColor.Primary : ThemeConstants.Button.ThemeColor.Success)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                @(isStreaming ? "⏸ Pause" : "▶ Resume")
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikButton</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikButton</span> <span class="token attr-name">OnClick</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ClearGrid<span class="token punctuation">"</span></span> <span class="token attr-name">ThemeColor</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@ThemeConstants.Button.ThemeColor.Light<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span> Clear<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikButton</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-body p-0<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TelerikGrid</span> <span class="token attr-name">Data</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@events<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>560px<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Sortable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Resizable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Reorderable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>
                     <span class="token attr-name">Groupable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span>                     
                     <span class="token attr-name">ShowColumnMenu</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumns</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(AppEvent.Time)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Time<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(AppEvent.Level)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Level<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>120px<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Template</span><span class="token punctuation">&gt;</span></span>
                        @{
                            var item = (AppEvent)context;
                        }
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>badge rounded-pill @GetBadgeClass(item.Level) px-3 py-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>@item.Level<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Template</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>GridColumn</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(AppEvent.Source)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Source<span class="token punctuation">"</span></span> <span class="token attr-name">Width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>140px<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>GridColumn</span> <span class="token attr-name">Field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>@nameof(AppEvent.Message)<span class="token punctuation">"</span></span> <span class="token attr-name">Title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Message<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>GridColumns</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TelerikGrid</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

@code {
    private List<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>AppEvent</span><span class="token punctuation">&gt;</span></span> events = new();
    private bool isConnected;
    private bool isStreaming = true;
    private int totalEventsReceived;
    private DotNetObjectReference<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>EventFeed</span><span class="token punctuation">&gt;</span></span>? dotNetRef;
    private IJSObjectReference? jsModule;
    private bool _jsInitialized;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            dotNetRef = DotNetObjectReference.Create(this);
                       
            jsModule = await JS.InvokeAsync<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>IJSObjectReference</span><span class="token punctuation">&gt;</span></span>("import", "./js/sse-demos.js");
                        
            await jsModule.InvokeVoidAsync("start", dotNetRef);
            
            _jsInitialized = true;
        }
    }

    [JSInvokable]
    public void OnEventReceived(AppEvent appEvent)
    {
        totalEventsReceived++;
        var updated = new List<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>AppEvent</span><span class="token punctuation">&gt;</span></span>(events);
        updated.Insert(0, appEvent);
        if (updated.Count &gt; 50)
            updated.RemoveRange(50, updated.Count - 50);
        events = updated;
        InvokeAsync(StateHasChanged);
    }

    [JSInvokable]
    public void OnConnectionChanged(bool connected)
    {
        isConnected = connected;
        InvokeAsync(StateHasChanged);
    }

    private async Task ToggleStreaming()
    {
        isStreaming = !isStreaming;
        
        if (jsModule is not null)
        {
            if (isStreaming)
                await jsModule.InvokeVoidAsync("start", dotNetRef);
            else
                await jsModule.InvokeVoidAsync("stop");
        }
    }

    private void ClearGrid()
    {
        events = new List<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>AppEvent</span><span class="token punctuation">&gt;</span></span>();
        StateHasChanged();
    }

    private string GetBadgeClass(string level) =&gt; level switch
    {
        "Info" =&gt; "bg-info text-dark",
        "Warning" =&gt; "bg-warning text-dark",
        "Error" =&gt; "bg-danger",
        "Success" =&gt; "bg-success",
        _ =&gt; "bg-secondary"
    };

    public async ValueTask DisposeAsync()
    {
        if (_jsInitialized &amp;&amp; jsModule is not null)
        {
            try
            {                
                await jsModule.InvokeVoidAsync("stop");
                                
                await jsModule.DisposeAsync();
            }
            catch
            {                
            }
        }
        dotNetRef?.Dispose();
    }

    public class AppEvent
    {
        public int Id { get; set; }
        public string Time { get; set; } = string.Empty;
        public string Level { get; set; } = string.Empty;
        public string Source { get; set; } = string.Empty;
        public string Message { get; set; } = string.Empty;
    }
}
</code></pre><p>In the previous code, there are some points to highlight:</p><ul><li><code>jsModule</code> dynamically loads the JS module.</li><li>The variable <code>dotNetRef</code> wraps the instance of the Blazor component that we will use inside the JS code.</li><li>The method <code>InvokeVoidAsync</code> is used both to start and to stop the event streaming.</li><li>The method <code>OnEventReceived</code> is invoked from the JS code each time an event is received. This allows updating the list <code>events</code> to show the new information in <code>TelerikGrid</code>.</li><li>The <code>OnConnectionChanged</code> method receives a connection status from the JS code to update the UI according to any change in the connection.</li></ul><p>With the new component ready, we can test the application, which looks like the following:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/telerik-datagrid-sse-event-feed.gif?sfvrsn=58cc913a_2" alt="Telerik DataGrid receiving live SSE event feed" /></p><p>With this, we verify that everything works correctly. Also, thanks to the Blazor DataGrid capabilities, we can perform operations such as monitoring only those high-severity events:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/timeline-grouped-events-view.png?sfvrsn=5ae1f10_2" alt="Timeline view of events grouped by day" /></p><p>With this we have a nice event viewer that monitors the status of multiple fictitious systems.</p><h2 id="conclusion">Conclusion</h2><p>Throughout this article you have learned about the SSE standard. You have also seen how changes introduced in .NET 10 help simplify the creation of SSE endpoints, enabling the creation of applications that send real-time events to clients. Now it&rsquo;s your turn to explore when you might use this web standard in your own projects. See you in the next article!</p><hr /><p>Try all this yourself with a free 30-day trial of Telerik UI for Blazor.</p><p><a href="https://www.telerik.com/try/ui-for-blazor" target="_blank" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/10827/17339193.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:5b92bc97-ce93-4caf-954e-4312b10c6ede</id>
    <title type="text">How to Build Semantic Search for Documentation with NestJS, Qdrant and Xenova</title>
    <summary type="text">In this post, we’ll build a semantic documentation search API that lets users ask natural-language questions instead of matching exact keywords.</summary>
    <published>2026-05-08T13:01:01Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Christian Nwamba </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17336853/how-build-semantic-search-documentation-nestjs-qdrant-xenova"/>
    <content type="text"><![CDATA[<p><span class="featured">In this post, we&rsquo;ll build a semantic documentation search API that lets users ask natural-language questions instead of matching exact keywords. Let&rsquo;s get started!</span></p><p>In this post, we&rsquo;ll build a semantic documentation search API that lets users ask natural-language questions instead of matching exact keywords. We&rsquo;ll use <a target="_blank" href="https://qdrant.tech/">Qdrant</a> as the vector database, <a target="_blank" href="https://www.npmjs.com/package/@xenova/transformers">Xenova/transformers</a> to generate local text embeddings and <a target="_blank" href="https://nestjs.com/">NestJS</a> as our API to tie everything together.</p><p>We will learn how to run Qdrant with <a target="_blank" href="https://www.docker.com/">Docker</a>, generate embeddings in <a target="_blank" href="https://nodejs.org/en">Node.js</a> and index docs as vectors with metadata in Qdrant. Our documentation API will provide a pure semantic search endpoint and a hybrid search endpoint that combines filters for an even more effective search.</p><h2 id="prerequisites">Prerequisites</h2><ul><li>Basic knowledge of NestJS and <a target="_blank" href="https://www.typescriptlang.org/">TypeScript</a> </li><li>Basic knowledge of HTTP, RESTful APIs, and cURL</li><li>Node.js and Docker should be installed</li></ul><h2 id="how-semantic-search-works">How Semantic Search Works</h2><p>Semantic search focuses on meaning, not just words. It understands user intent and contextual meaning, then finds data with similar meaning rather than matching keywords. Semantic search solves this by converting text into vectors (arrays of numbers) that capture meaning, and then comparing these vectors to find related information.</p><p>For example, if our docs contain the phrase &ldquo;How to authenticate users using JWT&rdquo; and a user searches for &ldquo;login security setup,&rdquo; semantic search can infer they mean the same thing.</p><h2 id="what-is-qdrant">What Is Qdrant?</h2><p>Qdrant is a vector database built for speed. It stores vectors and handles nearest neighbor calculations quickly. Qdrant uses the HNSW algorithm (Hierarchical Navigable Small World) to find similar vectors and return results in milliseconds. We&rsquo;ll use the official Docker image to run it locally, which keeps our environment clean and makes the database easy to start and stop.</p><h2 id="what-is-xenova">What Is Xenova?</h2><p>Xenova lets you run machine learning models directly in Node.js. We&rsquo;ll use it through the <code>@xenova/transformers</code> package to generate embeddings locally. This means no API calls, no rate limits and our data doesn&rsquo;t leave our machine. The model downloads once (~23 MB) and caches locally for future use.</p><h2 id="project-setup">Project Setup</h2><p>First, create a NestJS project:</p><pre class=" language-shell"><code class="prism  language-shell">nest new semantic-search-api
cd semantic-search-api
</code></pre><p>Next, run the command below to install our dependencies:</p><pre class=" language-shell"><code class="prism  language-shell">npm install @nestjs/config @qdrant/js-client-rest @xenova/transformers uuid \
  &amp;&amp; npm install --save-dev @types/uuid
</code></pre><p>In our <code>install command</code>, <code>@nestjs/config</code> is used to import environment variables into our app, <code>@qdrant/js-client-rest</code> is the JavaScript client for interacting with the Qdrant vector database, <code>@xenova/transformers</code> is used to generate local text embeddings, and <code>uuid</code> is used to create unique identifiers for documents and embeddings.</p><h2 id="running-qdrant-with-docker">Running Qdrant with Docker</h2><p>Instead of installing Qdrant directly, we&rsquo;ll use Docker Compose to keep our environment clean. Create a <code>docker-compose.yml</code> file at the root of your project and paste the code below:</p><pre class=" language-yml"><code class="prism  language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3.8'</span>

<span class="token key atrule">services</span><span class="token punctuation">:</span>
  <span class="token key atrule">qdrant</span><span class="token punctuation">:</span>
    <span class="token key atrule">image</span><span class="token punctuation">:</span> qdrant/qdrant<span class="token punctuation">:</span>latest
    <span class="token key atrule">container_name</span><span class="token punctuation">:</span> qdrant
    <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped
    <span class="token key atrule">ports</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> "6333<span class="token punctuation">:</span>6333"  <span class="token comment"># REST API port</span>
    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> ./qdrant_storage<span class="token punctuation">:</span>/qdrant/storage
</code></pre><p>Start the database in the background:</p><pre class=" language-shell"><code class="prism  language-shell">docker-compose up -d
</code></pre><p>Next, create a <code>.env</code> file and paste your Qdrant connection settings and embedding configuration:</p><pre class=" language-js"><code class="prism  language-js">QDRANT_URL<span class="token operator">=</span>http<span class="token punctuation">:</span><span class="token operator">/</span><span class="token operator">/</span>localhost<span class="token punctuation">:</span><span class="token number">6333</span>
QDRANT_COLLECTION<span class="token operator">=</span>documentation
QDRANT_VECTOR_DIMENSION<span class="token operator">=</span><span class="token number">384</span>
HF_MODEL_CACHE<span class="token operator">=</span><span class="token punctuation">.</span><span class="token operator">/</span>models
</code></pre><p>The variables above configure Qdrant&rsquo;s URL and collection name, set the vector dimension to 384 (which matches our embedding model), and specify where Xenova caches the downloaded model.</p><p>Next, let&rsquo;s update the <code>app</code> module to import the <code>ConfigModule</code>, so that we can load environment variables in our app:</p><p>Update your <code>app.module.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>

@<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>
    ConfigModule<span class="token punctuation">.</span><span class="token function">forRoot</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      isGlobal<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      envFilePath<span class="token punctuation">:</span> <span class="token string">'.env'</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token comment">// ... we will add other modules here later</span>
  <span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AppModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><h2 id="project-structure">Project Structure</h2><p>Our project structure will look like this:</p><pre class=" language-js"><code class="prism  language-js">src<span class="token operator">/</span>
├── qdrant<span class="token operator">/</span>
│   ├── qdrant<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   └── qdrant<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
├── embeddings<span class="token operator">/</span>
│   ├── embeddings<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   └── embeddings<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
├── documents<span class="token operator">/</span>
│   ├── documents<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
│   ├── documents<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>ts
│   ├── document<span class="token operator">-</span>ingestion<span class="token operator">/</span>
│   │   ├── document<span class="token operator">-</span>ingestion<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
│   │   └── document<span class="token operator">-</span>ingestion<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
│   └── document<span class="token operator">-</span>processor<span class="token operator">/</span>
│       ├── document<span class="token operator">-</span>processor<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
│       └── document<span class="token operator">-</span>processor<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
└── search<span class="token operator">/</span>
    ├── search<span class="token punctuation">.</span>module<span class="token punctuation">.</span>ts
    ├── search<span class="token punctuation">.</span>service<span class="token punctuation">.</span>ts
    ├── search<span class="token punctuation">.</span>service<span class="token punctuation">.</span>spec<span class="token punctuation">.</span>ts
    └── search<span class="token punctuation">.</span>controller<span class="token punctuation">.</span>ts
</code></pre><p>Run the command below to generate the necessary files:</p><pre class=" language-shell"><code class="prism  language-shell">nest g module qdrant &amp;&amp; \
nest g service qdrant &amp;&amp; \
nest g module embeddings &amp;&amp; \
nest g service embeddings &amp;&amp; \
nest g module documents &amp;&amp; \
nest g service documents/document-processor &amp;&amp; \
nest g service documents/document-ingestion &amp;&amp; \
nest g controller documents &amp;&amp; \
nest g module search &amp;&amp; \
nest g service search &amp;&amp; \
nest g controller search
</code></pre><h2 id="choosing-an-embedding-model">Choosing an Embedding Model</h2><p>For this project, we&rsquo;ll be using Xenova/all-MiniLM-L6-v2 for embeddings. This model is great at producing sentence-level embeddings, which work well for semantic search over documentation. It is relatively small and fast, and this makes it practical to run in Node.js without requiring a GPU. It outputs fixed 384-dimensional vectors (arrays with a fixed length of 384), which match our Qdrant collection configuration.</p><p>The model runs completely locally. On first use, Xenova downloads and caches it, and every subsequent run uses the cached version.</p><h2 id="building-the-embedding-service">Building the Embedding Service</h2><p>Our <code>EmbeddingService</code> will be responsible for converting text into vectors. Open the <code>embeddings.service.ts</code> file and update it with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>
  pipeline<span class="token punctuation">,</span>
  env<span class="token punctuation">,</span>
  FeatureExtractionPipeline<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@xenova/transformers'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">type</span> EmbeddingVector <span class="token operator">=</span> <span class="token keyword">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">EmbeddingsService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> extractor<span class="token punctuation">:</span> FeatureExtractionPipeline <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly DIMENSION<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly configService<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> vectorDimensionEnv <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span>getOrThrow<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>
      <span class="token string">'QDRANT_VECTOR_DIMENSION'</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>DIMENSION <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>vectorDimensionEnv<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">getExtractor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>FeatureExtractionPipeline<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>extractor<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      env<span class="token punctuation">.</span>localModelPath <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span>getOrThrow<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span>
        <span class="token string">'HF_MODEL_CACHE'</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>

      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Loading embedding model (first time only, ~5s)...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> pipe <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">pipeline</span><span class="token punctuation">(</span>
        <span class="token string">'feature-extraction'</span><span class="token punctuation">,</span>
        <span class="token string">'Xenova/all-MiniLM-L6-v2'</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>extractor <span class="token operator">=</span> pipe<span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Embedding model loaded.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>extractor<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">embed</span><span class="token punctuation">(</span>text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>EmbeddingVector<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> extractor <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getExtractor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> output <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">extractor</span><span class="token punctuation">(</span>text<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      pooling<span class="token punctuation">:</span> <span class="token string">'mean'</span><span class="token punctuation">,</span>
      normalize<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token keyword">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span>output<span class="token punctuation">.</span>data <span class="token keyword">as</span> Float32Array<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">embedBatch</span><span class="token punctuation">(</span>texts<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>EmbeddingVector<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> extractor <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getExtractor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> output <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">extractor</span><span class="token punctuation">(</span>texts<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      pooling<span class="token punctuation">:</span> <span class="token string">'mean'</span><span class="token punctuation">,</span>
      normalize<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span>output<span class="token punctuation">.</span>data <span class="token keyword">as</span> Float32Array<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">Array</span><span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span><span class="token punctuation">{</span> length<span class="token punctuation">:</span> texts<span class="token punctuation">.</span>length <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">=&gt;</span>
      data<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>i <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>DIMENSION<span class="token punctuation">,</span> <span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token keyword">this</span><span class="token punctuation">.</span>DIMENSION<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">warmup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">embed</span><span class="token punctuation">(</span><span class="token string">'warmup'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Embedding model warmup completed.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Embedding model warmup failed:'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">throw</span> error<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>extractor</code> property stores our loaded embedding model. It is initialized as <code>null</code> and loaded lazily on first use. This means the model only downloads when it is actually needed, rather than slowing down application startup.</p><p>The <code>getExtractor()</code> method loads and caches the model. First, we check if <code>this.extractor</code> already exists. If it does, we return it. If not, we set <code>env.localModelPath</code> to tell Xenova where to cache the downloaded model files, then call <code>pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2')</code> to download and load the model.</p><p>The <code>embed()</code> method calls the extractor with two options: <code>pooling: 'mean'</code>, which averages all token embeddings into a single vector, and <code>normalize: true</code>, which scales the vector to unit length (required for cosine similarity in Qdrant). The extractor returns a Float32Array, which we convert to a regular array using <code>Array.from()</code>.</p><p>For <code>embedBatch()</code>, we pass an array of texts to the extractor. The model returns a flattened array containing all vectors concatenated together. We split this back into individual vectors by slicing out chunks of 384 values (our vector dimension). The first text gets indices 0&ndash;383, the second gets 384&ndash;767 and so on.</p><p>The <code>warmup()</code> method runs a dummy embedding to preload the model, preventing the first real user request from experiencing a delay while the model loads. Be sure to export the <code>EmbeddingsService</code> in the <code>EmbeddingsModule</code>.</p><h2 id="building-the-qdrant-service">Building the Qdrant Service</h2><p>This service wraps the vector database and handles creating the collection as well as reading and writing vectors. Update the <code>qdrant.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ConfigService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/config'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>
  QdrantClient<span class="token punctuation">,</span>
  Schemas<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@qdrant/js-client-rest'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IQdrantPayload</span> <span class="token punctuation">{</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  url<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  chunkIndex<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  <span class="token punctuation">[</span>key<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">]</span><span class="token punctuation">:</span> unknown<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IQdrantPoint</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  vector<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  payload<span class="token punctuation">:</span> IQdrantPayload<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">QdrantService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly client<span class="token punctuation">:</span> QdrantClient<span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly vectorDimension<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> readonly collectionName<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly configService<span class="token punctuation">:</span> ConfigService<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'QDRANT_URL'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>url<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'QDRANT_URL is not set in environment'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">this</span><span class="token punctuation">.</span>collectionName <span class="token operator">=</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'QDRANT_COLLECTION'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">'documentation'</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> vectorDimensionEnv <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>configService<span class="token punctuation">.</span>getOrThrow<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token string">'QDRANT_VECTOR_DIMENSION'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>vectorDimension <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>vectorDimensionEnv<span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">this</span><span class="token punctuation">.</span>client <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">QdrantClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span> url <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">getCollectionName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">setupCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> collections <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">getCollections</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> exists <span class="token operator">=</span> collections<span class="token punctuation">.</span>collections<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span>
      c <span class="token operator">=&gt;</span> c<span class="token punctuation">.</span>name <span class="token operator">===</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>exists<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`✓ Collection "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" already exists.`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">createCollection</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      vectors<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        size<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>vectorDimension<span class="token punctuation">,</span>
        distance<span class="token punctuation">:</span> <span class="token string">'Cosine'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`✓ Created collection "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">".`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">upsertPoints</span><span class="token punctuation">(</span>points<span class="token punctuation">:</span> IQdrantPoint<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">upsert</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      wait<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      points<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">search</span><span class="token punctuation">(</span>
    vector<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    limit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span>
    filter<span class="token operator">?</span><span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'filter'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>Schemas\<span class="token punctuation">[</span><span class="token string">'ScoredPoint'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> params<span class="token punctuation">:</span> Schemas<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
      vector<span class="token punctuation">,</span>
      limit<span class="token punctuation">,</span>
      with_payload<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
      with_vector<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span>
      <span class="token operator">...</span><span class="token punctuation">(</span>filter <span class="token operator">&amp;&amp;</span> <span class="token punctuation">{</span> filter <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>client<span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>collectionName<span class="token punctuation">,</span> params<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>We define an interface for our vector points. Each point has an ID, a vector and a payload. The payload holds metadata such as the text content and URL.</p><p>Our constructor reads our environment variables and creates a Qdrant client. The <code>setupCollection()</code> method checks if our collection exists and creates it if it doesn&rsquo;t. We use Cosine distance, which is the standard for semantic similarity.</p><p>The <code>upsertPoints()</code> method saves vectors and their metadata to Qdrant. Finally, the <code>search()</code> method finds similar vectors. We request the payload but not the vector itself, since we only need the metadata for displaying results.</p><h2 id="document-chunking">Document Chunking</h2><h3 id="document-processing-service">Document Processing Service</h3><p>LLMs and vector databases work best with smaller chunks of text. When large text, such as a 10-page document, is embedded as a single vector, specific details get lost.</p><p>While our Xenova model has a safe upper bound limit of approximately 2,000 characters, for best search quality it is advised to embed text with a length of 400&ndash;600 characters. Therefore, we need to split documents into chunks; for this project, we&rsquo;ll aim for around 500 characters.</p><p>Our chunking strategy will aim for the maximum number of complete paragraphs we can fit within the 500-character limit. Then we&rsquo;ll start a new chunk with an overlap from the end of the previous chunk. The purpose of the overlap is to preserve context across chunk boundaries.</p><p>Update your <code>document-processor.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IDocumentMetadata</span> <span class="token punctuation">{</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  url<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IDocumentChunk</span> <span class="token punctuation">{</span>
  text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  chunkIndex<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  metadata<span class="token punctuation">:</span> IDocumentMetadata<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DocumentProcessorService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly CHUNK_SIZE <span class="token operator">=</span> <span class="token number">500</span><span class="token punctuation">;</span> <span class="token comment">// characters</span>
  <span class="token keyword">private</span> readonly OVERLAP <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span>     <span class="token comment">// characters</span>


  <span class="token function">chunkDocument</span><span class="token punctuation">(</span>
    content<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    metadata<span class="token punctuation">:</span> IDocumentMetadata<span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> IDocumentChunk<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> chunks<span class="token punctuation">:</span> IDocumentChunk<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> paragraphs <span class="token operator">=</span> content
      <span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'\n\n'</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>p <span class="token operator">=&gt;</span> p<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>p <span class="token operator">=&gt;</span> p<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">let</span> currentChunk <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
    <span class="token keyword">let</span> chunkIndex <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> paragraph <span class="token keyword">of</span> paragraphs<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> potentialChunk <span class="token operator">=</span> currentChunk
        <span class="token operator">?</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>currentChunk<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n\n</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>paragraph<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span>
        <span class="token punctuation">:</span> paragraph<span class="token punctuation">;</span>

      <span class="token keyword">if</span> <span class="token punctuation">(</span>potentialChunk<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>CHUNK_SIZE <span class="token operator">&amp;&amp;</span> currentChunk<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// Current chunk is full; emit it</span>
        chunks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
          text<span class="token punctuation">:</span> currentChunk<span class="token punctuation">,</span>
          chunkIndex<span class="token punctuation">,</span>
          metadata<span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// Start new chunk with overlap (prefer complete sentence, fallback to word boundary)</span>
        <span class="token keyword">const</span> overlap <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">findOverlap</span><span class="token punctuation">(</span>currentChunk<span class="token punctuation">)</span><span class="token punctuation">;</span>
        currentChunk <span class="token operator">=</span> overlap <span class="token operator">+</span> <span class="token string">'\n\n'</span> <span class="token operator">+</span> paragraph<span class="token punctuation">;</span>
        chunkIndex<span class="token operator">++</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        currentChunk <span class="token operator">=</span> potentialChunk<span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token comment">// Emit last chunk</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>currentChunk<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      chunks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        text<span class="token punctuation">:</span> currentChunk<span class="token punctuation">,</span>
        chunkIndex<span class="token punctuation">,</span>
        metadata<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> chunks<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">findOverlap</span><span class="token punctuation">(</span>text<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> searchWindow <span class="token operator">=</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token comment">// Try to find last complete sentence</span>
    <span class="token keyword">const</span> sentenceMatch <span class="token operator">=</span> searchWindow<span class="token punctuation">.</span><span class="token function">match</span><span class="token punctuation">(</span><span class="token regex">/[.!?]\s+([^.!?]+)$/</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>sentenceMatch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> sentenceMatch<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token comment">// Fallback: find word boundary near target overlap length</span>
    <span class="token keyword">const</span> tail <span class="token operator">=</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP <span class="token operator">*</span> <span class="token number">1.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> wordMatch <span class="token operator">=</span> tail<span class="token punctuation">.</span><span class="token function">match</span><span class="token punctuation">(</span><span class="token regex">/\s+(\S+.*)$/</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>wordMatch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> wordMatch<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    
    <span class="token comment">// Last resort: from last space</span>
    <span class="token keyword">const</span> lastSpace <span class="token operator">=</span> text<span class="token punctuation">.</span><span class="token function">lastIndexOf</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">,</span> text<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> lastSpace <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">?</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>lastSpace <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> text<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token keyword">this</span><span class="token punctuation">.</span>OVERLAP<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above, we set our chunk size to 500 characters with a 50-character overlap. The overlap helps preserve context across chunk boundaries.</p><p>The <code>chunkDocument()</code> method splits text by paragraph, then accumulates paragraphs until the next one would exceed our limit. It then saves the chunk and starts a new one with an overlap from the end of the chunk that was just saved.</p><p>The <code>findOverlap()</code> method tries to find a complete sentence for the overlap first. If that fails, it looks for a word boundary. This keeps the overlap readable rather than cutting words in half.</p><h3 id="document-ingestion-service">Document Ingestion Service</h3><p>This service processes raw documents, converts them to vectors and saves them in Qdrant. Update your <code>document-ingestion.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> v4 <span class="token keyword">as</span> uuidv4 <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'uuid'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DocumentProcessorService<span class="token punctuation">,</span> IDocumentMetadata<span class="token punctuation">,</span> IDocumentChunk <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../document-processor/document-processor.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> EmbeddingsService<span class="token punctuation">,</span> EmbeddingVector <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../../embeddings/embeddings.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> QdrantService<span class="token punctuation">,</span> IQdrantPoint<span class="token punctuation">,</span> IQdrantPayload <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../../qdrant/qdrant.service'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">IRawDocument</span> <span class="token keyword">extends</span> <span class="token class-name">IDocumentMetadata</span> <span class="token punctuation">{</span>
  content<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DocumentIngestionService</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> readonly processor<span class="token punctuation">:</span> DocumentProcessorService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> readonly embeddings<span class="token punctuation">:</span> EmbeddingsService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> readonly qdrant<span class="token punctuation">:</span> QdrantService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">/**
   * Ingest one or more raw documents:
   * - Chunk content into smaller overlapping pieces.
   * - Embed all chunk texts in a batch.
   * - Upsert points (vector + payload) into Qdrant.
   */</span>
  <span class="token keyword">async</span> <span class="token function">ingestDocuments</span><span class="token punctuation">(</span>docs<span class="token punctuation">:</span> IRawDocument<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token punctuation">{</span>
    status<span class="token punctuation">:</span> <span class="token string">'ok'</span> <span class="token operator">|</span> <span class="token string">'error'</span><span class="token punctuation">;</span>
    documents<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
    totalChunks<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
    skipped<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
    error<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>docs<span class="token operator">?</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> status<span class="token punctuation">:</span> <span class="token string">'ok'</span><span class="token punctuation">,</span> documents<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> totalChunks<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> skipped<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>qdrant<span class="token punctuation">.</span><span class="token function">setupCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">let</span> totalChunks <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
      <span class="token keyword">let</span> skipped <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> doc <span class="token keyword">of</span> docs<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">ingestDocument</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          totalChunks <span class="token operator">+=</span> result<span class="token punctuation">.</span>chunks<span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
          skipped<span class="token operator">++</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">return</span> <span class="token punctuation">{</span>
        status<span class="token punctuation">:</span> <span class="token string">'ok'</span><span class="token punctuation">,</span>
        documents<span class="token punctuation">:</span> docs<span class="token punctuation">.</span>length <span class="token operator">-</span> skipped<span class="token punctuation">,</span>
        totalChunks<span class="token punctuation">,</span>
        skipped<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Fatal error during ingestion:'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span>
        status<span class="token punctuation">:</span> <span class="token string">'error'</span><span class="token punctuation">,</span>
        documents<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
        totalChunks<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
        skipped<span class="token punctuation">:</span> docs<span class="token operator">?</span><span class="token punctuation">.</span>length <span class="token operator">?</span><span class="token operator">?</span> <span class="token number">0</span><span class="token punctuation">,</span>
        error<span class="token punctuation">:</span> error <span class="token keyword">instanceof</span> <span class="token class-name">Error</span> <span class="token operator">?</span> error<span class="token punctuation">.</span>message <span class="token punctuation">:</span> <span class="token string">'Unknown error'</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">ingestDocument</span><span class="token punctuation">(</span>doc<span class="token punctuation">:</span> IRawDocument<span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span><span class="token punctuation">{</span>
    success<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
    chunks<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>doc<span class="token punctuation">.</span>title <span class="token operator">||</span> <span class="token operator">!</span>doc<span class="token punctuation">.</span>content<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">'Skipping document, missing title or content'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">const</span> metadata<span class="token punctuation">:</span> IDocumentMetadata <span class="token operator">=</span> <span class="token punctuation">{</span>
        title<span class="token punctuation">:</span> doc<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
        category<span class="token punctuation">:</span> doc<span class="token punctuation">.</span>category <span class="token operator">||</span> <span class="token string">'uncategorized'</span><span class="token punctuation">,</span>
        url<span class="token punctuation">:</span> doc<span class="token punctuation">.</span>url <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>

      <span class="token keyword">const</span> chunks <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>processor<span class="token punctuation">.</span><span class="token function">chunkDocument</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>content<span class="token punctuation">,</span> metadata<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>chunks<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`Skipping "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" - produced no chunks`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">const</span> vectors <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>embeddings<span class="token punctuation">.</span><span class="token function">embedBatch</span><span class="token punctuation">(</span>
        chunks<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>chunk <span class="token operator">=&gt;</span> chunk<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token keyword">if</span> <span class="token punctuation">(</span>vectors<span class="token punctuation">.</span>length <span class="token operator">!==</span> chunks<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>
          <span class="token template-string"><span class="token string">`Error ingesting "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">": expected </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>chunks<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> vectors, got </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>vectors<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>

      <span class="token keyword">const</span> points <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">createQdrantPoints</span><span class="token punctuation">(</span>chunks<span class="token punctuation">,</span> vectors<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>qdrant<span class="token punctuation">.</span><span class="token function">upsertPoints</span><span class="token punctuation">(</span>points<span class="token punctuation">)</span><span class="token punctuation">;</span>

      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`✓ Ingested "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" (</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>chunks<span class="token punctuation">.</span>length<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> chunks).`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> chunks<span class="token punctuation">.</span>length <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`Error ingesting "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>doc<span class="token punctuation">.</span>title<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">":`</span></span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> success<span class="token punctuation">:</span> <span class="token keyword">false</span><span class="token punctuation">,</span> chunks<span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">createQdrantPoints</span><span class="token punctuation">(</span>
    chunks<span class="token punctuation">:</span> IDocumentChunk<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    vectors<span class="token punctuation">:</span> EmbeddingVector<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> IQdrantPoint<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> chunks<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>chunk<span class="token punctuation">,</span> index<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
      id<span class="token punctuation">:</span> <span class="token function">uuidv4</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      vector<span class="token punctuation">:</span> vectors<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">,</span>
      payload<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        title<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>title<span class="token punctuation">,</span>
        category<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>category<span class="token punctuation">,</span>
        url<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>url<span class="token punctuation">,</span>
        text<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>text<span class="token punctuation">,</span>
        chunkIndex<span class="token punctuation">:</span> chunk<span class="token punctuation">.</span>chunkIndex<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>In our constructor, we inject three services: the document processor for chunking, the embeddings service for generating vectors and the Qdrant service for database operations.</p><p>The <code>ingestDocuments()</code> method processes multiple documents at once. First, it confirms that the Qdrant collection is set up, then it processes each document individually. If one document fails, we still proceed with the others while tracking which documents were skipped and which were successful.</p><p>The <code>ingestDocument()</code> method handles the actual ingestion for individual documents. It verifies that the document has the required fields, sets up metadata, chunks the content, generates embeddings and saves them to Qdrant. It also confirms that the number of vectors is consistent with the number of chunks; if not, it sends a warning and skips that document.</p><p>The <code>createQdrantPoints()</code> method is a helper that combines our chunks, vectors and metadata into the format Qdrant expects.</p><h3 id="documents-controller-and-module">Documents Controller and Module</h3><p>Update your <code>documents.controller.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Body<span class="token punctuation">,</span> Controller<span class="token punctuation">,</span> Post <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> DocumentIngestionService<span class="token punctuation">,</span> IRawDocument <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./document-ingestion/document-ingestion.service'</span><span class="token punctuation">;</span>

@<span class="token function">Controller</span><span class="token punctuation">(</span><span class="token string">'documents'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">DocumentsController</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly ingestionService<span class="token punctuation">:</span> DocumentIngestionService<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  @<span class="token function">Post</span><span class="token punctuation">(</span><span class="token string">'ingest'</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">ingest</span><span class="token punctuation">(</span>@<span class="token function">Body</span><span class="token punctuation">(</span><span class="token punctuation">)</span> body<span class="token punctuation">:</span> <span class="token punctuation">{</span> docs<span class="token punctuation">:</span> IRawDocument<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>body<span class="token operator">?</span><span class="token punctuation">.</span>docs<span class="token operator">?</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'No documents provided'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>ingestionService<span class="token punctuation">.</span><span class="token function">ingestDocuments</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span>docs<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Next, update the <code>DocumentsModule to import the EmbeddingsModule</code> and <code>QdrantModule</code>.</p><h2 id="building-the-search-service">Building the Search Service</h2><p>The search service handles user queries. Update your <code>search.service.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> EmbeddingsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../embeddings/embeddings.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> QdrantService<span class="token punctuation">,</span> IQdrantPayload <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../qdrant/qdrant.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> Schemas <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@qdrant/js-client-rest'</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">ISearchResult</span> <span class="token punctuation">{</span>
  title<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  snippet<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  url<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  category<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  score<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  chunkIndex<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SearchService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> <span class="token keyword">static</span> readonly MIN_LIMIT <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> <span class="token keyword">static</span> readonly MAX_LIMIT <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span>
  <span class="token keyword">private</span> <span class="token keyword">static</span> readonly DEFAULT_LIMIT <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>

  <span class="token keyword">constructor</span><span class="token punctuation">(</span>
    <span class="token keyword">private</span> readonly embeddings<span class="token punctuation">:</span> EmbeddingsService<span class="token punctuation">,</span>
    <span class="token keyword">private</span> readonly qdrant<span class="token punctuation">:</span> QdrantService<span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">mapHits</span><span class="token punctuation">(</span>hits<span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'ScoredPoint'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> hits
      <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>hit <span class="token operator">=&gt;</span> hit<span class="token punctuation">.</span>payload <span class="token operator">&amp;&amp;</span> hit<span class="token punctuation">.</span>score <span class="token operator">!==</span> undefined<span class="token punctuation">)</span>
      <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>hit <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> payload <span class="token operator">=</span> hit<span class="token punctuation">.</span>payload <span class="token keyword">as</span> IQdrantPayload<span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span>
          title<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>title <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          snippet<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>text <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          url<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>url <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          category<span class="token punctuation">:</span> <span class="token function">String</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>category <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
          score<span class="token punctuation">:</span> hit<span class="token punctuation">.</span>score <span class="token operator">?</span><span class="token operator">?</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token comment">//similarity score</span>
          chunkIndex<span class="token punctuation">:</span> <span class="token function">Number</span><span class="token punctuation">(</span>payload<span class="token operator">?</span><span class="token punctuation">.</span>chunkIndex <span class="token operator">?</span><span class="token operator">?</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">validateAndNormalizeQuery</span><span class="token punctuation">(</span>query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> trimmed <span class="token operator">=</span> query<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> trimmed <span class="token operator">&amp;&amp;</span> trimmed<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span> <span class="token operator">?</span> trimmed <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">clampLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>SearchService<span class="token punctuation">.</span>MIN_LIMIT<span class="token punctuation">,</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>SearchService<span class="token punctuation">.</span>MAX_LIMIT<span class="token punctuation">,</span> limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">createCategoryFilter</span><span class="token punctuation">(</span>category<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> undefined<span class="token punctuation">)</span><span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'filter'</span><span class="token punctuation">]</span> <span class="token operator">|</span> undefined <span class="token punctuation">{</span>
    <span class="token keyword">const</span> trimmed <span class="token operator">=</span> category<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>trimmed<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> undefined<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      must<span class="token punctuation">:</span> <span class="token punctuation">[</span>
        <span class="token punctuation">{</span>
          key<span class="token punctuation">:</span> <span class="token string">'category'</span><span class="token punctuation">,</span>
          match<span class="token punctuation">:</span> <span class="token punctuation">{</span> value<span class="token punctuation">:</span> trimmed <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token function">performSearch</span><span class="token punctuation">(</span>
    query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    limit<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span>
    filter<span class="token operator">?</span><span class="token punctuation">:</span> Schemas\<span class="token punctuation">[</span><span class="token string">'SearchRequest'</span>\<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'filter'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> vector <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>embeddings<span class="token punctuation">.</span><span class="token function">embed</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">const</span> hits <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>qdrant<span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span>vector<span class="token punctuation">,</span> limit<span class="token punctuation">,</span> filter<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">mapHits</span><span class="token punctuation">(</span>hits<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Error performing search:'</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>

  
  <span class="token keyword">async</span> <span class="token function">search</span><span class="token punctuation">(</span>query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span> limit <span class="token operator">=</span> SearchService<span class="token punctuation">.</span>DEFAULT_LIMIT<span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> normalizedQuery <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">validateAndNormalizeQuery</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>normalizedQuery<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">performSearch</span><span class="token punctuation">(</span>normalizedQuery<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">clampLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">async</span> <span class="token function">searchWithCategory</span><span class="token punctuation">(</span>
    query<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    category<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span>
    limit <span class="token operator">=</span> SearchService<span class="token punctuation">.</span>DEFAULT_LIMIT<span class="token punctuation">,</span>
  <span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>ISearchResult<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> normalizedQuery <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">validateAndNormalizeQuery</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>normalizedQuery<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">const</span> filter <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">createCategoryFilter</span><span class="token punctuation">(</span>category<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">performSearch</span><span class="token punctuation">(</span>normalizedQuery<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">clampLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">,</span> filter<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The <code>mapHits()</code> method converts Qdrant&rsquo;s raw response into a user-friendly format. The <code>validateAndNormalizeQuery()</code> method verifies we have an actual query string, <code>clampLimit()</code> keeps result counts within safe limits and <code>createCategoryFilter()</code> builds the Qdrant filter object when users filter by category.</p><p>The <code>performSearch()</code> method embeds the user query, searches Qdrant and maps the results.</p><p>The <code>search()</code> method uses only pure semantic search with no filters, while <code>searchWithCategory()</code> adds category filtering for more specific searches.</p><h2 id="search-controller">Search Controller</h2><p>Our search controller exposes two endpoints. The <code>/search</code> endpoint provides pure semantic search, while <code>/search/hybrid</code> adds category filtering. Update your <code>search.controller.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> Controller<span class="token punctuation">,</span> Get<span class="token punctuation">,</span> Query <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> SearchService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./search.service'</span><span class="token punctuation">;</span>

@<span class="token function">Controller</span><span class="token punctuation">(</span><span class="token string">'search'</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SearchController</span> <span class="token punctuation">{</span>
  <span class="token keyword">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> readonly searchService<span class="token punctuation">:</span> SearchService<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">parseLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">:</span> <span class="token keyword">string</span> <span class="token operator">|</span> undefined<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> parsed <span class="token operator">=</span> <span class="token function">Number</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token function">isNaN</span><span class="token punctuation">(</span>parsed<span class="token punctuation">)</span> <span class="token operator">||</span> parsed <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token number">10</span> <span class="token punctuation">:</span> parsed<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  @<span class="token function">Get</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">search</span><span class="token punctuation">(</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'q'</span><span class="token punctuation">)</span> q<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'limit'</span><span class="token punctuation">)</span> limit<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>q<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'Query parameter "q" is required'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>searchService<span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span>q<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">parseLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  @<span class="token function">Get</span><span class="token punctuation">(</span><span class="token string">'hybrid'</span><span class="token punctuation">)</span>
  <span class="token keyword">async</span> <span class="token function">hybrid</span><span class="token punctuation">(</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'q'</span><span class="token punctuation">)</span> q<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'category'</span><span class="token punctuation">)</span> category<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
    @<span class="token function">Query</span><span class="token punctuation">(</span><span class="token string">'limit'</span><span class="token punctuation">)</span> limit<span class="token operator">?</span><span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">,</span>
  <span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>q<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'Query parameter "q" is required'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>searchService<span class="token punctuation">.</span><span class="token function">searchWithCategory</span><span class="token punctuation">(</span>q<span class="token punctuation">,</span> category<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">parseLimit</span><span class="token punctuation">(</span>limit<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Finally, update the <code>SearchModule</code> to import the <code>EmbeddingsModule</code> and <code>QdrantModule</code>.</p><h2 id="application-warmup">Application Warmup</h2><p>We don&rsquo;t want the first user request to hang while our ML model loads, so we&rsquo;ll add a warmup phase during application startup.</p><p>Update your <code>main.ts</code> file with the following:</p><pre class=" language-ts"><code class="prism  language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> NestFactory <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/core'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> AppModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./app.module'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> EmbeddingsService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./embeddings/embeddings.service'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> QdrantService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./qdrant/qdrant.service'</span><span class="token punctuation">;</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">bootstrap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">await</span> NestFactory<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>AppModule<span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Starting services warmup...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> embeddings <span class="token operator">=</span> app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span>EmbeddingsService<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> qdrant <span class="token operator">=</span> app<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span>QdrantService<span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>
      embeddings<span class="token punctuation">.</span><span class="token function">warmup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      qdrant<span class="token punctuation">.</span><span class="token function">setupCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'✓ Services ready.'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Warmup failed'</span><span class="token punctuation">,</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span>
    process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">await</span> app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">bootstrap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>This loads the ML model and sets up the database collection before accepting requests.</p><h2 id="testing-the-api">Testing the API</h2><p>Run the following in your terminal to start your server:</p><pre class=" language-shell"><code class="prism  language-shell">npm run start:dev
</code></pre><p>You should see the warmup logs followed by the server start message.</p><h3 id="ingesting-documents">Ingesting Documents</h3><p>Let&rsquo;s add some test documents:</p><pre class=" language-shell"><code class="prism  language-shell">curl -X POST http://localhost:3000/documents/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "docs": [
      {
        "title": "API Authentication",
        "category": "Security",
        "url": "/docs/auth",
        "content": "To access the API, you must use a Bearer token in the header. Tokens expire after 1 hour."
      },
      {
        "title": "Rate Limiting",
        "category": "Performance",
        "url": "/docs/rate-limits",
        "content": "We limit requests to 100 per minute per IP address. Exceeding this returns a 429 Too Many Requests error."
      }
    ]
  }'
</code></pre><p>Your response should be:</p><pre class=" language-shell"><code class="prism  language-shell">{
  "status": "ok",
  "documents": 2,
  "totalChunks": 2,
  "skipped": 0
}
</code></pre><h3 id="searching-documents">Searching Documents</h3><p>Let&rsquo;s try a semantic search. Note that we&rsquo;re not using the exact words &ldquo;Bearer&rdquo; or &ldquo;header&rdquo;:</p><pre class=" language-shell"><code class="prism  language-shell">curl "http://localhost:3000/search?q=how%20do%20I%20log%20in%20to%20the%20api"
</code></pre><p>The system understands that &ldquo;log in&rdquo; is semantically related to &ldquo;authentication&rdquo; and &ldquo;Bearer token,&rdquo; so it returns the Authentication document.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/sample-response-for-semantic-search.png?sfvrsn=d20d142e_2" title="Sample response for semantic search" alt="Sample response for semantic search" /></p><h3 id="testing-hybrid-search">Testing Hybrid Search</h3><p>Let&rsquo;s try searching with a category filter:</p><pre class=" language-shell"><code class="prism  language-shell">curl "http://localhost:3000/search/hybrid?q=api%20limits&amp;category=Performance"
</code></pre><p>Your response should be:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/sample-response-for-category-filter.png?sfvrsn=29ba3928_2" title="Sample response for category filter" alt="Sample response for category filter" /></p><p>This searches for content semantically related to &ldquo;api limits&rdquo; but only returns results in the &ldquo;Performance&rdquo; category.</p><h2 id="conclusion">Conclusion</h2><p>In this article, we learned how to run a vector database locally with Docker, generate embeddings without external APIs, chunk documents with overlapping windows and combine vector similarity with metadata filtering.</p><p>Possible next steps include swapping Xenova for OpenAI if we need larger models, or moving Qdrant to a cloud cluster for millions of vectors, while preserving the existing NestJS logic.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">What We&rsquo;ve Learned Designing Dev Tools in the Age of AI</h4></div><div class="col-8"><p class="u-fs16 u-mb0">AI didn&rsquo;t just change how we write code. It changed how we build products. <a target="_blank" href="https://www.telerik.com/blogs/what-weve-learned-designing-dev-tools-ai">Hear four lessons from real tech professionals building dev tools.</a></p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17336853.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:bb2fa99e-0c29-407f-9eb9-4d2b43a9632a</id>
    <title type="text">Creating More Realistic Tests with In-Memory Databases in ASP.NET Core</title>
    <summary type="text">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</summary>
    <published>2026-05-06T17:20:57Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Assis Zang </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17335641/creating-more-realistic-tests-memory-databases-aspnet-core"/>
    <content type="text"><![CDATA[<p><span class="featured">Testing ASP.NET Core APIs with in-memory SQLite and JustMock enables validation of real-world scenarios like pagination, keys and rules without a real database.</span></p><p>Testing a web API involves much more than simply checking whether methods return or send data correctly. It requires validating real-world behaviors like pagination, unique keys, data persistence and other database rules. Anticipating and reproducing these scenarios can be challenging, especially when they depend on specific query behaviors or stored data.</p><p>To address this, the .NET ecosystem provides powerful tools for unit testing. In this post, we explore how in-memory SQLite serves as an effective solution for testing ASP.NET Core APIs, enabling developers to simulate common scenarios without deploying a real database. Additionally, we demonstrate how tools like Progress Telerik JustMock can streamline test implementation and help validate business rules efficiently.</p><h2 id="the-importance-of-testing-in-real-world-environments">The Importance of Testing in Real-World Environments</h2><p>A common example of functionality in web APIs is paginated search, which at first glance is quite simple. We receive parameters such as <code>page</code> and <code>pageSize</code>, apply a Skip and a Take, return the data and move on. In a controlled scenario, with few records, it&rsquo;s difficult for something to go wrong.</p><p>The problem begins when this code reaches production or even test environments, without having been validated with a real database. Consider a common scenario: in a paginated search, sorting is essential for it to function correctly. Without an explicit <code>OrderBy</code>, the database does not guarantee the order of the returned records.</p><p>In in-memory lists, the result usually appears stable, which completely masks the problem. Without tests running this query on a relational database, the error goes unnoticed, resulting in inconsistent pagination with duplicate records between pages, items &ldquo;disappearing&rdquo; when navigating between pages or different results for the same request. This type of bug is especially difficult to reproduce locally when not using a real database or even a simulation of one.</p><p>This is where the in-memory database plays a very important role. It allows the query to be executed exactly as it will be executed in production, revealing flaws that only appear when pagination, sorting and the database work together.</p><h2 id="understanding-in-memory-sqlite">Understanding In-Memory SQLite</h2><p>In-memory SQLite is a way to use the SQLite database entirely in memory. Instead of saving data to a <code>.db</code> file, SQLite creates the database only while the application or connection is active. When the connection is closed, all data is discarded.</p><p>In-memory SQLite is widely used in automated testing, especially in ASP.NET Core applications in conjunction with Entity Framework Core, as it has advanced features such as simulating a real database and executing real SQL (constraints, indexes, unique keys, FK and others).</p><h2 id="testing-with-sqlite-in-memory">Testing with SQLite in Memory</h2><p>Next, we&rsquo;ll look at some examples of unit tests in common web API scenarios where in-memory SQLite excels. But first, let&rsquo;s create the basic application and then add the tests.</p><p>You can access all the code discussed in this post in this GitHub repository: <a target="_blank" href="https://github.com/zangassis/memo-order">Memo Order source code</a>.</p><p>To create the application you can use the command below:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new web -o MemoOrder
</code></pre><p>To add the NuGet packages you can use the following commands:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet add package Microsoft.EntityFrameworkCore --version 10.0.1
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.1
</code></pre><p>Then, open the application and create the following classes:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> TotalAmount <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> DateTime CreatedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">bool</span> IsDeleted <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderSummaryDto</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">decimal</span> Total <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AppDbContext</span> <span class="token punctuation">:</span> DbContext
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token function">AppDbContext</span><span class="token punctuation">(</span>DbContextOptions<span class="token operator">&lt;</span>AppDbContext<span class="token operator">&gt;</span> options<span class="token punctuation">)</span>
        <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> DbSet<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span> Orders <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token generic-method function">Set<span class="token punctuation">&lt;</span>Order<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnModelCreating</span><span class="token punctuation">(</span>ModelBuilder modelBuilder<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        modelBuilder<span class="token punctuation">.</span><span class="token generic-method function">Entity<span class="token punctuation">&lt;</span>Order<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span>entity <span class="token operator">=</span><span class="token operator">&gt;</span>
        <span class="token punctuation">{</span>
            entity<span class="token punctuation">.</span><span class="token function">HasKey</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>
            entity<span class="token punctuation">.</span><span class="token function">Property</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">IsRequired</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            entity<span class="token punctuation">.</span><span class="token function">HasIndex</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">IsUnique</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
entity<span class="token punctuation">.</span><span class="token function">HasQueryFilter</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token operator">!</span>o<span class="token punctuation">.</span>IsDeleted<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderRepository</span>
<span class="token punctuation">{</span>
    <span class="token keyword">private</span> <span class="token keyword">readonly</span> AppDbContext _context<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token function">OrderRepository</span><span class="token punctuation">(</span>AppDbContext context<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context <span class="token operator">=</span> context<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AddAsync</span><span class="token punctuation">(</span>Order order<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        _context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">await</span> _context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">async</span> Task<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span><span class="token operator">&gt;</span> <span class="token function">Pagination</span><span class="token punctuation">(</span>AppDbContext context<span class="token punctuation">,</span> <span class="token keyword">int</span> skip<span class="token punctuation">,</span> <span class="token keyword">int</span> take<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
                    <span class="token punctuation">.</span><span class="token function">OrderBy</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">Skip</span><span class="token punctuation">(</span>skip<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span>take<span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now let&rsquo;s create a special factory class that will be used in the tests to create the in-memory database and keep the connection open while the tests are running. Create the following class in the application:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">namespace</span> MemoOrder<span class="token punctuation">;</span>

<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>Data<span class="token punctuation">.</span>Sqlite<span class="token punctuation">;</span>
<span class="token keyword">using</span> Microsoft<span class="token punctuation">.</span>EntityFrameworkCore<span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SqliteInMemoryFactory</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> AppDbContext <span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> connection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SqliteConnection</span><span class="token punctuation">(</span><span class="token string">"DataSource=:memory:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        connection<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DbContextOptionsBuilder</span><span class="token operator">&lt;</span>AppDbContext<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">UseSqlite</span><span class="token punctuation">(</span>connection<span class="token punctuation">)</span>
            <span class="token punctuation">.</span>Options<span class="token punctuation">;</span>

        <span class="token keyword">var</span> context <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AppDbContext</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span>
        context<span class="token punctuation">.</span>Database<span class="token punctuation">.</span><span class="token function">EnsureCreated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">return</span> context<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that to tell SQLite to use an in-memory database, we use <code>DataSource=:memory:</code> in the connection string.</p><h3 id="creating-the-test-project">Creating the Test Project</h3><p>All the code needed to implement the unit tests is ready, so let&rsquo;s create the test project and download the NuGet packages for it. To do this, you can use the following commands, simply run them in a terminal in the application&rsquo;s root directory:</p><pre class=" language-bash"><code class="prism  language-bash">dotnet new xunit -n MemoOrder.Tests
<span class="token function">cd</span> MemoOrder.Tests
dotnet add package Microsoft.NET.Test.Sdk --version 18.0.1
dotnet add package xunit --version 2.9.3
dotnet add package xunit.runner.visualstudio --version 3.1.5
dotnet add package coverlet.collector --version 6.0.4
dotnet add reference <span class="token punctuation">..</span>/MemoOrder/MemoOrder.csproj
</code></pre><p>Next, inside the project MemoOrder.Tests, create a new class called OrderRepositoryTests and let&rsquo;s add tests to it.</p><h3 id="testing-persistence">Testing Persistence</h3><p>The first test will be used to validate persistence&mdash;that is, to insert data into the database (which in this case will be the in-memory database). To do this, simply add the following test method to the <code>OrderRepositoryTests</code> class:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">AddAsync_ShouldPersistOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderRepository</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-2026-0001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> 200m<span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    <span class="token keyword">var</span> savedOrder <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">FirstAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-2026-0001"</span><span class="token punctuation">,</span> savedOrder<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span>200m<span class="token punctuation">,</span> savedOrder<span class="token punctuation">.</span>TotalAmount<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that we use <code>var context = SqliteInMemoryFactory.Create();</code> to create and represent the database in memory. It works exactly like the EF Core context that maps database tables to class entities.</p><h3 id="testing-constraint-unique">Testing Constraint UNIQUE</h3><p>Another common mistake is allowing duplicate records to reach the production environment, which in many cases can have catastrophic impacts. With in-memory testing, we can anticipate problems like this. Simply create a test that verifies inserting a duplicate record will generate an exception. In this case, we could do the following:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Should_Throw_When_Duplicated_Order_Number</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> Assert<span class="token punctuation">.</span><span class="token generic-method function">ThrowsAsync<span class="token punctuation">&lt;</span>DbUpdateException<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In this test, we created an instance of the database in memory, added an item to the table and saved it.</p><p>Then, we tried to add the same item, which generates an exception because, in the dbContext configuration, we declared the Number property of the Order entity as Unique: <code>entity.HasIndex(o =&gt; o.Number).IsUnique();</code>.</p><p>Thus, we have a real-world scenario, demonstrating that duplicate records will not reach the production environment, as the unit test is able to reproduce exactly the same result.</p><h3 id="testing-pagination">Testing Pagination</h3><p>To test pagination using an in-memory database, we could do the following:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Should_Return_Paginated_Orders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderRepository</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> $<span class="token string">"ORD-{i}"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> i <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    List<span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span> page <span class="token operator">=</span> <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">Pagination</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> page<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-6"</span><span class="token punctuation">,</span> page<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>Note that here we create 10 items and insert them into the in-memory database. Then, we use the <code>Pagination(context, 5, 5)</code> method to perform the pagination and finally verify if the expected quantity and item are correct.</p><p>An in-memory database wouldn&rsquo;t be mandatory to test pagination. This could be done using only regular in-memory lists. However, in this way, we can reproduce the same production scenario.</p><h3 id="testing-soft-delete">Testing Soft Delete</h3><p>Soft-delete is a technique where data is not physically removed from the database, but rather marked as inactive through a flag, such as an <code>isDeleted</code> or <code>deleted_at</code> column. This allows you to keep the entire transaction history in the database.</p><p>The problem is that in some cases soft delete can leak unwanted data into reports, new endpoints or even refactored queries. To prevent this from happening, we can use the in-memory database and simulate a search with and without soft-delete:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
 <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">SoftDeleted_Orders_Should_Not_Appear_In_Default_Queries</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
 <span class="token punctuation">{</span>
     <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>
         <span class="token keyword">new</span> <span class="token class-name">Order</span>
         <span class="token punctuation">{</span>
             Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
             TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
             CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
             IsDeleted <span class="token operator">=</span> <span class="token keyword">false</span>
         <span class="token punctuation">}</span><span class="token punctuation">,</span>
         <span class="token keyword">new</span> <span class="token class-name">Order</span>
         <span class="token punctuation">{</span>
             Number <span class="token operator">=</span> <span class="token string">"ORD-002"</span><span class="token punctuation">,</span>
             TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
             CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
             IsDeleted <span class="token operator">=</span> <span class="token keyword">true</span>
         <span class="token punctuation">}</span>
     <span class="token punctuation">)</span><span class="token punctuation">;</span>

     <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

     Assert<span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span>orders<span class="token punctuation">)</span><span class="token punctuation">;</span>
     Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"ORD-001"</span><span class="token punctuation">,</span> orders<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span>
 <span class="token punctuation">}</span>
</code></pre><p>In this test, the list returns only one item because the second one is marked as deleted. This happens because we declared the query filter <code>entity.HasQueryFilter(o =&gt; !o.IsDeleted);</code> when the database is created. By using an in-memory database, we can verify this mechanism works.</p><p>It&rsquo;s also possible to ignore the query filter and still verify it works:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Admin_Query_Should_See_SoftDeleted_Orders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>
        <span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
            IsDeleted <span class="token operator">=</span> <span class="token keyword">false</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
        <span class="token keyword">new</span> <span class="token class-name">Order</span>
        <span class="token punctuation">{</span>
            Number <span class="token operator">=</span> <span class="token string">"ORD-002"</span><span class="token punctuation">,</span>
            TotalAmount <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span>
            CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">,</span>
            IsDeleted <span class="token operator">=</span> <span class="token keyword">true</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">IgnoreQueryFilters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> orders<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="testing-rollback">Testing Rollback</h3><p>When performing multiple operations, we run the risk of something failing during the process and the data becoming inconsistent. To keep this from happening, we can use the in-memory database to test if the rollback operation solves this problem.</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Transaction_Should_Rollback_When_Error_Occurs</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">using</span> <span class="token keyword">var</span> transaction <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Database<span class="token punctuation">.</span><span class="token function">BeginTransactionAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-NMBR-1"</span><span class="token punctuation">,</span>
        TotalAmount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">,</span>
        CreatedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">await</span> transaction<span class="token punctuation">.</span><span class="token function">RollbackAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> count <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders<span class="token punctuation">.</span><span class="token function">CountAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> count<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In this test, we added an item to the order list, saved it and then performed a rollback. Finally, we verified that the list was empty, checking that the operation was undone.</p><h3 id="testing-query-refactoring">Testing Query Refactoring</h3><p>Refactoring queries is often necessary, whether to improve performance or simply to make the code cleaner. The problem is that this can break queries that previously worked.</p><p>To see that both the old and new queries have the same result, we can use an in-memory database as demonstrated in the example below:</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Refactored_Query_Should_Return_Same_Result</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> oldResult <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;</span> <span class="token number">100</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> newResult <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> o<span class="token punctuation">.</span>TotalAmount <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;</span> <span class="token number">100</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span>oldResult<span class="token punctuation">,</span> newResult<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>In this example, we verify that both queries have the same result, so even after refactoring, the old logic will continue to work.</p><h3 id="query-test-with-projection-to-dto">Query Test with Projection to DTO</h3><p>When using Data Transfer Objects (DTOs) for data transport, we are subject to errors that can silently break our queries. Again, with an in-memory database, we can simulate a query using a DTO.</p><pre class=" language-csharp"><code class="prism  language-csharp">   <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">Projection_To_Dto_Should_Work</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">using</span> <span class="token keyword">var</span> context <span class="token operator">=</span> SqliteInMemoryFactory<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">var</span> result <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Orders
            <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">new</span> <span class="token class-name">OrderSummaryDto</span>
            <span class="token punctuation">{</span>
                Number <span class="token operator">=</span> o<span class="token punctuation">.</span>Number<span class="token punctuation">,</span>
                Total <span class="token operator">=</span> o<span class="token punctuation">.</span>TotalAmount
            <span class="token punctuation">}</span><span class="token punctuation">)</span>
            <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        Assert<span class="token punctuation">.</span><span class="token function">NotNull</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
</code></pre><p>Note that through this test, we can be sure that our DTOs will function correctly.</p><h2 id="not-all-tests-need-a-database">Not All Tests Need a Database</h2><p>Until now, we&rsquo;ve used in-memory SQLite to validate constraints (UNIQUE), global filters (Soft Delete), real pagination, GroupBy, Sum, transactions and rollback.</p><p>Tests like these depend on database behavior, but it&rsquo;s common to encounter scenarios where the database isn&rsquo;t present, and only business rules are involved. In this case, to facilitate our work and validate our tests, we can use tools like <a target="_blank" href="https://www.telerik.com/products/mocking.aspx">JustMock</a> and simulate a production-facing environment.</p><p>Next, we&rsquo;ll look at some examples of business rule tests where we can use JustMock to easily create mocks for our tests.</p><h3 id="verifying-the-repository-will-be-called">Verifying the Repository Will Be Called</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">CreateAsync_Should_Call_Repository_AddAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span>
    <span class="token punctuation">{</span>
        Id <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span>
        Number <span class="token operator">=</span> <span class="token string">"ORD-001"</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>

    Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span>Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">MustBeCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">await</span> service<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    Mock<span class="token punctuation">.</span><span class="token function">Assert</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="verifying-the-gethighvalueorders-method-returns-only-orders-above-the-minimum-value">Verifying the GetHighValueOrders Method Returns Only Orders Above the Minimum Value</h3><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">GetHighValueOrders_Should_Return_Only_Orders_Above_MinValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token comment">// Arrange</span>
    <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> orders <span class="token operator">=</span> <span class="token function">FakeOrders</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=</span><span class="token operator">&gt;</span> o<span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;=</span> <span class="token number">150</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>o<span class="token punctuation">.</span>IsDeleted<span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span>orders<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Act</span>
    <span class="token keyword">var</span> result <span class="token operator">=</span> service<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Assert</span>
    Assert<span class="token punctuation">.</span><span class="token function">AreEqual</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>Count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    Assert<span class="token punctuation">.</span><span class="token function">IsTrue</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>TotalAmount <span class="token operator">&gt;=</span> <span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><h3 id="verifying-the-service-forwards-exactly-the-filter-received">Verifying the Service Forwards Exactly the Filter Received</h3><pre class=" language-csharp"><code class="prism  language-csharp">       <span class="token punctuation">[</span>Fact<span class="token punctuation">]</span>
        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">GetHighValueOrders_Should_Call_Repository_With_Same_MinValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// Arrange</span>
            <span class="token keyword">var</span> repository <span class="token operator">=</span> Mock<span class="token punctuation">.</span><span class="token generic-method function">Create<span class="token punctuation">&lt;</span>IOrderRepository<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            Mock<span class="token punctuation">.</span><span class="token function">Arrange</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=</span><span class="token operator">&gt;</span> repository<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">Returns</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">List</span><span class="token operator">&lt;</span>Order<span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">MustBeCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token keyword">var</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">OrderService</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Act</span>
            service<span class="token punctuation">.</span><span class="token function">GetHighValueOrders</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token comment">// Assert</span>
            Mock<span class="token punctuation">.</span><span class="token function">Assert</span><span class="token punctuation">(</span>repository<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
</code></pre><p>If you run all the tests, you can verify that they all passed:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/all-tests-passed.png?sfvrsn=60e45a83_2" title="all tests passed" alt="All tests passed" /></p><h2 id="conclusion">Conclusion</h2><p>Testing web applications is common in software development, but these tests don&rsquo;t always guarantee that errors won&rsquo;t occur in the production environment. Using in-memory databases, such as in-memory SQLite, allows you to reproduce scenarios closer to reality.</p><p>In this post, we saw how this approach helps identify common errors, such as persistence, pagination and projection issues. Furthermore, we explored how JustMock can help save time when validating business rules. I hope the content covered here helps you further improve your applications, making them more reliable through even more realistic testing.</p><hr /><p><a href="https://www.telerik.com/try/justmock" target="_blank">Try JustMock</a> free for 30 days, or <a href="https://www.telerik.com/try/devcraft-ultimate" target="_blank">try out the whole Telerik DevCraft</a> suite for access to the ASP.NET Core component library and lots more (also a free 30-day trial).</p><img src="https://feeds.telerik.com/link/10827/17335641.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:2c9543f6-911a-443f-8612-e1d7e336574b</id>
    <title type="text">Routing Management and Creating NotFound Pages in Blazor</title>
    <summary type="text">Route handling got better in .NET 10. Let’s see how to create context for managing information on an error page and show users specific error pages.</summary>
    <published>2026-05-05T15:57:00Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Héctor Pérez </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17334896/routing-management-creating-notfound-pages-blazor"/>
    <content type="text"><![CDATA[<p><span class="featured">Route handling got better in .NET 10. Let&rsquo;s see how to create context for managing information on an error page and show users specific error pages.</span></p><p>If you have worked with Blazor in versions prior to .NET 10, you have certainly encountered some difficulties in implementing not found pages and in handling NavLink to show users the page they are currently on.</p><p>This is why, during the last update to .NET 10, the team behind Blazor has significantly improved the handling of routes and the management of not found pages. In this article, we will analyze the most important aspects. Let&rsquo;s get started!</p><h2 id="understanding-the-handling-of-not-found-routes-in-blazor">Understanding the Handling of Not Found Routes in Blazor</h2><p>When we talk about routing, we refer to the mechanism that allows the user to navigate to a specific component based on the requested URL. However, there may be times when there are non-existent URLs that the user attempts to navigate to, such as when an item has been deleted or when, for some reason, a page has been removed from the site.</p><p>Properly handling these scenarios is essential for a good user experience, good SEO and, in general, having a reliable web application.</p><h2 id="the-problem-with-not-found-pages-before-.net-10">The Problem with Not Found Pages Before .NET 10</h2><p>You should know that before .NET 10, as part of the <code>Router</code> component, there was a render fragment called <code>NotFound</code>, which at first glance could lead us to think that it allowed defining graphical code to show content when a route was not found:</p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Router</span> <span class="token attr-name">AppAssembly</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Program).Assembly<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Found</span> <span class="token attr-name">Context</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>routeData<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        ...
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Found</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>NotFound</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>LayoutView</span> <span class="token attr-name">Layout</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Layout.MainLayout)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Page not found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Sorry, but there is nothing here!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>LayoutView</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>NotFound</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Router</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In practice, something different occurred, as when attempting to navigate to a non-existent page, an error page was displayed instead of the defined graphical code:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/net9-render-fragment-notfound-logic.gif?sfvrsn=d61310e6_2" alt="Execution flow of &#39;NotFound&#39; RenderFragment in .NET 9 routing" /></p><p>Another technique that was used before .NET 10 was to handle the not found page code within the components. For example, there could be an ecommerce-type application with a products page and a product details page. On the product details page, if the user tried to access a non-existent page, it was handled similarly to the following way:</p><pre class=" language-xml"><code class="prism  language-xml">@if (product == null)
{    
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert alert-warning text-center<span class="token punctuation">"</span></span> <span class="token attr-name">role</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h4</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>alert-heading<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Product Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h4</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>The product with ID <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>@Id<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> does not exist in our catalog.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>hr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-outline-primary<span class="token punctuation">"</span></span> <span class="token attr-name">@onclick</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>GoBack<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token entity" title="←">&amp;larr;</span> Back to Products
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
}
else
{
    ...
}
</code></pre><p>The execution of the previous code resulted in the following appearance:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/handling-notfound-errors-net9.png?sfvrsn=8f3d31f2_2" alt="Handling Not Found errors in .NET 9" /></p><p>The previous code, although it achieved the goal of showing the user that the product did not exist, had several problems:</p><ul><li>It returned an HTTP 200 code, not a 404.</li><li>Each component had to implement its own logic.</li><li>If there was navigation to a non-existent URL (not handled by a component), a site error page was shown.</li></ul><p>Next, let&rsquo;s see how .NET 10 improves the handling of not found pages.</p><h2 id="features-in-.net-10-for-route-management">Features in .NET 10 for Route Management</h2><p>In .NET 10, things have changed regarding route management. Let&rsquo;s analyze what these new features are.</p><h3 id="the-router.notfoundpage-parameter">The Router.NotFoundPage Parameter</h3><p>One of the most prominent updates is the addition of the <code>NotFoundPage</code> parameter to the <code>Router</code> component, which allows specifying a Razor page component that will be displayed when a route is not found. By default, in Blazor projects starting from .NET 10, in the <code>Pages</code> folder we find a component called <code>NotFound.razor</code>, whose structure is as follows:</p><p><strong>NotFound.razor</strong></p><pre class=" language-xml"><code class="prism  language-xml">@page "/not-found"
@layout MainLayout

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">&gt;</span></span>Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Sorry, the content you are looking for does not exist.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
</code></pre><p><strong>Routes.razor</strong></p><pre class=" language-xml"><code class="prism  language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Router</span> <span class="token attr-name">AppAssembly</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Program).Assembly<span class="token punctuation">"</span></span> <span class="token attr-name">NotFoundPage</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>typeof(Pages.NotFound)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
   ...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Router</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>The preview of this code gives the following result:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/default-notfoundpage-component-net10.png?sfvrsn=8d90d029_2" alt="Default NotFoundPage component behavior in .NET 10" /></p><p>In the code above, we can see that the 404 error page must include the <code>@page</code> directive for it to function correctly.</p><h3 id="customizing-the-notfound-component">Customizing the NotFound Component</h3><p>With the <code>NotFound.razor</code> component showing us how to implement a 404 page in our Blazor apps, the next step is to customize it. There are several ways to do this. For example, by adding support for contextual messages through a service class called <code>NotFoundContext</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NotFoundContext</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token operator">?</span> Heading <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token keyword">public</span> <span class="token keyword">string</span><span class="token operator">?</span> Message <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">UpdateContext</span><span class="token punctuation">(</span><span class="token keyword">string</span> heading<span class="token punctuation">,</span> <span class="token keyword">string</span> message<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Heading <span class="token operator">=</span> heading<span class="token punctuation">;</span>
        Message <span class="token operator">=</span> message<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>This class will allow you to show a custom heading and message based on the operation the user has requested. For it to work correctly, you need to register the service in <code>Program.cs</code>:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">var</span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method function">AddScoped<span class="token punctuation">&lt;</span>NotFoundContext<span class="token punctuation">&gt;</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
<span class="token keyword">var</span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre><p>Now, let&rsquo;s give a bit more design to the <code>NotFound.razor</code> template:</p><pre class=" language-xml"><code class="prism  language-xml">@page "/not-found"
@layout MainLayout
@using NotFoundManagementNET102.Services
@inject NotFoundContext NotFoundContext

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>PageTitle</span><span class="token punctuation">&gt;</span></span>Not Found<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>PageTitle</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>container mt-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>row justify-content-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>col-md-8 col-lg-6<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card shadow-sm border-0 text-center<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-body p-5<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mb-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#6c757d<span class="token punctuation">"</span></span>
                             <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>bi bi-exclamation-triangle<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>0 0 16 16<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016...<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-title text-dark mb-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        @(NotFoundContext.Heading ?? "Page Not Found")
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card-text text-muted mb-4<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        @(NotFoundContext.Message ?? "Sorry, the content you are looking for does not exist.")
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>d-flex justify-content-center gap-2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/products<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-primary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            <span class="token entity" title="←">&amp;larr;</span> Back to Products
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>btn btn-outline-secondary<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
                            Go Home
                        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>
                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>In the code above, you can notice that it checks whether the user sets a value for <code>Heading</code> and <code>Message</code>, in order to display either a generic message or a personalized one. Let&rsquo;s see next how to display custom pages based on the user&rsquo;s context.</p><h3 id="handling-not-found-pages-with-navigationmanager.notfound-and-onnotfound">Handling Not Found Pages with NavigationManager.NotFound() and OnNotFound</h3><p>In .NET 9, <code>NavigationManager.NotFound()</code> and <code>OnNotFound</code> were introduced as part of the framework improvements for handling not found pages. We can combine both with the <code>NotFoundContext</code> service to display a 404 response and custom messages, through the following code:</p><pre class=" language-csharp"><code class="prism  language-csharp">@page <span class="token string">"/product/{Id:int}"</span>
@<span class="token keyword">using</span> NotFoundManagementNET102<span class="token punctuation">.</span>Models
@<span class="token keyword">using</span> NotFoundManagementNET102<span class="token punctuation">.</span>Services
@implements <span class="token class-name">IDisposable</span>
@inject ProductService ProductService
@inject NavigationManager Navigation
@inject NotFoundContext NotFoundContext

<span class="token operator">&lt;</span>PageTitle<span class="token operator">&gt;</span>@<span class="token punctuation">(</span>product<span class="token operator">?</span><span class="token punctuation">.</span>Name <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">"Product Detail"</span><span class="token punctuation">)</span><span class="token operator">&lt;</span><span class="token operator">/</span>PageTitle<span class="token operator">&gt;</span>

@<span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"container mt-4"</span><span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span>nav aria<span class="token operator">-</span>label<span class="token operator">=</span><span class="token string">"breadcrumb"</span><span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>ol <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>li <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb-item"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span>a href<span class="token operator">=</span><span class="token string">"/products"</span><span class="token operator">&gt;</span>Products<span class="token operator">&lt;</span><span class="token operator">/</span>a<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>li <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"breadcrumb-item active"</span> aria<span class="token operator">-</span>current<span class="token operator">=</span><span class="token string">"page"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Name<span class="token operator">&lt;</span><span class="token operator">/</span>li<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>ol<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>nav<span class="token operator">&gt;</span>

        <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card shadow-sm"</span><span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-body"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"row"</span><span class="token operator">&gt;</span>
                    <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-md-8"</span><span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>h2 <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-title"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Name<span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>span <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"badge bg-secondary mb-3"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Category<span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span>p <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-text text-muted"</span><span class="token operator">&gt;</span>@product<span class="token punctuation">.</span>Description<span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>

                        <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"row mt-4"</span><span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-6"</span><span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>h5<span class="token operator">&gt;</span>Price<span class="token operator">&lt;</span><span class="token operator">/</span>h5<span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>p <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"fs-4 text-primary fw-bold"</span><span class="token operator">&gt;</span>$@product<span class="token punctuation">.</span>Price<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token string">"N2"</span><span class="token punctuation">)</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"col-6"</span><span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>h5<span class="token operator">&gt;</span>Stock<span class="token operator">&lt;</span><span class="token operator">/</span>h5<span class="token operator">&gt;</span>
                                <span class="token operator">&lt;</span>span <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"badge @(product.Stock &gt; 30 ? "</span>bg<span class="token operator">-</span>success<span class="token string">" : product.Stock &gt; 10 ? "</span>bg<span class="token operator">-</span>warning<span class="token string">" : "</span>bg<span class="token operator">-</span>danger<span class="token string">") fs-6"</span><span class="token operator">&gt;</span>
                                    @product<span class="token punctuation">.</span>Stock units
                                <span class="token operator">&lt;</span><span class="token operator">/</span>span<span class="token operator">&gt;</span>
                            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                        <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span>div <span class="token keyword">class</span><span class="token operator">=</span><span class="token string">"card-footer"</span><span class="token operator">&gt;</span>
                <span class="token operator">&lt;</span>TelerikButton OnClick<span class="token operator">=</span><span class="token string">"GoBack"</span>
                               ThemeColor<span class="token operator">=</span><span class="token string">"@ThemeConstants.Button.ThemeColor.Primary"</span><span class="token operator">&gt;</span>
                    <span class="token operator">&amp;</span>larr<span class="token punctuation">;</span> Back to Products
                <span class="token operator">&lt;</span><span class="token operator">/</span>TelerikButton<span class="token operator">&gt;</span>
            <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
        <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span>
<span class="token punctuation">}</span>

@code <span class="token punctuation">{</span>
    <span class="token punctuation">[</span>Parameter<span class="token punctuation">]</span>
    <span class="token keyword">public</span> <span class="token keyword">int</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>

    <span class="token keyword">private</span> Product<span class="token operator">?</span> product<span class="token punctuation">;</span>

    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">void</span> <span class="token function">OnInitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 1.</span>
        Navigation<span class="token punctuation">.</span>OnNotFound <span class="token operator">+</span><span class="token operator">=</span> HandleNotFound<span class="token punctuation">;</span>

        <span class="token comment">// 2.</span>
        product <span class="token operator">=</span> ProductService<span class="token punctuation">.</span><span class="token function">GetProductById</span><span class="token punctuation">(</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token comment">// 3.            </span>
            Navigation<span class="token punctuation">.</span><span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">HandleNotFound</span><span class="token punctuation">(</span><span class="token keyword">object</span><span class="token operator">?</span> sender<span class="token punctuation">,</span> NotFoundEventArgs e<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 4.</span>
        NotFoundContext<span class="token punctuation">.</span><span class="token function">UpdateContext</span><span class="token punctuation">(</span>
            <span class="token string">"Product Not Found"</span><span class="token punctuation">,</span>
            $<span class="token string">"The product with ID {Id} does not exist in our catalog."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">GoBack</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        Navigation<span class="token punctuation">.</span><span class="token function">NavigateTo</span><span class="token punctuation">(</span><span class="token string">"/products"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 5.</span>
        Navigation<span class="token punctuation">.</span>OnNotFound <span class="token operator">-</span><span class="token operator">=</span> HandleNotFound<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Let&rsquo;s analyze in more detail what happens in the code above:</p><ol><li>We subscribe to <code>Navigation.OnNotFound</code> to monitor when a product is not found.</li><li>The product is searched by its ID.</li><li>If the product does not exist, <code>Navigation.NotFound</code> is invoked, allowing the rendering of the component with an HTTP 404 code.</li><li>In the <code>HandleNotFound</code> handler, we set a custom title and message.</li><li>We unsubscribe from the event to avoid memory leaks.</li></ol><p>With the previous implementation, if we navigate to a nonexistent URL such as https://localhost:7038/not-found-page, a generic message will be displayed:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/generic-message-custom-component.png?sfvrsn=1642c57c_2" alt="Generic message displayed by the custom component" /></p><p>However, when trying to access the URL of a nonexistent product such as https://localhost:7038/product/122, we will find a custom message:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/custom-message-custom-component.png?sfvrsn=27ee4062_2" alt="Custom message rendered through a personalized component" /></p><p>With the above, we have confirmed that it is now much easier to handle not found routes.</p><h3 id="improvements-in-url-detection-using-navlinkmatch.all">Improvements in URL Detection using NavLinkMatch.All</h3><p>Before .NET 10, when using <code>Match="NavLinkMatch.All"</code>, Blazor was very strict in detecting links. For example, suppose you had something like this in your navigation menu:</p><p><code>&lt;NavLink href="https://www.telerik.comproducts" Match="NavLinkMatch.All"&gt;Products&lt;/NavLink&gt;</code></p><p>If you visited the <code>/products</code> page, the UI would mark an active link in the menu. However, if you navigated to a different URL such as <code>/products?price=cheap</code> or <code>/products#offers</code>, the UI would mark the section as inactive, leading to a poor user experience. This behavior is currently happening in our application:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/navigation-url-change-focus-loss.gif?sfvrsn=5b397a86_2" alt="Focus loss during navigation URL change" /></p><p>In the image above, you can see how the focus is lost when navigating to a product details page. This happens because the main URL is <code>http://localhost:5084/products</code>, while the product URL looks like this: <code>http://localhost:5084/product/2</code>.</p><p>Fortunately, in .NET 10 we can inherit from <code>NavLink</code> and write custom logic to handle matching the URLs we need:</p><pre class=" language-csharp"><code class="prism  language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomNavLink</span> <span class="token punctuation">:</span> NavLink
<span class="token punctuation">{</span>
    <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">bool</span> <span class="token function">ShouldMatch</span><span class="token punctuation">(</span><span class="token keyword">string</span> currentUriAbsolute<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token comment">// 1.</span>
        <span class="token keyword">var</span> baseUri <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>NavigationManagerBase<span class="token punctuation">.</span>BaseUri<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> currentUri <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> relativePath <span class="token operator">=</span> currentUri<span class="token punctuation">.</span>AbsolutePath<span class="token punctuation">.</span><span class="token function">TrimStart</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLowerInvariant</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 2.</span>
        <span class="token keyword">var</span> href <span class="token operator">=</span> AdditionalAttributes <span class="token operator">!=</span> <span class="token keyword">null</span>
            <span class="token operator">&amp;&amp;</span> AdditionalAttributes<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span><span class="token string">"href"</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> hrefValue<span class="token punctuation">)</span>
            <span class="token operator">?</span> hrefValue<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">TrimStart</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLowerInvariant</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
            <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>href<span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">ShouldMatch</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 3.</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>href <span class="token operator">==</span> <span class="token string">"products"</span><span class="token punctuation">)</span>
        <span class="token punctuation">{</span>
            <span class="token keyword">return</span> relativePath <span class="token operator">==</span> <span class="token string">"products"</span>
                <span class="token operator">||</span> relativePath<span class="token punctuation">.</span><span class="token function">StartsWith</span><span class="token punctuation">(</span><span class="token string">"product/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword">return</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">ShouldMatch</span><span class="token punctuation">(</span>currentUriAbsolute<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token punctuation">[</span>Inject<span class="token punctuation">]</span>
    <span class="token keyword">private</span> NavigationManager NavigationManagerBase <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre><p>In the code above:</p><ol><li>We obtain the relative path from the absolute URI.</li><li>We get the href.</li><li>We perform a custom match so that <code>NavLink</code> is active both in <code>/products</code> and <code>/product/{id}</code>.</li></ol><p>For the previous implementation to work in the project, we need to swap <code>NavLink</code> for <code>CustomNavLink</code> in <code>NavMenu.razor</code>:</p><pre class=" language-xml"><code class="prism  language-xml">...
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>nav-item px-3<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>CustomNavLink</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>nav-link<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>products<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>bi bi-bag-fill-nav-menu<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> Products
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>CustomNavLink</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
...
</code></pre><p>The result of the implementation is that the user knows at all times where they are, thanks to the implementation of a custom <code>NavLink</code>:</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/custom-navlink-logic.gif?sfvrsn=7067cad3_2" alt="Implementing custom logic for the NavLink component" /></p><h2 id="conclusion">Conclusion</h2><p>Throughout this article, you have learned about some new features in .NET 10 that are particularly useful for handling routes.</p><p>This includes creating a context for managing information on an error page and how to use this context to show users specific error pages, in addition to an inheritance of NavLink to define custom rules for better navigation.</p><p>Undoubtedly, these new features were much needed in Blazor. Now it&rsquo;s your time to use them and create better user experiences utilizing them alongside Progress Telerik <a target="_blank" href="https://www.telerik.com/blazor-ui">components for Blazor</a>.</p><img src="https://feeds.telerik.com/link/10827/17334896.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:354321ce-fa28-42ad-be8e-5ef8fe72a13c</id>
    <title type="text">Loading UI/UX Patterns for AI Applications</title>
    <summary type="text">Give users appropriate loading feedback for the AI process going on. Here are some patterns aligned to the wait time, with implementation ideas for Blazor.</summary>
    <published>2026-04-28T16:39:10Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Ed Charbeneau </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17327238/loading-ui-ux-patterns-ai-applications"/>
    <content type="text"><![CDATA[<p><span class="featured">Give users appropriate loading feedback for the AI process going on. Here are some patterns aligned to the wait time, with implementation ideas for Blazor.</span></p><p>AI-driven applications introduce a new set of challenges for user experience design. Traditional web applications operate within predictable boundaries, where requests complete in a consistent and measurable way. AI features change that dynamic, with response times ranging from near-instant to long-running operations that can take minutes. Designing for that variability is no longer optional, it is a core part of building reliable AI-powered applications.</p><p>The challenge extends beyond implementation into perception. Users do not experience time objectively; they react to how it feels. A blank screen creates doubt, while visible progress keeps the experience grounded. When feedback aligns with the effort required, the interaction feels natural. When it does not, friction builds quickly. Thoughtful loading design addresses this gap by communicating progress, setting expectations and reinforcing the value of the task in motion.</p><p>This guide focuses on practical loading UI patterns for AI scenarios, organized by real-world wait times. Each pattern is implemented with <a target="_blank" href="https://www.telerik.com/blazor-ui">Blazor and Progress Telerik UI components</a>, keeping the discussion grounded in tools developers already use. The goal is simple: create loading experiences that do more than fill time, improving perceived performance and building confidence in the system behind the scenes.
</p><h2>Understanding AI Application Loading Patterns</h2><p>AI applications introduce a different class of loading behavior. Traditional web requests are relatively predictable, but AI workloads are not. Inference, natural language processing and computer vision all introduce variability based on model complexity, input size and system load. As powerful as these capabilities are, they require a more deliberate approach to how progress is communicated.</p><p>Effective loading experiences center on managing expectations through clear, continuous feedback. Every loading state should communicate what is happening now, what has completed and what comes next, keeping users oriented as the system works. With this foundation in place, loading patterns can be tailored to specific wait times, keeping the experience responsive and understandable regardless of how long processing takes.</p><h2>Near Instantaneous Responses (Less Than 1 Second)</h2><p>For interactions under one second, traditional loading indicators often add more friction than value. Quick flashes of spinners or progress bars create a visual glitch that feels broken rather than helpful. Instead, the interface should respond immediately with subtle visual feedback that confirms the action without interrupting flow.</p><p>One exception stands out: when a fast action represents the final step in a longer workflow. In these cases, introducing a brief, intentional delay can create a sense of completion, giving users a moment to recognize the result of their effort before moving on.</p><h3>AI Task Examples</h3><p>AI tasks that typically complete in under one second include:</p><ul><li>Real-time text suggestions and autocomplete</li><li>Simple sentiment analysis on short text</li><li>Basic image classification with lightweight models</li><li>Cached AI responses</li><li>Rule-based chatbot responses</li><li>Simple recommendation filtering</li></ul><h3>Blazor Implementation Strategy</h3><p>Implementing instantaneous feedback in Blazor requires careful coordination between state changes and visual cues. The goal is to reflect user actions immediately while avoiding flicker from overly brief loading states. In practice, this often means enforcing a minimal display duration for indicators so that interactions feel smooth and intentional. </p><p>Try the A/B samples below by clicking the &ldquo;Zoom and Enhance&rdquo; buttons twice. The A sample should feel smoother on short intervals because of the intentional delay.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/mAkScmvQ58rIAkuM38?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Instant interactions benefit from confirmation rather than explanation. Subtle cues like button state changes, input highlighting or lightweight animations signal that the system has responded, keeping the experience fluid and uninterrupted. When these signals are consistent and immediate, users stay oriented without feeling like they are waiting.</p><h2>Short Wait Times (1&ndash;3 Seconds)</h2><p>Short waits call for lightweight, indeterminate indicators that acknowledge progress without interrupting flow. Simple spinners or skeleton screens work well because they provide immediate feedback while keeping the interface responsive. More elaborate animations tend to work against this, as users do not have enough time to process them and the interaction can feel slower than it is.</p><p>Skeleton screens are especially effective in AI scenarios because they establish structure upfront. By rendering a placeholder version of the final layout, the interface maintains continuity and gives users a clear sense of what is coming next as content is generated.</p><h3>AI Task Examples</h3><p>AI operations in the 1&ndash;3 second range include:</p><ul><li>Text generation for short responses</li><li>Image enhancement or basic filtering</li><li>Summarizing a document of moderate length</li><li>Translation of short to medium text</li><li>Basic data analysis and insights generation</li><li>Voice-to-text conversion for brief audio</li></ul><h3>Blazor Implementation Strategy</h3><h4>Chat Progress and Thinking</h4><p>The Telerik UI for&nbsp;<a href="https://www.telerik.com/blazor-ui/skeleton" target="_blank">Blazor Skeleton</a> component provides a straightforward way to mirror final layouts while AI-generated content is loading and the agent is thinking.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/wqkymwFI34Q3Bj4H27?editor=true&amp;result=true&amp;errorList=false"></iframe><h4>Smart Paste Loading</h4><p>The following demo shows a <a href="https://www.telerik.com/blazor-ui/smartpastebutton" target="_blank">smart paste interaction</a> that incorporates loading indicators to signal the process taking place. The Loading Container is displayed over the form fields being populated, while a button loader indicates the action is taking place once the button is clicked.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/mquemabj125i6dVw38?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Short waits benefit from clarity without added friction. Contextual messaging that reflects the task in progress, such as &ldquo;Thinking&rdquo; or &ldquo;Generating recommendations,&rdquo; helps users understand what is happening without slowing the experience down. When combined with progressive disclosure, where partial results are rendered as they become available, the interface stays active and responsive, reducing perceived wait time while making the system&rsquo;s work visible.</p><h2>Medium Wait Times (3&ndash;10 Seconds)</h2><p>Users begin to question responsiveness, and a lack of visible progress quickly turns into doubt. Multi-agent workflows or agentic workflows with multiple indeterminate steps can be difficult to navigate. Agents can make requests to external tools that have asynchronous processes. While these processes run, it is important to report the activity in a manageable way without losing focus of the main content.</p><h3>Blazor Implementation</h3><p>Provide feedback for AI operations that expose measurable progress. The example below shows a chat process where the agent uses external tools. Loading indicators are exposed in a collapsible panel to allow the user to see the progress but also hide it when the details are no longer necessary.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/QqEoGwvA09jzuqBe29?editor=true&amp;result=true&amp;errorList=false"></iframe><h3>UX Strategy</h3><p>Medium waits benefit from a balance of clarity and momentum. Directional time estimates, even something as simple as &ldquo;This may take about a minute,&rdquo; help users decide whether to stay engaged or return later, while continuously moving progress indicators reinforce that the system is still working. When paired with contextual messaging that explains the current step or highlights what is being processed, the experience shifts from passive waiting to a guided interaction that keeps users oriented and informed.</p><h2>Extended Wait Times (10+ Seconds)</h2><p>Extended wait times require a different approach. Clear visibility into progress matters, but so does the freedom to move on without losing track of what is happening. Combining progress indicators with background processing patterns allows the system to stay transparent while keeping the rest of the application usable.</p><p>Percent-complete indicators play a central role here by giving users a sense of scale. Seeing measurable progress helps them judge how much work remains and whether it is worth continuing to wait. At the same time, providing cancel or abort options keeps users in control rather than locked into a long-running process.</p><p>Step-based indicators work especially well for multi-stage AI operations by breaking the process into clear, understandable phases. Even when exact timing is uncertain, each completed step provides direction and reinforces forward movement.</p><h3>Blazor Implementation</h3><p>The following example uses a <a href="https://www.telerik.com/blazor-ui/grid" target="_blank">grid</a> format to display and manage agent activity. Each element displays important information about the task&rsquo;s state. In addition, failure modes are included and made actionable from the interface. This type of UX is great for dashboard-like scenarios where users can launch agent workflows and return later to see the task completion status.</p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/QKOSGwlH59MIvvBw27?editor=true&amp;result=true&amp;errorList=false"></iframe><p>If the user may encounter a long and undetermined loading state, a looping indicator can be used to communicate the overall steps that are running in the background, without labeling exactly what stage the process is at now. This nondeterministic progress indicator displays a carousel of information while the user waits. </p><p>With agentic applications, long processes can give the impression of wasted time, even though a process that takes minutes for an agent may have taken a human hour to complete manually. This loading pattern reminds users of the work going on behind the scenes.&nbsp;While some consider this a &ldquo;dark pattern&rdquo; when used to obscure the process details, it can be quite useful when used correctly to share honest information about the value the process provides.</p><p><iframe width="100%" height="500px" src="https://blazorrepl.telerik.com/repl/embed/GKkImcbe33AOXWRF16?editor=true&amp;result=true&amp;errorList=false">&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;nbsp;</iframe></p><p>Extended waits benefit from a design that prioritizes flexibility and user control. Background task patterns, such as persistent status panels or drawers, allow users to continue working while keeping progress visible, reducing the friction of being forced to wait in place. When paired with notification systems that signal completion, the need to actively monitor progress is removed entirely, giving users confidence that the system will follow through.</p><p>Longer waits create space to surface meaningful insights, explain processing steps or provide guidance for improving future results. When these elements are combined effectively, the experience shifts from a blocking delay to a managed, transparent process that keeps users informed without interrupting their workflow.</p><h2>Advanced Patterns for AI Applications</h2><h3>Contextual Loading Messages</h3><p>AI applications benefit from contextual loading messages that explain current processing steps. Replace generic &ldquo;Loading...&rdquo; text with specific descriptions: &ldquo;Analyzing image composition,&rdquo; &ldquo;Generating creative variations&rdquo; or &ldquo;Cross-referencing knowledge base.&rdquo; These messages build user understanding of AI capabilities while setting realistic expectations for output quality.</p><h3>Streaming and Incremental Results</h3><p>Many modern AI applications support streaming responses, particularly for text generation. Implement progressive disclosure patterns that display AI outputs as they generate, reducing perceived wait time while demonstrating active processing. This approach works particularly well for conversational AI interfaces and content-generation tools.</p><h2>Summary</h2><p>Designing loading experiences for AI applications is no longer about handling delays. It is about shaping how users understand and interact with the system. When feedback aligns with the work being performed, users stay engaged and in control regardless of how long processing takes.</p><p>By matching loading strategies to expected wait times, developers can move beyond generic indicators and deliver feedback that is intentional and informative. From immediate visual confirmation to multi-stage progress tracking, each pattern helps make AI interactions feel predictable and responsive.</p><p>Blazor and Telerik UI components provide a practical foundation for implementing these patterns, enabling teams to build consistent loading experiences without introducing unnecessary complexity. The advantage comes from how these tools are applied, not just their availability.</p><p>As AI continues to shape modern applications, loading design becomes a defining part of the user experience. Applications that communicate clearly during processing will feel faster and more reliable, regardless of the work happening behind the scenes.</p><h3>Get Started with These Examples</h3><p>Use any of the Telerik UI for <a target="_blank" href="https://www.telerik.com/blazor-ui">Blazor components</a> shown above, plus the <a target="_blank" href="https://www.telerik.com/blazor-mcp-servers">Blazor MCP servers</a> free with the 30-day trial.</p><p><a href="https://www.telerik.com/try/ui-for-blazor" target="_blank" class="Btn">Try Now</a></p><img src="https://feeds.telerik.com/link/10827/17327238.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:89e0e3fc-f615-4718-b937-f4de1ceba07a</id>
    <title type="text">Introducing the Progress Agentic RAG .NET SDK</title>
    <summary type="text">The .NET SDK for Progress Agentic RAG provides Retrieval-Augmented Generation (RAG) capabilities to .NET development with knowledge base management, AI-powered search and resource operations.</summary>
    <published>2026-04-23T15:33:21Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Ed Charbeneau </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17324229/introducing-progress-agentic-rag-net-sdk"/>
    <content type="text"><![CDATA[<p><span class="featured">The .NET SDK for Progress Agentic RAG provides Retrieval-Augmented Generation (RAG) capabilities to .NET development with knowledge base management, AI-powered search and resource operations.</span></p><h2>What Is Progress Agentic RAG?</h2><p>Progress Agentic RAG is a RAG-as-a-Service that makes it dramatically easier to build AI systems grounded in real, trusted content. Rather than wiring together vector databases, embedding pipelines and retrieval logic yourself, Progress Agentic RAG provides an end-to-end platform for indexing, understanding and retrieving multimodal data.</p><p>It enables intelligent, agent-driven workflows that combine structured knowledge, contextual search and LLM orchestration into a unified experience.</p><p>With the introduction of the .NET SDK for Progress Agentic RAG, .NET developers can integrate this capability directly into their applications with just a few lines of code, leveraging modern .NET architecture patterns such as dependency injection, async workflows and strongly typed APIs.</p><h3>From Retrieval to Agentic Intelligence</h3><p>Progress Agentic RAG offers a fully capable AI Search Dashboard solution that allows you to get started in a matter of minutes. Simply connect or upload your data to a Knowledge Box, wait for NucliaDB&rsquo;s blazing-fast indexing engine to process it, and you&rsquo;re ready to explore grounded, contextual AI responses.</p><p>That immediate productivity is powerful. But modern enterprise development requires more than a dashboard.</p><p>Enterprise environments demand typed APIs, strong tooling and predictable behavior. Many RAG solutions are Python-first, leaving .NET teams stitching together REST calls manually and building custom abstractions just to regain the ergonomics they expect from their platform.</p><p>The new .NET SDK changes that.</p><p>It provides:</p><ul style="margin-top:0in;" type="disc"><li>Strongly typed APIs</li><li>Async-first patterns</li><li>Native .NET integration</li><li>Simplified knowledge base interaction</li></ul><p>With these capabilities, you can move beyond simple retrieval and begin composing intelligent, agent-driven experiences directly inside your application architecture. As powerful as it is convenient, the .NET SDK makes a great choice for new AI-enabled .NET applications.</p><h2>Getting Started</h2><p>Once a Knowledge Box has been established, you can begin interacting with Progress Agentic RAG through the .NET SDK.</p><p>The SDK is distributed as a <a target="_blank" href="https://www.nuget.org/packages/Progress.Nuclia">NuGet package</a> and covers the complete NucliaDB REST API. That includes strongly typed models, structured output helpers, dependency injection extensions and more than 200 APIs that expose the full surface area of the platform. See the <a target="_blank" href="https://docs.rag.progress.cloud/docs/develop/dotnet-sdk/">SDK documentation page </a>for a comprehensive list of service providers available.</p><h3>Install the NuGet Package</h3><pre><code class="language-csharp">dotnet add package Progress.Nuclia</code></pre><p>With the package installed, you can register the INucliaDb interface using modern dependency injection patterns. The SDK supports everything from basic configuration to advanced multi-tenant scenarios using keyed services.</p><h3>Register the Client</h3><pre><code class="language-csharp">using Progress.Nuclia.Extensions;

// Create configuration
var config = new NucliaDbConfig(
    ZoneId: "aws-us-east-2-1",
    KnowledgeBoxId: "your-knowledge-box-id",
    ApiKey: "your-api-key"
);

// Register with logging
builder.Services.AddNucliaDb(config).UseLogging();</code></pre><p>This approach aligns naturally with ASP.NET Core&rsquo;s architecture. You configure once, inject where needed, and keep your AI integration cleanly separated from business logic.</p><h2>Ask Questions with Agentic RAG</h2><p>With configuration complete, you can begin querying your Knowledge Box using AskAsync or AskStreamingAsync.</p><pre><code class="language-csharp">// Make request
AskRequest askRequest = new("What issues are driving the most customer escalations this quarter?");
var response = await client.Search.AskAsync(askRequest);

// Display answer
Console.WriteLine(response.Data.Answer);</code></pre><p>In just a few lines of code, you&rsquo;re executing a grounded, agent-driven query against indexed enterprise data.</p><p>The Ask functionality is only the beginning. With more than 200 APIs available in the SDK, you can ingest and manage resources, create conversational interactions and perform search, all using strongly typed, async-first C# patterns.</p><p>With structured configuration and native .NET integration in place, you can move from experimentation to production-ready AI systems with confidence.</p><h2>Explore the Examples: Blazor and .NET MAUI</h2><p>The fastest way to understand what Progress Agentic RAG can do in a real application is to see it running inside the frameworks you already use.</p><p>We&rsquo;ve published hands-on examples built with:</p><ul style="margin-top:0in;" type="disc"><li><a target="_blank" href="https://github.com/telerik/telerik-blazor-progress-rag-demo">Blazor &mdash; showcasing grounded AI experiences in modern web applications</a></li><li><a target="_blank" href="https://github.com/telerik/telerik-maui-progress-rag-demo">NET MAUI &mdash; demonstrating cross-platform, AI-powered mobile and desktop apps</a></li></ul><p>These samples go beyond simple API calls. They show how to:</p><ul style="margin-top:0in;" type="disc"><li>Register the SDK using dependency injection</li><li>Execute agentic queries with structured output</li><li>Stream responses into interactive UI components</li><li>Keep AI concerns cleanly separated from presentation logic</li></ul><p>If you&rsquo;re building internal tools, customer-facing dashboards or cross-platform AI assistants, these examples provide a production-oriented starting point.</p><p>Clone the samples, wire up your Knowledge Box and see how quickly you can integrate grounded, agent-driven intelligence into your existing .NET architecture.</p><h2>Additional Media</h2><p>Learn more about Progress Agentic RAG and the .NET SDK from video tutorials and podcasts:</p><ul><li>The Progress Agentic RAG .NET SDK was featured on episode 1997 of .NET Rocks:&nbsp;<a target="_blank" href="https://www.dotnetrocks.com/details/1997">.NET Rocks! Agentic RAG with Ed Charbeneau</a></li><li>Getting started videos by Jeff (Csharp) Fritz</li></ul><div sf-youtube-url="https://youtu.be/ilJpB9Y7waA?si=nIm6nTg2HLc0Bvkf"><iframe frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Build Your First AI Search in .NET with Progress Agentic RAG" width="640" height="360" src="https://www.youtube.com/embed/ilJpB9Y7waA?si=nIm6nTg2HLc0Bvkf&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fwww.telerik.com&amp;widgetid=3&amp;forigin=https%3A%2F%2Fwww.telerik.com%2FSitefinity%2Fadminapp%2Fcontent%2Fblogs%2Fc2c9ae54-03a9-4243-86cc-9c50fcc0a754%2Fblogposts%2F89e0e3fc-f615-4718-b937-f4de1ceba07a%2Fedit%3Fsf_provider%3DOpenAccessDataProvider%26sf_culture%3Den&amp;aoriginsup=1&amp;vf=1"></iframe></div><div sf-youtube-url="https://youtu.be/qE6zy75btLo?si=ZferEV00m-sRamDA"><iframe frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Build Your First AI Search in .NET with Progress Agentic RAG Part 2" width="640" height="360" src="https://www.youtube.com/embed/qE6zy75btLo?si=ZferEV00m-sRamDA&amp;enablejsapi=1&amp;origin=https%3A%2F%2Fwww.telerik.com&amp;widgetid=2&amp;forigin=https%3A%2F%2Fwww.telerik.com%2FSitefinity%2Fadminapp%2Fcontent%2Fblogs%2Fc2c9ae54-03a9-4243-86cc-9c50fcc0a754%2Fblogposts%2F89e0e3fc-f615-4718-b937-f4de1ceba07a%2Fedit%3Fsf_provider%3DOpenAccessDataProvider%26sf_culture%3Den&amp;aoriginsup=1&amp;vf=1"></iframe></div><img src="https://feeds.telerik.com/link/10827/17324229.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:e67bd1e0-6dce-4ba4-92de-7c7975a18492</id>
    <title type="text">Blazor Basics: Getting Started with Blazor Development in VS Code</title>
    <summary type="text">Learn how to use Visual Studio Code for Blazor web development. VS Code is a free, lightweight code editor, and it is available for Mac and Linux users (unlike its more robust sibling Visual Studio, which is exclusive to Windows).</summary>
    <published>2026-04-21T16:22:45Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Claudio Bernasconi </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17323030/blazor-basics-getting-started-blazor-development-vs-code"/>
    <content type="text"><![CDATA[<p><span class="featured">Learn how to use Visual Studio Code for Blazor web development. VS Code is a free, lightweight code editor, and it is available for Mac and Linux users (unlike its more robust sibling Visual Studio, which is exclusive to Windows).</span></p><p>While many developers using Windows love the fully integrated development experience Visual Studio provides, it&rsquo;s not an option for some of us.</p><p>We will learn how to set up Visual Studio Code for Blazor web application development, a few first-hand tips and how to conveniently debug.</p><h2 id="why-use-visual-studio-code-for-blazor-web-development">Why Use Visual Studio Code for Blazor Web Development?</h2><p>Maybe you or your team already use <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> for software development because of its excellent code editor or GIT integration. Why learn something new if you are happy with what you already have?</p><p>Another reason that comes to mind is your preferred operating system.</p><p><a target="_blank" href="https://visualstudio.microsoft.com/">Visual Studio</a> is only available for <strong>Windows</strong>. While I am a Windows user and have always loved the fully fledged feature set provided by Visual Studio for .NET development, I understand that there are other options.</p><p>For <strong>macOS</strong> or <strong>Linux</strong>, you do not have the option to use Visual Studio. Visual Studio Code (VS Code) is a great, lightweight, and free alternative.</p><p>Also, licensing can be an issue. While the Visual Studio Community edition has limitations, VS Code is free for .NET development, both personal and commercial.</p><p>Last but not least, VS Code uses less RAM, hard-drive space and CPU compared to fully fledged IDEs, such as Visual Studio. For hardware constraint situations, it&rsquo;s an ideal choice.</p><h2 id="getting-started-the-setup">Getting Started: The Setup</h2><p>We need two essential things before we can start developing Blazor web applications with VS Code.</p><ol><li>Obviously, we need the latest <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> version installed</li><li>We also need the <a target="_blank" href="https://dotnet.microsoft.com/">latest .NET SDK</a> installed</li></ol><p>Make sure you can execute the <code>dotnet --version</code> command in a terminal. If it doesn&rsquo;t show the version of the installed <strong>.NET SDK</strong>, add the <strong>.NET CLI</strong> to the <code>PATH</code> variable of your operating system and restart your computer before trying again.</p><h2 id="installing-the-c-dev-kit">Installing the C# Dev Kit</h2><p>Next, we want to install the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit">C# Dev Kit extension</a> for Visual Studio Code.</p><p><img title="C# Dev Kit Visual Studio Code extension" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/csharp-dev-kit-extension.png?sfvrsn=55b57e85_2" alt="The marketplace page of the C# Dev Kit Visual Studio Code extension." /></p><p>This extension adds C# development features, including project and solution management, debugging, syntax highlighting, code navigation, refactorings and more, to VS Code. It&rsquo;s a mature extension with more than 13 million downloads.</p><blockquote><p><strong>Hint:</strong> The C# Dev Kit extension not only adds support for Blazor web development but for all C# project types, including desktop, mobile, web, or cloud-native applications.</p></blockquote><h2 id="creating-a-blazor-web-app-in-vs-code">Creating a Blazor Web App in VS Code</h2><p>If you open an empty VS Code instance, you&rsquo;ll get the Create .NET Project menu option in the Explorer panel.</p><p><img title="Visual Studio Code: Explorer" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/explorer-create-new-dotnet-project.png?sfvrsn=7487329a_2" alt="The Explorer panel in Visual Studio Code in an empty project shows the Create .NET Project button." /></p><p>Pressing the <strong>Create .NET Project</strong> button will execute a VS Code command that allows you to select the project type. We select the default <strong>Blazor Web App</strong> project template and choose the destination folder on the hard drive.</p><p>Depending on the project template you choose, you&rsquo;ll be able to add additional information to influence the outcome of the created project.</p><p>After a few seconds, the Explorer now shows the project folder containing the created solution file, the project file and the <code>Components</code> folder containing the example pages and layouts.</p><p><img title="VS Code: Explorer" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/visual-studio-code-solution-explorer.png?sfvrsn=72c36ed7_2" alt="The Explorer panel in VS Code in a Blazor Web App shows the folders, such as the Components folder and all the required code files." /></p><blockquote><p><strong>Hint:</strong> We won&rsquo;t explore the structure and architecture of the Blazor Web application.</p></blockquote><p>Another option for creating a .NET project is using the .NET CLI from the terminal. The <code>dotnet new blazor</code> command creates the same project as shown above.</p><h2 id="running-the-blazor-web-application-in-vs-code">Running the Blazor Web Application in VS Code</h2><p>Running a Blazor Web Application is no different from running any other .NET-based application. In a terminal, you can use the <code>dotnet run</code> or <code>dotnet watch run</code> command to run it.</p><p>The <code>dotnet watch run</code> command automatically rebuilds and refreshes the browser (hot reload) when you edit the code and save the file.</p><p><img title="VS Code: Running a Blazor Web App" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/dotnet-watch-run-command.png?sfvrsn=94413184_2" alt="A terminal executing the dotnet watch run command in the folder of a Blazor Web App." /></p><p>The application runs the same whether you run it in Visual Studio or from the command line without VS Code. All three options use the .NET CLI under the hood.</p><p>Compared to Visual Studio, VS Code is noticeably faster at applying simple changes to components, such as the text of the default Home page component.</p><blockquote><p><strong>Tip:</strong> Using hot reload saves a lot of time. Use the <code>dotnet watch run</code> command and open the <code>Counter.razor</code> component. Change the code in the <code>IncrementCount</code> method from <code>currentCount++</code> to <code>currentCount+=2</code> and save the file. Whenever you hit the <strong>Click Me</strong> button, the counter now increases the value by <strong>two</strong> instead of <strong>one</strong>.</p></blockquote><h2 id="debugging-c-code-in-vs-code">Debugging C# Code in VS Code</h2><p>VS Code not only supports running the Blazor web app, but also debugging it.</p><p>To enable debugging:</p><ol><li>Open the <strong>Run and Debug</strong> panel (CTRL+SHIFT+D)</li><li>Click the <strong>Run and Debug</strong> button.</li><li>Select <strong>C#</strong></li><li>Select one of the available project options, or <strong>launch the Startup</strong> project.</li><li>Click on <strong>Start Debugging</strong> (Green Triangle), or press <strong>F5</strong>.</li></ol><p>You can click left of any C# code line to add a breakpoint. A yellow background highlights the code line when the debugger stops the execution. And you can use the toolbar to step through your code and hover over variables to see their values.</p><p><img title="VS Code: Debugging a Blazor Web App" src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/debugging-blazor-web-app.png?sfvrsn=dfa894d3_2" alt="The VS Code debugger stopping at a code line and showing the local variables and the toolbar to control the program execution." /></p><p>The debugging experience is comparable to that of other fully fledged IDEs and allows you to find bugs and understand code execution.</p><p>You can add variables to the watch view to keep their values visible as you step through program execution. You also see the call stack whenever the program execution halts, and you can enable or disable the breakpoints.</p><blockquote><p><strong>Hint:</strong> Make sure the application isn&rsquo;t running from a terminal, such as using the dotnet watch run command.</p></blockquote><h2 id="conclusion">Conclusion</h2><p>While I personally prefer using Visual Studio 2026 for most of my development work, I hopefully was able to have shown that Visual Studio Code also offers a first-class development environment.</p><p>For Linux or macOS, VS Code would definitely be my first choice because it provides most of the tools I regularly use and contains much less bloat than a fully fledged IDE. VS Code is free for personal and commercial use.</p><p>Depending on the scope of your project and how you want to work, VS Code is a good choice to get started with Blazor web development. Especially if you already use VS Code for other projects, such as React or Angular web application development.</p><p>If you want to learn more about Blazor development, watch my <a target="_blank" href="https://www.youtube.com/playlist?list=PLwISgxnkpZGL_LhTQCWwp-WCzupv7lcp0">free Blazor Crash Course</a> on YouTube. And stay tuned to the Telerik blog for more <a target="_blank" href="https://www.telerik.com/blogs/tag/blazor-basics">Blazor Basics</a>.</p><img src="https://feeds.telerik.com/link/10827/17323030.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:00f2f87e-a41a-4c28-8ad3-079bcbc8bc0a</id>
    <title type="text">Angular Grid at Scale: How Kendo UI Handles Millions of Rows</title>
    <summary type="text">Angular Grid at Scale: How Kendo UI Handles Millions of Rows.</summary>
    <published>2026-04-21T16:17:26Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Dany Paredes </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17323031/angular-grid-scale-how-kendo-ui-handles-millions-rows"/>
    <content type="text"><![CDATA[<p>Let&rsquo;s say the Monday after the Super Bowl, your boss wants to see a dashboard list of the more than 120 million people who watched it. (The reality of this situation does not matter. Just go with it!) Your boss says, &ldquo;The dashboard needs to show every connected viewer in real time. A grid. Sortable. Filterable. And it can&rsquo;t be slow.&rdquo;</p><p>You open your code editor, create an HTML table, and try to render one million rows. The browser freezes. The tab crashes. Your Monday just got worse.</p><p>You need to display large amounts of data in a grid without killing the browser. The trick is simple: don&rsquo;t render what the user can&rsquo;t see.</p><p>This is where <strong>virtual scrolling</strong> comes in. Instead of creating a DOM element for every single row, you only render the rows visible on screen, plus a few extra. The user scrolls smoothly through millions of records, each new batch loading in as needed, and the browser stays fast and responsive.</p><p>Let&rsquo;s do it together! Today, we will build a Super Bowl Dashboard in Angular. We will see how to handle one million viewers using Progress Kendo UI for <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/">Angular Grid</a>. We will start with a simple table, see why it fails and then fix it using virtual scrolling and server-side data fetching.</p><p>Let&rsquo;s make this work in a real project.</p><h2 id="setting-up-the-project">Setting Up the Project</h2><p>First, create a new Angular application by running the command:</p><pre class=" language-bash"><code class="prism  language-bash">ng new superbowl-dashboard
</code></pre><p>Navigate to the project:</p><pre class=" language-bash"><code class="prism  language-bash"><span class="token function">cd</span> superbowl-dashboard
</code></pre><p>Now install the Angular Grid. The <code>ng add</code> command handles dependencies and theme configuration for us:</p><pre class=" language-bash"><code class="prism  language-bash">ng add @progress/kendo-angular-grid --skip-confirmation
</code></pre><p>This installs the grid package and its peer dependencies and sets up the Kendo UI default theme.</p><p>Once the installation is done, we need to activate our Kendo UI license. This step removes the watermark and unlocks all the features.</p><blockquote><p>Don&rsquo;t have a license? Don&rsquo;t worry! You can get a <a target="_blank" href="https://www.telerik.com/try/kendo-ui">completely free trial</a> with <strong>no credit card required</strong>, so you can follow along and build the dashboard without worrying about the watermark.</p></blockquote><p>To do this, download the license key file from your Telerik account. Then, run this command inside your project folder:</p><pre class=" language-bash"><code class="prism  language-bash">npx kendo-ui-license activate
</code></pre><p>Because the Kendo UI Grid is so robust, it includes many features that can slightly increase the initial bundle size. We need to prevent Angular from throwing a &ldquo;bundle initial exceeded maximum budget&rdquo; error when we run or build an app. Open the <code>angular.json</code> file and increase the <code>budgets</code> config for the <code>initial</code> type to around <code>5MB</code> for warnings and <code>10MB</code> for errors.</p><p>Perfect! We have a fresh Angular project with Kendo UI Grid installed and styled. Now let&rsquo;s generate our Super Bowl viewers&rsquo; fake data.</p><h2 id="generating-one-million-viewers">Generating One Million Viewers</h2><p>Before we start with the grid, we need data. We&rsquo;ll create a single service that handles everything: generating fake viewer data for client-side demos and simulating a paginated server API for server-side scrolling.</p><p>To create the service, open the terminal and run the following Angular CLI command:</p><pre class=" language-bash"><code class="prism  language-bash">ng g s services/viewer
</code></pre><p>First, we will define a simple <code>Viewer</code> interface so our grid knows what fields to expect. Then, our service will include a <code>generateViewers</code> method to create mock data, and a <code>fetchPage</code> method that simulates a paginated server API. We won&rsquo;t use <code>fetchPage</code> right away, but it will be ready for when we implement server-side virtual scrolling later.</p><p>Don&rsquo;t worry too much about the exact implementation of the helper methods; the main idea here is not how to generate fake users, but how to create a grid capable of supporting massive amounts of data without breaking a sweat!</p><p>Open <code>src/app/services/viewer.ts</code> and add the following:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">Viewer</span> <span class="token punctuation">{</span>
  id<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  username<span class="token punctuation">:</span> <span class="token keyword">string</span><span class="token punctuation">;</span>
  watchTimeMin<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
  isLive<span class="token punctuation">:</span> <span class="token keyword">boolean</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">PagedResult</span> <span class="token punctuation">{</span>
  data<span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  total<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token keyword">const</span> ADJECTIVES <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">"Swift"</span><span class="token punctuation">,</span>
  <span class="token string">"Lucky"</span><span class="token punctuation">,</span>
  <span class="token string">"Bold"</span><span class="token punctuation">,</span>
  <span class="token string">"Chill"</span><span class="token punctuation">,</span>
  <span class="token string">"Epic"</span><span class="token punctuation">,</span>
  <span class="token string">"Happy"</span><span class="token punctuation">,</span>
  <span class="token string">"Lazy"</span><span class="token punctuation">,</span>
  <span class="token string">"Wild"</span><span class="token punctuation">,</span>
  <span class="token string">"Cool"</span><span class="token punctuation">,</span>
  <span class="token string">"Fast"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> NOUNS <span class="token operator">=</span> <span class="token punctuation">[</span>
  <span class="token string">"Fan"</span><span class="token punctuation">,</span>
  <span class="token string">"Eagle"</span><span class="token punctuation">,</span>
  <span class="token string">"Tiger"</span><span class="token punctuation">,</span>
  <span class="token string">"Bear"</span><span class="token punctuation">,</span>
  <span class="token string">"Wolf"</span><span class="token punctuation">,</span>
  <span class="token string">"Hawk"</span><span class="token punctuation">,</span>
  <span class="token string">"Fox"</span><span class="token punctuation">,</span>
  <span class="token string">"Lion"</span><span class="token punctuation">,</span>
  <span class="token string">"Shark"</span><span class="token punctuation">,</span>
  <span class="token string">"Bull"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

@<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> providedIn<span class="token punctuation">:</span> <span class="token string">"root"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ViewerService</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> readonly TOTAL_VIEWERS <span class="token operator">=</span> 1_000_000<span class="token punctuation">;</span>

  <span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> startId <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> viewers<span class="token punctuation">:</span> Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> count<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      viewers<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
        id<span class="token punctuation">:</span> startId <span class="token operator">+</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span>
        username<span class="token punctuation">:</span> <span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomFrom</span><span class="token punctuation">(</span>ADJECTIVES<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomFrom</span><span class="token punctuation">(</span>NOUNS<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">9999</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">,</span>
        watchTimeMin<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">randomBetween</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">240</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        isLive<span class="token punctuation">:</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&gt;</span> <span class="token number">0.08</span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> viewers<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">/**
   * Simulates a server API call with pagination.
   * Uses a small delay to mimic real network latency.
   */</span>
  <span class="token keyword">async</span> <span class="token function">fetchPage</span><span class="token punctuation">(</span>skip<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> take<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Promise<span class="token operator">&lt;</span>PagedResult<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> <span class="token number">80</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      data<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>take<span class="token punctuation">,</span> skip<span class="token punctuation">)</span><span class="token punctuation">,</span>
      total<span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>TOTAL_VIEWERS<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> randomFrom<span class="token operator">&lt;</span>T<span class="token operator">&gt;</span><span class="token punctuation">(</span>arr<span class="token punctuation">:</span> T<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">:</span> T <span class="token punctuation">{</span>
    <span class="token keyword">return</span> arr<span class="token punctuation">[</span>Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">randomBetween</span><span class="token punctuation">(</span>min<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">,</span> max<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">number</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token punctuation">(</span>max <span class="token operator">-</span> min <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> min<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Each viewer has a fun auto-generated username (like <code>SwiftEagle4821</code>), their watch time and a live status. The <code>fetchPage</code> method is an <code>async</code> function that simulates a paginated API with a small delay to mimic network latency. We&rsquo;ll use <code>generateViewers</code> first and come back to <code>fetchPage</code> later.</p><p>With our service ready, let&rsquo;s see what happens when we try to render all these viewers at once.</p><h2 id="the-problem-rendering-all-rows-at-once">The Problem: Rendering All Rows at Once</h2><p>Let&rsquo;s try to do it and experience the problem firsthand. Open <code>src/app/app.ts</code> and replace its content:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> CommonModule <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/common"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  standalone<span class="token punctuation">:</span> <span class="token keyword">true</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>CommonModule<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span> 
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">loadViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s open our template file, <code>src/app/app.html</code> and add a few buttons for 1K, 10K and 100K viewers. These buttons will call our <code>loadViewers()</code> method to quickly generate different amounts of fake data.</p><p>Finally, we&rsquo;ll use the <code>&lt;kendo-grid&gt;</code> component in our template. The key property to pay attention to is <code>[data]="viewers()"</code>, which tells Kendo UI to read from our reactive signal and constantly display the list of viewers.</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Connected viewers: {{ viewers().length.toLocaleString() }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>actions<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(10_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>10K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(100_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>100K Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1M Viewers<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>grid-container<span class="token punctuation">"</span></span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">600</span>px<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>table</span> <span class="token attr-name">border</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>1<span class="token punctuation">"</span></span><span class="token style-attr language-css"><span class="token attr-name"> <span class="token attr-name">style</span></span><span class="token punctuation">="</span><span class="token attr-value"><span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">100%</span><span class="token punctuation">;</span> <span class="token property">border-collapse</span><span class="token punctuation">:</span> collapse<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>thead</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>#<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Username<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Watch (min)<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>th</span><span class="token punctuation">&gt;</span></span>Live<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>th</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>thead</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tbody</span><span class="token punctuation">&gt;</span></span>
      @for (viewer of viewers(); track viewer.id) {
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>tr</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.id }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.username }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.watchTimeMin }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>td</span><span class="token punctuation">&gt;</span></span>{{ viewer.isLive ? 'Yes' : 'No' }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>td</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tr</span><span class="token punctuation">&gt;</span></span>
      }
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>tbody</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>table</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre><p>Now we can run our app. Go to the terminal and execute the following command:</p><pre class=" language-bash"><code class="prism  language-bash">ng serve
</code></pre><p>This will start the local development server. Once it finishes compiling, open your browser and navigate to <code>http://localhost:4200</code>.</p><p>Click <strong>&ldquo;1K Viewers&rdquo;</strong> and it feels smooth. Click <strong>&ldquo;10K Viewers&rdquo;</strong> and you will notice a significant lag.</p><p>Now click <strong>&ldquo;100K Viewers&rdquo;</strong> &hellip; and watch the browser scream for help. The page will freeze, the scroll will be jumpy and you might even get the &ldquo;Page Unresponsive&rdquo; dialog.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/page-unresponsive-dialog.png?sfvrsn=bb5cb2ab_2" alt="Page Unresponsive dialog" /></p><p>We have seen the problem: standard HTML tables and simple loops cannot handle huge datasets. When you click the 1M Viewers button, the browser stops working.</p><p>OK, but how can we fix this?</p><h2 id="the-solution-angular-grid-withvirtual-scrolling">The Solution: Angular Grid withVirtual Scrolling</h2><p>If you read about Kendo UI, you know the Kendo UI Grid, but before we to start to use it, I want to explain &ldquo;virtual scrolling.&rdquo; Think of virtual scrolling like a camera moving over a big stadium. You only see the seats in the camera frame, maybe 50 seats. The stadium has 1,000,000 seats, but the camera doesn&rsquo;t need to show all of them at once. It only shows what is visible.</p><p>Kendo UI Grid does exactly this with one property: <code>scrollable="virtual"</code>. Let&rsquo;s use it.</p><p>First, update your <code>app.ts</code>. We need to import <code>KENDO_GRID</code> and remove the <code>CommonModule</code> because the grid will handle everything now:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@progress/kendo-angular-grid"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span> 
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token function">loadViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">:</span> <span class="token keyword">number</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">generateViewers</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>Now, let&rsquo;s update <code>src/app/app.html</code>. We will replace the standard <code>&lt;table&gt;</code> with the <code>&lt;kendo-grid&gt;</code> component:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Connected viewers: {{ viewers().length.toLocaleString() }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>actions<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(10_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>10K<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(100_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>100K<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loadViewers(1_000_000)<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>1M<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span>
  <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewers()<span class="token punctuation">"</span></span>
  <span class="token attr-name">[height]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span>
  <span class="token attr-name">scrollable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>virtual<span class="token punctuation">"</span></span>
  <span class="token attr-name">[rowHeight]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>36<span class="token punctuation">"</span></span>
  <span class="token attr-name">[pageSize]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>50<span class="token punctuation">"</span></span>
<span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Username<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>180<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>watchTimeMin<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Watch (min)<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isLive<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Live<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
</code></pre><h3 id="why-does-this-work">Why Does This Work?</h3><p>We added three important properties to the <code>&lt;kendo-grid&gt;</code> to make it fast:</p><ol><li><strong><code>scrollable="virtual"</code></strong>: This tells Kendo UI: &ldquo;Don&rsquo;t create all the rows at once. Only create the ones the user can see right now.&rdquo;</li><li><strong><code>[rowHeight]="36"</code></strong>: The grid needs to know exactly how tall each row is. This helps the grid calculate the scroll position correctly.</li><li><strong><code>[pageSize]="50"</code></strong>: This is how many rows Kendo UI keeps in the DOM. A good tip: set this to <strong>3 times</strong> the number of rows visible on your screen.</li></ol><p>Now, if you click the buttons, you will see that a page with 10,000, 100,000 or even <strong>one million viewers</strong> scrolls smoothly. The browser is fast because only ~50 rows are in the DOM at any time.</p><p>Kendo UI Grid does all the hard work for the rendering.</p><p>But there is one more problem. We fixed the <strong>rendering</strong>, but we are still loading one million records into the browser&rsquo;s memory. For a real app, loading 1,000,000 records at once is a bad idea. It uses too much RAM and it is slow to start.</p><p>What if we want to show one million users in real-time without using all the RAM? Let&rsquo;s use the final solution: <strong>Server-Side Data Fetching</strong>.</p><h2 id="server-side-data-fetching-with-endless-scrolling">Server-Side Data Fetching with Endless Scrolling</h2><p>Here&rsquo;s the reality: the Super Bowl has <strong>120 million viewers</strong>. We can&rsquo;t load all of them into the browser at once. Instead, the grid should fetch data page by page as the user scrolls , loading only what&rsquo;s needed, when it&rsquo;s needed.</p><p>Kendo UI Grid supports this out of the box with the <a target="_blank" href="https://www.telerik.com/kendo-angular-ui/components/grid/api/gridcomponent#scrollbottom"><code>scrollBottom</code></a> event. When the user scrolls to the bottom of the current data, the grid fires this event, and we simply fetch the next page and append it. Let&rsquo;s build it!</p><p>Generate a new component by running the command in the terminal:</p><pre class=" language-bash"><code class="prism  language-bash">ng g c components/live-grid
</code></pre><p>Open <code>src/app/components/live-grid/live-grid.ts</code> and replace the content with:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component<span class="token punctuation">,</span> inject<span class="token punctuation">,</span> signal<span class="token punctuation">,</span> ChangeDetectionStrategy <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> KENDO_GRID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@progress/kendo-angular-grid"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> ViewerService<span class="token punctuation">,</span> Viewer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"../../services/viewer"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-live-grid"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>KENDO_GRID<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./live-grid.html"</span><span class="token punctuation">,</span>
  changeDetection<span class="token punctuation">:</span> ChangeDetectionStrategy<span class="token punctuation">.</span>OnPush<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LiveGrid</span> <span class="token punctuation">{</span>
  <span class="token keyword">private</span> viewerService <span class="token operator">=</span> <span class="token function">inject</span><span class="token punctuation">(</span>ViewerService<span class="token punctuation">)</span><span class="token punctuation">;</span>

  isConnected <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  loading <span class="token operator">=</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  viewers <span class="token operator">=</span> signal<span class="token operator">&lt;</span>Viewer<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  pageSize <span class="token operator">=</span> 1_000<span class="token punctuation">;</span>

  <span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>isConnected<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token function">onScrollBottom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loading</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">private</span> <span class="token function">loadMore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>loading<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    
    <span class="token keyword">this</span><span class="token punctuation">.</span>viewerService<span class="token punctuation">.</span><span class="token function">fetchPage</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">viewers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>length<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>pageSize<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>viewers<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">[</span><span class="token operator">...</span>current<span class="token punctuation">,</span> <span class="token operator">...</span>result<span class="token punctuation">.</span>data<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">this</span><span class="token punctuation">.</span>loading<span class="token punctuation">.</span><span class="token keyword">set</span><span class="token punctuation">(</span><span class="token keyword">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>The logic here is straightforward. First, <code>connect()</code> starts the process by fetching the initial data page using <code>loadMore()</code>. Then, whenever the user scrolls to the bottom of the grid, the <code>(scrollBottom)</code> event fires, triggering <code>onScrollBottom()</code> to fetch and append the next chunk of data to our <code>viewers</code> signal.</p><p>To make this work in the UI, we just need to set <code>scrollable="scrollable"</code> on our Kendo Grid to enable endless scrolling, and bind our <code>loading</code> signal to show a skeleton animation during the fetch.</p><p>Now let&rsquo;s build the template in <code>src/app/components/live-grid/live-grid.html</code> to tie all of this together:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Live Server Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Viewers: {{ viewers().length }}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">(click)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>connect()<span class="token punctuation">"</span></span> <span class="token attr-name">[disabled]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isConnected()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>Connect to Live Feed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

@if (isConnected()) {
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid</span> 
    <span class="token attr-name">[data]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewers()<span class="token punctuation">"</span></span> 
    <span class="token attr-name">[height]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span> 
    <span class="token attr-name">scrollable</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>scrollable<span class="token punctuation">"</span></span>
    <span class="token attr-name">[loading]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>loading()<span class="token punctuation">"</span></span>
    <span class="token attr-name">(scrollBottom)</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>onScrollBottom()<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>id<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>#<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>username<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Username<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>180<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>watchTimeMin<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Watch (min)<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>110<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>kendo-grid-column</span> <span class="token attr-name">field</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>isLive<span class="token punctuation">"</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Live<span class="token punctuation">"</span></span> <span class="token attr-name">[width]</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>70<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid-column</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>kendo-grid</span><span class="token punctuation">&gt;</span></span>
}
</code></pre><p>Wire the component into the app. Update <code>app.ts</code>:</p><pre class=" language-typescript"><code class="prism  language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@angular/core"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> LiveGrid <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./components/live-grid/live-grid"</span><span class="token punctuation">;</span>

@<span class="token function">Component</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  selector<span class="token punctuation">:</span> <span class="token string">"app-root"</span><span class="token punctuation">,</span>
  imports<span class="token punctuation">:</span> <span class="token punctuation">[</span>LiveGrid<span class="token punctuation">]</span><span class="token punctuation">,</span>
  templateUrl<span class="token punctuation">:</span> <span class="token string">"./app.html"</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>And <code>src/app/app.html</code>:</p><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Super Bowl Viewers Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>app-live-grid</span> <span class="token punctuation">/&gt;</span></span>
</code></pre><p>Run <code>ng serve</code>, open <code>http://localhost:4200</code> and click &ldquo;Connect to Live Feed.&rdquo;</p><p>You&rsquo;ll see the grid load the first 1,000 viewers. Now scroll down. When you reach the bottom, the grid fetches the next 1,000 viewers and appends them and the loading skeleton appears briefly while data is being fetched. Keep scrolling, and watch the viewers counter grow until it gets to 1,000,000. Yeah!!</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-04/grid-scroll.gif?sfvrsn=56a027cd_2" alt="Super Bowl viewers dashboard being scrolled and scrolled, very smoothly" /></p><h2 id="recap">Recap</h2><p>We saw the problem: rendering thousands of rows at once makes the browser freeze. Then we added Kendo UI <strong>virtual scrolling</strong> with <code>scrollable="virtual"</code>. This is the secret to showing large datasets without crashing.</p><p>Finally, we used <strong>server-side data fetching</strong> with the <code>(scrollBottom)</code> event. The grid loads data page by page. This keeps the memory usage low and the experience smooth.</p><p>In your next challenge don&rsquo;t worry about the data, Kendo UI Grid makes it easy. With a few properties, your dashboard can handle millions of rows. ✌</p><p>Give it a try for free!</p><p><a href="https://www.telerik.com/try/kendo-angular-ui" target="_blank" class="Btn">Try Kendo UI for Angular</a></p><p>Happy coding!</p><p>Source Code: <a target="_blank" href="https://github.com/danywalls/kendo-grid-large-app">https://github.com/danywalls/kendo-grid-large-app</a></p><img src="https://feeds.telerik.com/link/10827/17323031.gif" height="1" width="1"/>]]></content>
  </entry>
  <entry>
    <id>urn:uuid:a21016d2-1ad2-4a02-bae7-eef7529852c5</id>
    <title type="text">What’s Next for React in 2026</title>
    <summary type="text">The State of React survey reveals developer insights into the patterns and tools they use and how their opinions about React are shifting.</summary>
    <published>2026-04-20T16:37:09Z</published>
    <updated>2026-05-31T08:53:02Z</updated>
    <author>
      <name>Hassan Djirdeh </name>
    </author>
    <link rel="alternate" href="https://feeds.telerik.com/link/10827/17322394/whats-next-react-2026"/>
    <content type="text"><![CDATA[<p><span class="featured">The State of React survey reveals developer insights into the patterns and tools they use and how their opinions about React are shifting.</span></p><p>The <a target="_blank" href="https://2025.stateofreact.com/">State of React 2025</a> survey results paint an interesting picture of where React stands as it heads into 2026. <a target="_blank" href="https://www.telerik.com/blogs/whats-new-react-19">React 19</a> adoption is well underway, but stability hasn&rsquo;t translated into consensus about what comes next.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/whats-next-for-react-state-of-react-25-survey.png?sfvrsn=952363dc_2" alt="State of React 2025" /></p><p>What the survey shows is that some of React&rsquo;s biggest bets are paying off while others are still finding their footing. The patterns we&rsquo;re building with, the tools we&rsquo;re reaching for and the way we think about React architecture are all shifting. In this article, we&rsquo;ll look at what the data actually says and what it means for the year ahead.</p><h2 id="react-server-components">React Server Components</h2><p>One of the most talked about additions to React in the last couple of years has been <a target="_blank" href="https://react.dev/reference/rsc/server-components">React Server Components (RSC)</a>. Server Components are components that run on the server and let us keep server-only logic, data access and sensitive code out of the client bundle. Alongside that, <a target="_blank" href="https://react.dev/reference/rsc/server-functions">Server Functions</a> let client-side code invoke server-side logic through a framework-managed interface, without hand-rolling traditional API endpoints for every interaction.</p><blockquote><p>For a great read on Server Components, check out <a target="_blank" href="https://www.telerik.com/blogs/current-state-react-server-components-guide-perplexed">The Current State of React Server Components: A Guide for the Perplexed</a>.</p></blockquote><p>Some have said that Server Components were to be the foundation of React&rsquo;s next evolution toward a more complete full-stack framework. The <a target="_blank" href="https://2025.stateofreact.com/en-US/features/#all_features">survey data</a>, however, is more nuanced.</p><p>About 45% of respondents have used Server Components, and among those who have, only about a third report a positive experience. Server Functions tell a similar story, with roughly 37% adoption and 33% positive sentiment among users. In both cases, only a small fraction of the overall community has used these features <em>and</em> come away with a positive sentiment.</p><p>Contrast that with <a target="_blank" href="https://react.dev/reference/react/Suspense">Suspense</a>, React&rsquo;s mechanism for declaratively handling loading states while waiting for asynchronous data or code. <a target="_blank" href="https://2025.stateofreact.com/en-US/features/#new_apis_ratios">Suspense has the highest adoption rate among new features and boasts strong satisfaction numbers</a>.</p><p>It&rsquo;s a useful comparison because it shows that the React community isn&rsquo;t resistant to new patterns: when a new API solves a clear problem with a reasonable developer experience, adoption follows. With that said, Suspense is a smaller, more contained feature that&rsquo;s easier to introduce into existing applications, while Server Components require a more fundamental shift in how we think about our application architecture.</p><p>The architecture data reinforces this picture. <a target="_blank" href="https://2025.stateofreact.com/en-US/usage/#js_app_patterns">When asked which rendering patterns they&rsquo;ve used</a>, most teams still rely on the tried-and-true: Single-Page Applications lead the way at 84%, followed by Server-Side Rendering (61%) and Static Site Generation (44%). Newer approaches like partial hydration (25%), streaming SSR (18%) and islands architecture (14%) are gaining traction, but they&rsquo;re far from mainstream.</p><p>None of this means Server Components don&rsquo;t matter. Architecturally, the ability to move rendering logic to the server, reduce client-side JavaScript and simplify data fetching is significant. But the developer experience may just need more time to mature and catch up to the architectural promise. For most teams, the pragmatic move is to adopt RSC incrementally and where it makes sense, rather than treating it as a mandate to rewrite everything.</p><h2 id="what-developers-are-curious-about">What Developers Are Curious About</h2><p>The survey also gives us a sense of where developer curiosity is headed. The <a target="_blank" href="https://2025.stateofreact.com/en-US/features/#reading_list">reading list</a>, the section of the survey that lets respondents flag topics they want to learn more about, is a useful signal here.</p><p><a href="https://react.dev/reference/react/ViewTransition">ViewTransition</a>, a React API for coordinating animated transitions between UI states, ranks near the top. So does <a target="_blank" href="https://react.dev/reference/react/Activity">Activity</a>, which lets us hide and show parts of our UI while preserving their internal state and DOM. Both are currently only available in React&rsquo;s Canary channel, but they point to a future where React handles more of the UX polish that we currently rely on third-party libraries for.</p><p>What&rsquo;s worth noting is the general pattern across the survey data. The features gaining the most positive attention tend to be the ones that solve focused problems without requiring a wholesale rethink of how we build applications. Developers are drawn to APIs that slot into their existing workflows and make specific things easier, whether that&rsquo;s handling loading states with Suspense, coordinating transitions with <code>&lt;ViewTransition&gt;</code> or managing background rendering with <code>&lt;Activity&gt;</code>.</p><h2 id="the-ui-component-library-landscape">The UI Component Library Landscape</h2><p>The <a target="_blank" href="https://2025.stateofreact.com/en-US/libraries/component-libraries/#component_libraries_cardinalities">survey data around UI component libraries</a> also tells an interesting story. The average respondent has used 2.3 UI libraries and a significant proportion don&rsquo;t use any component library at all. As the survey itself notes, this suggests the space isn&rsquo;t quite settled yet, and that there&rsquo;s still room for new entrants to make their mark.</p><p>What this tells us is that developers are still actively evaluating their options. Even the most widely used libraries in the survey sit around <a target="_blank" href="https://2025.stateofreact.com/en-US/libraries/component-libraries/">50-57% usage</a>, and the libraries with the highest satisfaction rates aren&rsquo;t always the ones with the broadest adoption. The needs are clear: production-quality components, built-in accessibility, consistent theming, TypeScript support and, increasingly, integration with AI-powered development workflows.</p><h3 id="kendoreact">KendoReact</h3><p>For teams building enterprise applications, the choice of component library has long-term implications. It affects how quickly we can ship features, how accessible our applications are out of the box and how well our UI scales across complex use cases like data grids, schedulers and form-heavy workflows.</p><p><img src="https://d585tldpucybw.cloudfront.net/sfimages/default-source/blogs/2026/2026-02/whats-next-for-react-kendoreact-home.png?sfvrsn=9f2d81b7_2" alt="KendoReact website: Master the Art of React UI" /></p><p>Progress <a target="_blank" href="https://www.telerik.com/kendo-react-ui">KendoReact</a> is one library worth looking at in this context. It provides <a target="_blank" href="https://www.telerik.com/kendo-react-ui/components">120+ production-ready components</a> with built-in accessibility, deep theming support through <a target="_blank" href="https://www.telerik.com/themebuilder">ThemeBuilder</a> and recent investments in AI-powered developer tooling including an <a target="_blank" href="https://www.telerik.com/react-mcp-servers">MCP server and AI coding assistant</a>. For teams evaluating their UI toolkit for the year ahead, it&rsquo;s a library that&rsquo;s actively investing in the same directions the ecosystem is moving.</p><h2 id="ai-as-an-accelerator">AI as an Accelerator</h2><p>It would be impossible to write about React development in 2026 without mentioning AI. However, the important thing to keep in mind is that AI is changing <strong>how</strong> we write React code, not <strong>what</strong> we build with it.</p><p>The AI tooling landscape has shifted significantly over the last couple of years. AI-native editors like <a target="_blank" href="https://cursor.com/">Cursor</a> and <a target="_blank" href="https://code.claude.com/docs/en/overview">Claude Code</a> understand our entire codebase and can generate components that match our existing patterns and conventions. <a target="_blank" href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol (MCP)</a> integrations give AI assistants real-time access to component library documentation, so the code they generate actually uses the right props and follows current best practices.</p><p>These capabilities make us faster by reducing the time we spend on boilerplate and letting us iterate more quickly. However, they don&rsquo;t replace the architectural decisions we need to make: when to adopt Server Components, how to structure our state management and which rendering patterns fit our use case. AI accelerates the execution of those decisions, not the decisions themselves.</p><blockquote><p>For a deeper dive into how AI is reshaping day-to-day React workflows, from code generation to theming to agentic development, check out <a target="_blank" href="https://www.telerik.com/blogs/ai-productivity-react-developers-2026">AI Productivity for React Developers in 2026</a>.</p></blockquote><h2 id="what-this-means-for-2026">What This Means for 2026</h2><p>The data we surveyed today gives us a clear picture of where React is today and where it could be heading in 2026. React is stable, widely adopted and evolving, but the community&rsquo;s appetite for significant change is measured.</p><ul><li><strong>Server Components represent React&rsquo;s most ambitious shift</strong>, but mainstream acceptance will take time. For most teams, the winning approach is incremental adoption, where it solves real problems.</li><li><strong>Developer experience still wins.</strong> The features seeing the strongest adoption and interest (Suspense, ViewTransition, Activity) solve focused problems without demanding that we rebuild our mental model of React.</li><li><strong>The component library landscape remains unsettled.</strong> Teams need libraries that invest in accessibility, developer experience and integrate well with modern tooling, including AI assistants.</li><li><strong>AI is making us more productive at the execution layer</strong>, but strategic decisions about architecture and user experience still require human judgment.</li></ul><p>Looking ahead, the most successful React teams in 2026 will stay pragmatic: adopting new patterns when they solve real problems, choosing stable tooling and using AI to accelerate delivery without losing sight of fundamentals. React&rsquo;s ecosystem is mature enough that we can be selective about what we adopt and when.</p><aside><hr data-sf-ec-immutable="" /><div class="row"><div class="col-4 u-normal-full u-small-mb0"><h4 class="u-fs20 u-fw5 u-lh125 u-mb0">AI Productivity for React Developers in 2026</h4></div><div class="col-8"><p class="u-fs16 u-mb0">Developers are not blindly handing work over to AI, but treating it as a strategic assistant. <a target="_blank" href="https://www.telerik.com/blogs/ai-productivity-react-developers-2026">Here&rsquo;s what this looks like for React devs in 2026.</a></p></div></div></aside><img src="https://feeds.telerik.com/link/10827/17322394.gif" height="1" width="1"/>]]></content>
  </entry>
</feed>
