<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rodrigo Nogueira</title>
    <description>The latest articles on DEV Community by Rodrigo Nogueira (@rodrigobnogueira).</description>
    <link>https://dev.to/rodrigobnogueira</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F806537%2F35f1c67a-cb9c-4bb8-8f57-314cce07df30.jpg</url>
      <title>DEV Community: Rodrigo Nogueira</title>
      <link>https://dev.to/rodrigobnogueira</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rodrigobnogueira"/>
    <language>en</language>
    <item>
      <title>A NestJS reference app that proves the nest-native stack under realistic backend pressure</title>
      <dc:creator>Rodrigo Nogueira</dc:creator>
      <pubDate>Mon, 25 May 2026 15:37:52 +0000</pubDate>
      <link>https://dev.to/rodrigobnogueira/a-nestjs-reference-app-that-proves-the-nest-native-stack-under-realistic-backend-pressure-5dc0</link>
      <guid>https://dev.to/rodrigobnogueira/a-nestjs-reference-app-that-proves-the-nest-native-stack-under-realistic-backend-pressure-5dc0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: &lt;code&gt;nest-native/reference-app&lt;/code&gt; is a v0.1 reference application that demonstrates the &lt;a href="https://nest-native.dev/" rel="noopener noreferrer"&gt;nest-native&lt;/a&gt; stack end-to-end — &lt;code&gt;nest-drizzle-native&lt;/code&gt; and &lt;code&gt;nest-trpc-native&lt;/code&gt; composing under realistic backend pressure: feature modules, multi-tenant auth context, cross-service transactions via &lt;code&gt;@Transactional()&lt;/code&gt;, an outbox-pattern worker for post-commit side effects, and a typed tRPC client smoke check. Everything decorator-first, ~zero hidden magic, and all eight implementation milestones shipped via PR with security and dependency review on each one. Repo: &lt;a href="https://github.com/nest-native/reference-app" rel="noopener noreferrer"&gt;github.com/nest-native/reference-app&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A quick note about the org
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nest-native.dev/" rel="noopener noreferrer"&gt;&lt;code&gt;nest-native&lt;/code&gt;&lt;/a&gt; is a fresh community GitHub organization publishing decorator-first NestJS integrations. The point is that each integration should &lt;strong&gt;feel like an official NestJS package&lt;/strong&gt; — modules, decorators, DI, enhancers, lifecycle hooks — while staying honest about the underlying tool. Drizzle stays SQL-first. tRPC stays tRPC. No hidden magic where explicit application code would be clearer.&lt;/p&gt;

&lt;p&gt;Two libraries are currently published:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://nest-native.dev/nest-drizzle-native/" rel="noopener noreferrer"&gt;&lt;code&gt;nest-drizzle-native&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — Drizzle ORM with &lt;code&gt;DrizzleModule.forRoot()&lt;/code&gt;, &lt;code&gt;forFeature([Repo])&lt;/code&gt;, &lt;code&gt;@DrizzleRepository&lt;/code&gt;, &lt;code&gt;@InjectDrizzle&lt;/code&gt;, and &lt;code&gt;@Transactional&lt;/code&gt; via &lt;code&gt;@nestjs-cls/transactional&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://nest-native.dev/nest-trpc-native/" rel="noopener noreferrer"&gt;&lt;code&gt;nest-trpc-native&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — Decorator-first tRPC for NestJS: &lt;code&gt;TrpcModule.forRoot()&lt;/code&gt;, &lt;code&gt;@Router&lt;/code&gt;, &lt;code&gt;@Query&lt;/code&gt;/&lt;code&gt;@Mutation&lt;/code&gt;/&lt;code&gt;@Subscription&lt;/code&gt;, &lt;code&gt;@Input&lt;/code&gt;, &lt;code&gt;@TrpcContext&lt;/code&gt;, plus generated &lt;code&gt;AppRouter&lt;/code&gt; types for fully-typed tRPC clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both ship with samples, ≥99% coverage, support policies, and a strict design bar. That bar is exactly what made the reference app necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem this exists to solve
&lt;/h2&gt;

&lt;p&gt;If you're starting a new NestJS backend in 2026 and you've picked Drizzle for the database and tRPC for the API layer, you have a &lt;strong&gt;composition problem&lt;/strong&gt;. Each library is well-documented in isolation, but a real backend is the composition — and the composition is where most of the design decisions live. Library docs cover their slice; nobody covers the seams.&lt;/p&gt;

&lt;p&gt;Here are the questions you'd otherwise have to answer from scratch, in roughly the order they bite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How do I thread "current user" and "current organization" through everything?&lt;/strong&gt; An Express middleware can set &lt;code&gt;req.authContext&lt;/code&gt; easily enough — but how does that reach a tRPC procedure? A guard? A service three calls deep in a request? The right answer involves request-scoped DI, &lt;code&gt;TrpcModule.forRoot({ createContext })&lt;/code&gt;, custom param decorators that work for both transports, and class-level &lt;code&gt;@UseGuards&lt;/code&gt;. There's no canonical example anywhere that wires all of it together end-to-end.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How do I do a real transaction across services?&lt;/strong&gt; "Insert a user, then a membership, then a project, then an audit row, then enqueue a notification" is a single business operation. If you split it across services — which you should — do they share a transaction? &lt;code&gt;@nestjs-cls/transactional&lt;/code&gt; gives you a &lt;code&gt;@Transactional()&lt;/code&gt; decorator, but how does the inner repo know to use the tx client and not the raw one? And what if you're on &lt;code&gt;better-sqlite3&lt;/code&gt; for local dev? (Spoiler: not with the official adapter — its async wrapper silently commits empty transactions against synchronous sqlite. You need a custom sync adapter, which the reference app ships as ~30 lines.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How do I send a post-commit side effect without losing it?&lt;/strong&gt; "After the transaction commits, send the welcome email" sounds easy until you realize you can't send inside the tx (rollback → ghost email) and you can't send after the tx return either (process crashes → lost email). The transactional outbox is the right answer. Reinventing it is a week of subtle bugs around atomic claims, idempotency keys, stuck-claim recovery, and exponential backoff.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How do I prove my tRPC procedures still match my clients?&lt;/strong&gt; tRPC's pitch is end-to-end type safety — but only if the generated &lt;code&gt;AppRouter&lt;/code&gt; actually round-trips into a real typed client at CI time. Most teams skip this and find out their procedures broke when their frontend breaks in staging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What does the boring scaffolding actually look like?&lt;/strong&gt; ESLint flat config with a cognitive-complexity ceiling, &lt;code&gt;drizzle-kit&lt;/code&gt; migrations with a forward-only contract, &lt;code&gt;node:test&lt;/code&gt; + &lt;code&gt;c8&lt;/code&gt; coverage, an &lt;code&gt;npm run ci&lt;/code&gt; chain (&lt;code&gt;typecheck → lint → complexity → tests → audit → build&lt;/code&gt;), a Dockerfile that runs both API and worker off the same image, a CHANGELOG with per-dependency justifications. Two to four days of YAML and config decisions before you can write any business logic.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This repo answers each of those with a &lt;strong&gt;decision, not a menu of options&lt;/strong&gt;. You can disagree with any one of them and swap it out — but you're disagreeing with something concrete rather than designing in a vacuum.&lt;/p&gt;

&lt;p&gt;It is &lt;strong&gt;not a product&lt;/strong&gt; and &lt;strong&gt;not a library&lt;/strong&gt;. The two implicit deliverables are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A credible demo a team can fork (or copy patterns from) for a real backend.&lt;/li&gt;
&lt;li&gt;A feedback loop into the libraries themselves — if a pattern feels awkward here, that's signal to add API upstream in a separate PR to the relevant library repo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A bug found while building it actually flowed exactly that way. A missing &lt;code&gt;await&lt;/code&gt; in &lt;code&gt;nest-trpc-native&lt;/code&gt; was silently mis-mapping &lt;code&gt;HttpException → INTERNAL_SERVER_ERROR&lt;/code&gt; whenever any interceptor was in the chain (and &lt;code&gt;@nestjs-cls/transactional&lt;/code&gt; always registers a passthrough interceptor). The fix shipped as &lt;a href="https://www.npmjs.com/package/nest-trpc-native" rel="noopener noreferrer"&gt;&lt;code&gt;nest-trpc-native@0.4.3&lt;/code&gt;&lt;/a&gt; before milestone 6 of the reference app could land. Without a reference app exercising the composition under load, that bug would have hit production for someone else first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Pick&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;NestJS 11.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPC&lt;/td&gt;
&lt;td&gt;tRPC 11.x via &lt;code&gt;nest-trpc-native&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM&lt;/td&gt;
&lt;td&gt;Drizzle 0.45.x via &lt;code&gt;nest-drizzle-native&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB (local)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;better-sqlite3&lt;/code&gt; (zero-setup)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB (prod recipe)&lt;/td&gt;
&lt;td&gt;Postgres via &lt;code&gt;pg&lt;/code&gt; (documented swap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@nestjs-cls/transactional&lt;/code&gt; with a custom sync adapter for better-sqlite3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;scrypt + HS256 JWT, all via &lt;code&gt;node:crypto&lt;/code&gt; (no JWT lib dep)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;node:test&lt;/code&gt; + &lt;code&gt;c8&lt;/code&gt; coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lint&lt;/td&gt;
&lt;td&gt;ESLint 10 flat config + &lt;code&gt;sonarjs&lt;/code&gt; (cognitive complexity ceiling 15)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Runtime &lt;code&gt;dependencies&lt;/code&gt; are pinned and every one of them has a one-line justification in &lt;a href="https://github.com/nest-native/reference-app/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;CHANGELOG.md&lt;/a&gt;. Default to Node built-ins. New deps need explicit acceptance in the PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Module shape
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03gvf8gfy5mggd3uulbm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03gvf8gfy5mggd3uulbm.png" alt="reference-app as an architecture city: a central reference-app building wires nine feature modules (auth, users, projects, memberships, onboarding, audit, outbox, trpc, db) via glowing teal paths; two red arcs trace the cross-cutting concerns — tx (transactions) and auth (request context)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;reference-app&lt;/code&gt; (center) wires the nine feature modules around it. Two concerns cut across the architecture: *&lt;/em&gt;&lt;code&gt;tx&lt;/code&gt;** — a &lt;code&gt;@Transactional()&lt;/code&gt; method spans &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;memberships&lt;/code&gt;, &lt;code&gt;projects&lt;/code&gt;, &lt;code&gt;audit&lt;/code&gt;, &lt;code&gt;outbox&lt;/code&gt; in one transaction — and &lt;strong&gt;&lt;code&gt;auth&lt;/code&gt;&lt;/strong&gt; — the request-scoped &lt;code&gt;CURRENT_USER&lt;/code&gt; / &lt;code&gt;CURRENT_ORGANIZATION&lt;/code&gt; context is consumed by every procedure.*&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AppModule&lt;/code&gt; wires &lt;code&gt;DatabaseModule&lt;/code&gt; (with &lt;code&gt;DrizzleModule.forRoot&lt;/code&gt;), &lt;code&gt;ClsModule&lt;/code&gt; (with &lt;code&gt;ClsPluginTransactional&lt;/code&gt;), &lt;code&gt;AuthModule&lt;/code&gt;, &lt;code&gt;RequestContextModule&lt;/code&gt;, one module per feature (&lt;code&gt;organizations&lt;/code&gt;, &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;projects&lt;/code&gt;, &lt;code&gt;audit-log&lt;/code&gt;, &lt;code&gt;outbox&lt;/code&gt;, &lt;code&gt;onboarding&lt;/code&gt;), and &lt;code&gt;AppTrpcModule&lt;/code&gt; (with &lt;code&gt;TrpcModule.forRoot&lt;/code&gt;). Every feature module is the same four files: &lt;code&gt;&amp;lt;feature&amp;gt;.repository.ts&lt;/code&gt; (&lt;code&gt;@DrizzleRepository&lt;/code&gt;), &lt;code&gt;&amp;lt;feature&amp;gt;.service.ts&lt;/code&gt; (business logic, reads &lt;code&gt;CURRENT_USER&lt;/code&gt; / &lt;code&gt;CURRENT_ORGANIZATION&lt;/code&gt;), &lt;code&gt;&amp;lt;feature&amp;gt;.router.ts&lt;/code&gt; (&lt;code&gt;@Router('…')&lt;/code&gt; + &lt;code&gt;@UseGuards(AuthGuard)&lt;/code&gt;), &lt;code&gt;&amp;lt;feature&amp;gt;.module.ts&lt;/code&gt; (&lt;code&gt;DrizzleModule.forFeature([Repo])&lt;/code&gt; + service + router). Shape borrowed from &lt;code&gt;nest-drizzle-native&lt;/code&gt;'s sample-17.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tenant auth, end-to-end
&lt;/h2&gt;

&lt;p&gt;One Express middleware reads the &lt;code&gt;Authorization: Bearer …&lt;/code&gt; header, calls &lt;code&gt;AuthService.resolve(token)&lt;/code&gt; (HS256 verify via &lt;code&gt;node:crypto&lt;/code&gt;), and sets &lt;code&gt;req.authContext = { user, organization }&lt;/code&gt;. That single shape is then consumed three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;REST controllers&lt;/strong&gt; — &lt;code&gt;@CurrentUser()&lt;/code&gt; / &lt;code&gt;@CurrentOrganization()&lt;/code&gt; param decorators read it via &lt;code&gt;switchToHttp().getRequest()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tRPC procedures&lt;/strong&gt; — &lt;code&gt;TrpcModule.forRoot({ createContext: ({ req }) =&amp;gt; ({ authContext: req.authContext ?? null }) })&lt;/code&gt; puts the same value on the tRPC ctx. The same param decorators fall through &lt;code&gt;getArgs()[1]&lt;/code&gt; first, so one implementation serves both transports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request-scoped DI&lt;/strong&gt; — &lt;code&gt;RequestContextModule&lt;/code&gt; exposes &lt;code&gt;CURRENT_USER&lt;/code&gt; and &lt;code&gt;CURRENT_ORGANIZATION&lt;/code&gt; as &lt;code&gt;Scope.REQUEST&lt;/code&gt; providers backed by &lt;code&gt;req.authContext&lt;/code&gt;. Services inject them directly, no threading through every method signature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JWT signing and verification: ~50 lines on top of &lt;code&gt;node:crypto&lt;/code&gt;'s HMAC, no JWT library dep. Password hashing: &lt;code&gt;scrypt&lt;/code&gt; with the format &lt;code&gt;scrypt$&amp;lt;salt-hex&amp;gt;$&amp;lt;hash-hex&amp;gt;&lt;/code&gt; — same helpers in the seed and the auth service so seeded admins can log in immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The central proof — a five-step &lt;code&gt;@Transactional()&lt;/code&gt; workflow
&lt;/h2&gt;

&lt;p&gt;The brief calls it "the central proof": one method that writes across five tables inside a single transaction, then queues a post-commit side effect via the outbox pattern. If transactions don't compose cleanly across services, this is where it breaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk42qo03pb26up435azr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk42qo03pb26up435azr.png" alt="inviteUser as a pipeline: five numbered steps — users, memberships, projects, audit_events, outbox_events — flow through a transaction; a commit valve releases the queued outbox event to the worker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Five steps inside one transaction (1. &lt;code&gt;users&lt;/code&gt;, 2. &lt;code&gt;memberships&lt;/code&gt;, 3. &lt;code&gt;projects&lt;/code&gt;, 4. &lt;code&gt;audit_events&lt;/code&gt;, 5. &lt;code&gt;outbox_events&lt;/code&gt;), then commit, then the worker delivers the post-commit side effect.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The annotated body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Transactional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;inviteUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;InviteUserInput&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InviteUserResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsertUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialPassword&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;memberships&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* orgId, userId, role */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;    &lt;span class="cm"&gt;/* orgId, name, createdBy */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;          &lt;span class="cm"&gt;/* "user.invited" with invitee+project metadata */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user.invited&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;invitedEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`user.invited:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;outboxEventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two non-obvious choices in the wiring around it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;@InjectTransaction()&lt;/code&gt;, not &lt;code&gt;@InjectDrizzle()&lt;/code&gt;.&lt;/strong&gt; The CLS proxy resolves to the active tx client inside &lt;code&gt;@Transactional&lt;/code&gt; and falls back to the raw client outside one — transparent to the repo. &lt;code&gt;@InjectDrizzle()&lt;/code&gt; always returns the raw client, so writes from inside the transactional method would not participate in the transaction. Easy to miss; the symptom is "everything looks fine, nothing rolls back."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A custom sync transactional adapter for better-sqlite3.&lt;/strong&gt; The official &lt;code&gt;@nestjs-cls/transactional-adapter-drizzle-orm&lt;/code&gt; wraps the inner Drizzle callback in &lt;code&gt;async&lt;/code&gt;, which silently commits empty transactions against synchronous sqlite (its &lt;code&gt;client.transaction(fn)&lt;/code&gt; is sync and treats the async callback's immediately-returned Promise as a successful return). The repo ships a ~30-line &lt;a href="https://github.com/nest-native/reference-app/blob/main/src/database/sync-drizzle-transactional-adapter.ts" rel="noopener noreferrer"&gt;&lt;code&gt;SyncDrizzleTransactionalAdapter&lt;/code&gt;&lt;/a&gt; that keeps the inner callback synchronous while still returning a Promise to satisfy the plugin contract. Swap to the official adapter when moving to libsql or Postgres.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The brief mandates three tests around this method, and all three pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Happy path&lt;/strong&gt; — all five rows persisted; the outbox row is visible after commit; the worker tick processes it; &lt;code&gt;FakeEmailTransport&lt;/code&gt; records exactly one email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollback safety&lt;/strong&gt; — force a throw between project insert and the audit event; assert zero rows from this transaction persist; assert no email recorded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker crash recovery&lt;/strong&gt; — seed an outbox row in &lt;code&gt;processing&lt;/code&gt; state with a stale &lt;code&gt;claimed_at&lt;/code&gt;; the next claimer tick re-claims it (under the stuck-timeout), processes it exactly once, and a follow-up tick is a no-op.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The outbox + worker
&lt;/h2&gt;

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

&lt;p&gt;&lt;em&gt;Rows go &lt;code&gt;pending → processing → completed&lt;/code&gt;. Retryable errors bounce back to &lt;code&gt;pending&lt;/code&gt; (&lt;code&gt;attempts++&lt;/code&gt; + backoff). Max attempts go to &lt;code&gt;failed&lt;/code&gt;. Stuck &lt;code&gt;processing&lt;/code&gt; rows get re-claimed by another worker after a timeout.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three small pieces, one file each: &lt;strong&gt;&lt;code&gt;OutboxProducer&lt;/code&gt;&lt;/strong&gt; inserts a &lt;code&gt;pending&lt;/code&gt; row inside the active tx with a partial-unique idempotency key (multiple NULLs coexist, non-null is unique). &lt;strong&gt;&lt;code&gt;OutboxRegistry&lt;/code&gt;&lt;/strong&gt; is a &lt;code&gt;Map&amp;lt;topic, handler&amp;gt;&lt;/code&gt; populated by handlers on module init. &lt;strong&gt;&lt;code&gt;OutboxClaimer.tick()&lt;/code&gt;&lt;/strong&gt; opens a tx, selects pending-OR-stuck rows, marks them &lt;code&gt;processing&lt;/code&gt;, dispatches, then marks &lt;code&gt;completed&lt;/code&gt; / retries with backoff+jitter / marks &lt;code&gt;failed&lt;/code&gt; at max attempts. The claim is the "BEGIN IMMEDIATE + status filter" shape from the brief; Postgres would use &lt;code&gt;SELECT … FOR UPDATE SKIP LOCKED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The worker process is just &lt;a href="https://github.com/nest-native/reference-app/blob/main/scripts/start-worker.ts" rel="noopener noreferrer"&gt;&lt;code&gt;scripts/start-worker.ts&lt;/code&gt;&lt;/a&gt; — boot a headless Nest application context, resolve &lt;code&gt;OutboxClaimer&lt;/code&gt;, tick on a configurable interval, abort cleanly on &lt;code&gt;SIGTERM&lt;/code&gt;/&lt;code&gt;SIGINT&lt;/code&gt;. Same Docker image runs either the API (default &lt;code&gt;CMD&lt;/code&gt;) or the worker (&lt;code&gt;node dist/scripts/start-worker.js&lt;/code&gt; override); &lt;code&gt;docker-compose.yml&lt;/code&gt; wires both on a shared SQLite volume with a healthcheck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed tRPC client smoke
&lt;/h2&gt;

&lt;p&gt;The brief's "definition of done" requires a typed client that consumes the generated &lt;code&gt;AppRouter&lt;/code&gt; and exercises one query, one mutation, and one auth-protected call against a live local server. That lives in &lt;a href="https://github.com/nest-native/reference-app/blob/main/client-smoke/client.ts" rel="noopener noreferrer"&gt;&lt;code&gt;client-smoke/&lt;/code&gt;&lt;/a&gt; and is part of &lt;code&gt;npm run ci&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createTRPCClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;httpBatchLink&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@trpc/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/@generated/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;anon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createTRPCClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;httpBatchLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trpc`&lt;/span&gt; &lt;span class="p"&gt;})]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;anon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                              &lt;span class="c1"&gt;// query&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;anon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;   &lt;span class="c1"&gt;// mutation&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createTRPCClient&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;httpBatchLink&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/trpc`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;})],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;me&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                            &lt;span class="c1"&gt;// auth-protected query&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiled output (which lands in CI as &lt;code&gt;npm run client-smoke:typecheck&lt;/code&gt;) catches any router-shape change that would break a real client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in 30 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/nest-native/reference-app
&lt;span class="nb"&gt;cd &lt;/span&gt;reference-app
nvm use &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install
&lt;/span&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reference-app.db npm run db:migrate
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reference-app.db npm run seed

&lt;span class="nv"&gt;AUTH_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev-secret-must-be-at-least-32-characters-xxxxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reference-app.db &lt;span class="se"&gt;\&lt;/span&gt;
  npm run start:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;AUTH_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev-secret-must-be-at-least-32-characters-xxxxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./reference-app.db &lt;span class="se"&gt;\&lt;/span&gt;
  npm run start:worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The seed creates &lt;code&gt;admin@acme.test&lt;/code&gt; / &lt;code&gt;admin123!&lt;/code&gt; with one starter project. &lt;code&gt;client-smoke&lt;/code&gt; walks the typed end-to-end flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it deliberately is not
&lt;/h2&gt;

&lt;p&gt;Restating these because they shape what &lt;em&gt;shouldn't&lt;/em&gt; be added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a CLI&lt;/strong&gt; (&lt;code&gt;create-nest-native-app&lt;/code&gt;). Permanent maintenance cost, marginal value over a well-organized template repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not the home of a standalone outbox package.&lt;/strong&gt; The outbox pattern lives here as an in-app module. A hypothetical &lt;code&gt;nest-outbox-native&lt;/code&gt; extraction (no such package exists today) would only be worth considering after three+ real apps independently rewrite the same shape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a frontend.&lt;/strong&gt; &lt;code&gt;client-smoke/&lt;/code&gt; is a typed-client smoke test, not a UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not multi-database / GraphQL / micro-frontends.&lt;/strong&gt; Resist scope creep.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a pattern repeats three times here, it's a candidate for upstreaming to &lt;code&gt;nest-drizzle-native&lt;/code&gt; or &lt;code&gt;nest-trpc-native&lt;/code&gt; — not for a local helper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/nest-native/reference-app" rel="noopener noreferrer"&gt;github.com/nest-native/reference-app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Landing &amp;amp; one-sitting architecture doc: &lt;a href="https://nest-native.dev/reference-app/" rel="noopener noreferrer"&gt;nest-native.dev/reference-app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Release: &lt;a href="https://github.com/nest-native/reference-app/releases/tag/v0.1.0" rel="noopener noreferrer"&gt;v0.1.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Libraries it serves:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nest-native.dev/nest-drizzle-native/" rel="noopener noreferrer"&gt;&lt;code&gt;nest-drizzle-native&lt;/code&gt;&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/nest-drizzle-native" rel="noopener noreferrer"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nest-native.dev/nest-trpc-native/" rel="noopener noreferrer"&gt;&lt;code&gt;nest-trpc-native&lt;/code&gt;&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/nest-trpc-native" rel="noopener noreferrer"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The org as a whole: &lt;a href="https://nest-native.dev/" rel="noopener noreferrer"&gt;nest-native.dev&lt;/a&gt; · &lt;a href="https://github.com/nest-native" rel="noopener noreferrer"&gt;github.com/nest-native&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you build something on top, open an issue — pattern repetition is what tells us when an idea is ready to graduate from "this is how the reference app does it" to "this is shipped library API."&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>drizzle</category>
      <category>trpc</category>
    </item>
    <item>
      <title>Introducing nest-drizzle-native: A Nest-native Drizzle ORM integration</title>
      <dc:creator>Rodrigo Nogueira</dc:creator>
      <pubDate>Sun, 17 May 2026 05:11:11 +0000</pubDate>
      <link>https://dev.to/rodrigobnogueira/introducing-nest-drizzle-native-a-nest-native-drizzle-orm-integration-21pe</link>
      <guid>https://dev.to/rodrigobnogueira/introducing-nest-drizzle-native-a-nest-native-drizzle-orm-integration-21pe</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filhaomftbws398167mnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filhaomftbws398167mnz.png" alt="![nest-drizzle-native announcement image](...)" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The release of &lt;strong&gt;nest-drizzle-native 0.2.1&lt;/strong&gt; introduces a community package designed to make Drizzle ORM feel entirely natural inside NestJS applications.&lt;/p&gt;

&lt;p&gt;The core concept is straightforward: preserve Drizzle's explicit, SQL-first philosophy while giving NestJS projects the module, dependency injection, repository, testing, and transaction ergonomics they expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Motivation
&lt;/h2&gt;

&lt;p&gt;Drizzle is excellent because it stays close to SQL and avoids heavy, opaque ORM magic. &lt;/p&gt;

&lt;p&gt;NestJS, on the other hand, provides applications with a strong architectural shape built around modules, providers, decorators, testing utilities, lifecycle hooks, and clear dependency boundaries. &lt;/p&gt;

&lt;p&gt;A bridge between those two worlds allows teams to build scalable enterprise architectures without turning Drizzle into something it is not. Existing solutions often act as thin connection wrappers, leaving developers to manually solve complex enterprise patterns like cross-service transactions or DTO co-generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it supports today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DrizzleModule.forRoot()&lt;/code&gt;, &lt;code&gt;forRootAsync()&lt;/code&gt;, and &lt;code&gt;forFeature()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@InjectDrizzle()&lt;/code&gt; for direct Drizzle client injection&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@DrizzleRepository()&lt;/code&gt; for query-focused provider classes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Transactional()&lt;/code&gt; / &lt;code&gt;@InjectTransaction()&lt;/code&gt; powered natively via &lt;code&gt;@nestjs-cls/transactional&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Named connections for multi-database applications&lt;/li&gt;
&lt;li&gt;Testing utilities for clean module and repository testing&lt;/li&gt;
&lt;li&gt;Zero runtime dependencies in the published library package&lt;/li&gt;
&lt;li&gt;Runnable samples covering transactions, DTO validation, optional &lt;code&gt;drizzle-zod&lt;/code&gt; validation, Swagger/OpenAPI integration, raw SQL execution, and various driver setups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let's look at the code
&lt;/h2&gt;

&lt;p&gt;The biggest pain point of using Drizzle in a structured backend is transaction management. Passing a &lt;code&gt;tx&lt;/code&gt; object through five layers of injected services completely breaks Dependency Injection. &lt;/p&gt;

&lt;p&gt;With &lt;code&gt;nest-drizzle-native&lt;/code&gt;, transactions are propagated through the CLS transaction context used by &lt;code&gt;@nestjs-cls/transactional&lt;/code&gt;, allowing them to flow seamlessly across your services just by using a decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Transactional&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nest-drizzle-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuditService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./audit.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UsersRepository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./users.repository&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./create-user.dto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;usersRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UsersRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuditService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Transactional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usersRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USER_CREATED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository itself is a clean Nest provider that keeps Drizzle's query builder visible instead of hiding SQL behind an Active Record layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;DrizzleRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;InjectDrizzle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nest-drizzle-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppDatabase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./create-user.dto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;DrizzleRepository&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;InjectDrizzle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppDatabase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it does not try to do
&lt;/h2&gt;

&lt;p&gt;The goal is strictly avoiding the re-creation of TypeORM or an Active Record layer. &lt;/p&gt;

&lt;p&gt;Developers still write real, functional Drizzle queries. The library simply provides those queries with a native NestJS structure through decorators and context management, eliminating the need to pass transaction callbacks (&lt;code&gt;tx&lt;/code&gt;) down through multiple layers of services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://nest-native.github.io/nest-drizzle-native/" rel="noopener noreferrer"&gt;https://nest-native.github.io/nest-drizzle-native/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/nest-native/nest-drizzle-native" rel="noopener noreferrer"&gt;https://github.com/nest-native/nest-drizzle-native&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/nest-drizzle-native" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/nest-drizzle-native&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/nest-native/nest-drizzle-native/releases/tag/nest-drizzle-native%400.2.1" rel="noopener noreferrer"&gt;https://github.com/nest-native/nest-drizzle-native/releases/tag/nest-drizzle-native%400.2.1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Community feedback from NestJS and Drizzle users is highly encouraged—especially regarding real-world transaction patterns, testing strategies, multi-database setups, and production adoption. Feel free to open issues or contribute to the repository!&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>drizzle</category>
      <category>typescript</category>
      <category>node</category>
    </item>
    <item>
      <title>nest-trpc-native: Full NestJS Power + tRPC Type Safety with Zero Runtime Overhead</title>
      <dc:creator>Rodrigo Nogueira</dc:creator>
      <pubDate>Tue, 17 Mar 2026 17:24:47 +0000</pubDate>
      <link>https://dev.to/rodrigobnogueira/nest-trpc-native-full-nestjs-power-trpc-type-safety-with-zero-runtime-overhead-4f15</link>
      <guid>https://dev.to/rodrigobnogueira/nest-trpc-native-full-nestjs-power-trpc-type-safety-with-zero-runtime-overhead-4f15</guid>
      <description>&lt;p&gt;Hey NestJS + tRPC community! 👋&lt;/p&gt;

&lt;p&gt;I’m excited to release &lt;strong&gt;nest-trpc-native v0.3.0&lt;/strong&gt; — a decorator-first, native-feeling tRPC integration for NestJS.&lt;/p&gt;

&lt;p&gt;No adapter glue code in your app layer. No compromise on NestJS features.&lt;/p&gt;

&lt;p&gt;You write tRPC routers like Nest classes, with full support for dependency injection, guards, interceptors, pipes, filters, request-scoped providers, and end-to-end type safety.&lt;/p&gt;

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

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

&lt;p&gt;Most tRPC + NestJS approaches push you toward one side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep all NestJS lifecycle features, but lose clean tRPC developer experience, or&lt;/li&gt;
&lt;li&gt;Keep pure tRPC style, but lose NestJS enhancers and request-scoped DI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;nest-trpc-native&lt;/code&gt; is designed so you can have both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Router('users')&lt;/code&gt; + &lt;code&gt;@Query()&lt;/code&gt;, &lt;code&gt;@Mutation()&lt;/code&gt;, &lt;code&gt;@Subscription()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Full support for &lt;code&gt;@UseGuards()&lt;/code&gt;, &lt;code&gt;@UseInterceptors()&lt;/code&gt;, &lt;code&gt;@UsePipes(ValidationPipe)&lt;/code&gt;, &lt;code&gt;@UseFilters()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Input()&lt;/code&gt; and &lt;code&gt;@TrpcContext()&lt;/code&gt; for parameter extraction&lt;/li&gt;
&lt;li&gt;Auto-generated router types (&lt;code&gt;autoSchemaFile: 'src/@generated/server.ts'&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Works with &lt;strong&gt;Express&lt;/strong&gt; and &lt;strong&gt;Fastify&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Zod &lt;strong&gt;or&lt;/strong&gt; classic &lt;code&gt;class-validator&lt;/code&gt; validation&lt;/li&gt;
&lt;li&gt;Zero runtime dependencies in &lt;code&gt;nest-trpc-native&lt;/code&gt; itself (host app controls Nest/tRPC peers)&lt;/li&gt;
&lt;li&gt;Monorepo-friendly sample layout&lt;/li&gt;
&lt;li&gt;Microservice transport pattern demonstrated in a focused sample&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Router Example (NestJS style)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;UseGuards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthGuard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersRouter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UsersService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FindOneSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;UsePipes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidationPipe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;TrpcContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;requestId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Mutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateUserSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Client side stays fully type-safe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;trpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// autocomplete + compile-time safety&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nest-trpc-native @trpc/server
npm &lt;span class="nb"&gt;install&lt;/span&gt; @nestjs/common @nestjs/core reflect-metadata rxjs
&lt;span class="c"&gt;# optional:&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then register the module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TrpcModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/trpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;autoSchemaFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/@generated/server.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Samples You Can Run Now
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sample/00-showcase&lt;/code&gt;: full integration baseline (guards, interceptors, pipes, filters, request scope, Express/Fastify, typed clients, subscriptions)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sample/11-microservice-transport&lt;/code&gt;: tRPC edge gateway + Nest microservice transport (TCP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From repo root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run showcase
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docs, Repo, and NPM
&lt;/h3&gt;

&lt;p&gt;Docs: &lt;a href="https://rodrigobnogueira.github.io/nest-trpc-native/docs/introduction" rel="noopener noreferrer"&gt;https://rodrigobnogueira.github.io/nest-trpc-native/docs/introduction&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/rodrigobnogueira/nest-trpc-native" rel="noopener noreferrer"&gt;https://github.com/rodrigobnogueira/nest-trpc-native&lt;/a&gt;&lt;br&gt;&lt;br&gt;
NPM: &lt;a href="https://www.npmjs.com/package/nest-trpc-native" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/nest-trpc-native&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it, break it, open issues, and share feedback.&lt;/p&gt;

&lt;p&gt;Thanks!&lt;/p&gt;

&lt;h1&gt;
  
  
  nestjs #trpc #typescript
&lt;/h1&gt;

</description>
      <category>nestjs</category>
      <category>trpc</category>
      <category>typescript</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
