<?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.us-east-2.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>The dual-write problem in NestJS, solved with Drizzle: a transactional outbox + idempotent inbox</title>
      <dc:creator>Rodrigo Nogueira</dc:creator>
      <pubDate>Fri, 03 Jul 2026 22:00:26 +0000</pubDate>
      <link>https://dev.to/rodrigobnogueira/the-dual-write-problem-in-nestjs-solved-with-drizzle-a-transactional-outbox-idempotent-inbox-462l</link>
      <guid>https://dev.to/rodrigobnogueira/the-dual-write-problem-in-nestjs-solved-with-drizzle-a-transactional-outbox-idempotent-inbox-462l</guid>
      <description>&lt;p&gt;Somewhere in most event-driven backends, there's a method that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&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;PlaceOrderInput&lt;/span&gt;&lt;span class="p"&gt;)&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;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;orders&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;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// 1. write the row&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;kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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;order.placed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 2. publish the event&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks fine, and it has a bug. If the process crashes between 1 and 2, the order exists but the event never happened — downstream consumers silently miss it. Swap the order and you get the opposite failure: an event for an order that was rolled back. There is no &lt;code&gt;try/catch&lt;/code&gt; arrangement that fixes this, because a database and a broker cannot commit atomically. This is the &lt;strong&gt;dual-write problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The boring, proven fix is the &lt;strong&gt;transactional outbox&lt;/strong&gt;: don't publish in step 2. Instead, write the event into an &lt;code&gt;outbox_events&lt;/code&gt; table &lt;em&gt;in the same database transaction&lt;/em&gt; as the business row. A background worker then relays committed rows to the broker. The transaction is the only atomic boundary you have — so put both writes inside it.&lt;/p&gt;

&lt;p&gt;That gives you at-least-once delivery, which means the consumer side needs the mirror-image pattern: an &lt;strong&gt;idempotent inbox&lt;/strong&gt; that deduplicates redeliveries, so the side effect runs exactly once even when Kafka delivers twice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nest-native/messaging" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/messaging&lt;/code&gt;&lt;/a&gt; — part of &lt;a href="https://github.com/nest-native" rel="noopener noreferrer"&gt;nest-native&lt;/a&gt;, a set of NestJS integrations — packages this pair as a library, after the pattern was first built by hand inside a reference application. It's the outbox/inbox pattern for the &lt;strong&gt;Drizzle ORM + NestJS&lt;/strong&gt; stack — a niche the existing NestJS outbox libraries (which target TypeORM and MikroORM — see &lt;a href="https://github.com/fullstackhouse/nestjs-outbox" rel="noopener noreferrer"&gt;nestjs-outbox&lt;/a&gt; and &lt;a href="https://github.com/Nestixis/nestjs-inbox-outbox" rel="noopener noreferrer"&gt;nestjs-inbox-outbox&lt;/a&gt;, both solid) don't cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  The producer half: enqueue inside your transaction
&lt;/h2&gt;

&lt;p&gt;The library ships the tables as Drizzle factories per dialect (SQLite, Postgres, MySQL). You add them to your schema and generate a migration like any other table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// schema.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;outboxEvents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inboxEvents&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-native/messaging/sqlite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or /postgres, /mysql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Transactions ride on &lt;a href="https://www.npmjs.com/package/@nestjs-cls/transactional" rel="noopener noreferrer"&gt;&lt;code&gt;@nestjs-cls/transactional&lt;/code&gt;&lt;/a&gt; with its Drizzle adapter — the same &lt;code&gt;@Transactional()&lt;/code&gt; decorator you'd use anyway:&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;MessagingModule&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;drizzleInstanceToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DRIZZLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// your Drizzle DI token&lt;/span&gt;
  &lt;span class="na"&gt;outboxStore&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;SqliteOutboxStore&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c1"&gt;// or PostgresOutboxStore / MysqlOutboxStore&lt;/span&gt;
  &lt;span class="na"&gt;inboxStore&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;SqliteInboxStore&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                              &lt;span class="c1"&gt;// where the claimer relays to — below&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the business code:&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;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;OrderService&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="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;InjectTransaction&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OutboxProducer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SqliteOutboxStore&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&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;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;orders&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;producer&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;order.placed&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&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;`order:&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="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 order row and the outbox row commit together, or roll back together. A throw after &lt;code&gt;enqueue&lt;/code&gt; produces no phantom event; a crash after commit loses nothing, because the event is durably in your database.&lt;/p&gt;

&lt;p&gt;(One nuance the library handles for you: better-sqlite3 transactions are synchronous while Postgres/MySQL are async. The per-dialect stores own that difference — on SQLite &lt;code&gt;enqueue&lt;/code&gt; returns the row directly inside the sync transaction body; on Postgres you &lt;code&gt;await&lt;/code&gt; it. Same code shape either way.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Relaying: the claimer and the worker
&lt;/h2&gt;

&lt;p&gt;A claimer polls for committed rows, publishes each through a transport, and applies retry-with-backoff on failure — including reclaiming rows from a worker that died mid-flight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/start-worker.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&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;NestFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createApplicationContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runWorkerLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OutboxClaimer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;pollIntervalMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shutdownSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// AbortSignal wired to SIGTERM&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Kafka the transport is one line, built on &lt;a href="https://github.com/nest-native/kafka" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/kafka&lt;/code&gt;&lt;/a&gt; (Confluent's official JS client underneath):&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;MessagingModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRootAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;drizzleInstanceToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DRIZZLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;outboxStore&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;SqliteOutboxStore&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;inboxStore&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;SqliteInboxStore&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;KafkaProducerService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;useTransport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;KafkaOutboxTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;producer&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;Don't have Kafka yet? There's an in-process transport (&lt;code&gt;@nest-native/messaging/in-process&lt;/code&gt;) — a topic→handler registry with the same at-least-once semantics — so a modular monolith can adopt the pattern today and swap the transport for a broker later without touching a line of domain code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The consumer half: exactly-once effects
&lt;/h2&gt;

&lt;p&gt;Kafka is at-least-once by contract, so redelivery is a &lt;em&gt;when&lt;/em&gt;, not an &lt;em&gt;if&lt;/em&gt;. The inbox primitive is a single method:&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;outcome&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;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runOnce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dedupKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&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="c1"&gt;// your side effect — runs in the SAME transaction as the dedup row&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="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// 'processed' on the first delivery, 'duplicate' on any redelivery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;runOnce&lt;/code&gt; inserts a &lt;code&gt;(source, message_key)&lt;/code&gt; row protected by a unique index and runs your side effect in the same transaction. A redelivery violates the index → &lt;code&gt;'duplicate'&lt;/code&gt; → the side effect is skipped. If your side effect throws, the dedup row rolls back with it, so the retry reprocesses cleanly. That composition — unique index + shared transaction — is the entire trick, and it's provable in the database.&lt;/p&gt;

&lt;p&gt;For Kafka consumers the library wraps the full delivery decision (validate → dedup → ack / dead-letter / redeliver) in an engine you delegate to from a thin &lt;code&gt;@KafkaConsumer&lt;/code&gt; shell:&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;KafkaConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order.placed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders-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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderConsumer&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;inbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KafkaInboxConsumer&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;audit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrderAuditService&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;KafkaHandler&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;handle&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;KafkaMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;KafkaHeaders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;KafkaCtx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KafkaContext&lt;/span&gt;&lt;span class="p"&gt;)&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;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consume&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;OrderPlaced&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;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order.placed:orders-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isOrderPlaced&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                   &lt;span class="c1"&gt;// poison message → DLQ, then ack&lt;/span&gt;
      &lt;span class="na"&gt;sideEffect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dedupKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dedupKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;dlqTopic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order.placed.DLQ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poison messages (unparseable, unkeyable) go to a dead-letter topic instead of redelivering forever; transient failures rethrow so the broker redelivers; duplicates ack silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing without a broker
&lt;/h2&gt;

&lt;p&gt;Everything above runs in tests with no infrastructure: an in-memory outbox transport (&lt;code&gt;@nest-native/messaging/testing&lt;/code&gt;) for the producer half, and &lt;code&gt;@nest-native/kafka/testing&lt;/code&gt;'s in-memory broker for the full pipeline — including redelivery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order.placed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publishedMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// redeliver the same message&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;idle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                                 &lt;span class="c1"&gt;// wait for handler pipelines to settle&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auditRows&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&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="c1"&gt;// side effect ran exactly once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library itself is tested at 100% coverage against real SQLite, real in-process Postgres (pglite), and the in-memory broker.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the whole thing running
&lt;/h2&gt;

&lt;p&gt;The pattern is one chapter of a larger, runnable story: the &lt;a href="https://github.com/nest-native/reference-app" rel="noopener noreferrer"&gt;nest-native reference app&lt;/a&gt; is a multi-tenant work-tracking SaaS where every task write emits &lt;code&gt;task.created/assigned/completed&lt;/code&gt; through this outbox, a consumer builds an activity feed through this inbox, the event contracts are published as an &lt;a href="https://github.com/nest-native/asyncapi" rel="noopener noreferrer"&gt;AsyncAPI 3.0 catalog&lt;/a&gt;, and a &lt;a href="https://github.com/nest-native/ai-sdk" rel="noopener noreferrer"&gt;streaming AI assistant&lt;/a&gt; summarizes the activity — six libraries, one coherent journey, green tests, no Docker required for the default profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest scope
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This is the &lt;strong&gt;app-level&lt;/strong&gt; outbox. At larger scale you may prefer CDC (Debezium + Kafka Connect) tailing the WAL — different trade-offs, no app code, more infrastructure.&lt;/li&gt;
&lt;li&gt;If you're on TypeORM or MikroORM, the libraries linked above already serve you well. &lt;code&gt;@nest-native/messaging&lt;/code&gt; exists specifically because nothing covered Drizzle.&lt;/li&gt;
&lt;li&gt;Delivery is at-least-once end to end; the inbox gives you exactly-once &lt;em&gt;effects&lt;/em&gt;, which is the guarantee that actually matters — exactly-once &lt;em&gt;delivery&lt;/em&gt; across a network isn't something any tool can promise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docs: &lt;a href="https://nest-native.dev/messaging/" rel="noopener noreferrer"&gt;nest-native.dev/messaging&lt;/a&gt; · Source: &lt;a href="https://github.com/nest-native/messaging" rel="noopener noreferrer"&gt;github.com/nest-native/messaging&lt;/a&gt;. Feedback and issues welcome — especially if you hit a case the pattern doesn't cover.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>kafka</category>
      <category>database</category>
    </item>
    <item>
      <title>A NestJS reference app that proves six nest-native libraries 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;a href="https://github.com/nest-native/reference-app" rel="noopener noreferrer"&gt;&lt;code&gt;nest-native/reference-app&lt;/code&gt;&lt;/a&gt; is a production-shaped &lt;strong&gt;multi-tenant work-tracking SaaS&lt;/strong&gt; — think a small Linear — that composes &lt;strong&gt;all six &lt;a href="https://nest-native.dev/" rel="noopener noreferrer"&gt;nest-native&lt;/a&gt; libraries&lt;/strong&gt; the way a real product would: Drizzle persistence, a typed tRPC API, domain events emitted &lt;strong&gt;in the same transaction&lt;/strong&gt; as the writes, a Kafka backbone with exactly-once consumer effects, a published AsyncAPI 3.0 catalog, and a streaming AI assistant. Every guarantee has a test. It runs with zero infrastructure by default (SQLite, no broker, offline AI), and the same domain code runs against real Kafka by setting one env var.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What changed since this post first went up
&lt;/h2&gt;

&lt;p&gt;This post originally covered a two-library app (&lt;code&gt;nest-drizzle-native&lt;/code&gt; + &lt;code&gt;nest-trpc-native&lt;/code&gt;). Since then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The libraries moved to the &lt;strong&gt;&lt;code&gt;nest-native&lt;/code&gt; org&lt;/strong&gt; and are published scoped: &lt;a href="https://github.com/nest-native/drizzle" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/drizzle&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/nest-native/trpc" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/trpc&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The family grew to &lt;strong&gt;six packages&lt;/strong&gt; — &lt;a href="https://github.com/nest-native/kafka" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/kafka&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/nest-native/messaging" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/messaging&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/nest-native/asyncapi" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/asyncapi&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/nest-native/ai-sdk" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/ai-sdk&lt;/code&gt;&lt;/a&gt; joined.&lt;/li&gt;
&lt;li&gt;The reference app grew from "prove the wiring" into a &lt;strong&gt;story&lt;/strong&gt;: one product where each library earns its place, and where the reliable-messaging half was battle-tested hard enough that it got extracted &lt;em&gt;into&lt;/em&gt; a library.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything below describes the app as it is today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The org, in one paragraph
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nest-native" rel="noopener noreferrer"&gt;&lt;code&gt;nest-native&lt;/code&gt;&lt;/a&gt; publishes decorator-first NestJS integrations that should &lt;strong&gt;feel like official NestJS packages&lt;/strong&gt; — modules, DI, decorators, guards/interceptors, lifecycle hooks — while staying honest about the tool underneath. Drizzle stays SQL-first; tRPC stays tRPC; the outbox is a real table, not magic. Every package ships with &lt;code&gt;"dependencies": {}&lt;/code&gt; (you install only the peers you use), 100% test coverage as a hard CI gate, runnable samples, and documented non-goals.&lt;/p&gt;

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

&lt;p&gt;Library docs cover their slice; nobody covers the seams. And the seams are where backends actually get built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How does "current user / current organization" reach a tRPC procedure, a guard, and a repository three calls deep — through one request-scoped mechanism?&lt;/li&gt;
&lt;li&gt;How do I write a row &lt;strong&gt;and&lt;/strong&gt; publish an event without a crash window between them?&lt;/li&gt;
&lt;li&gt;How do consumers survive Kafka's at-least-once redelivery without double-applying effects?&lt;/li&gt;
&lt;li&gt;Where do other teams find my event contracts — and how do I keep those contracts from drifting apart across producer, consumer, and docs?&lt;/li&gt;
&lt;li&gt;How do I ship a streaming AI endpoint that tests &lt;strong&gt;offline&lt;/strong&gt;, deterministically, in CI?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reference app answers all of these in one codebase, as a coherent product rather than a pile of demos.&lt;/p&gt;

&lt;h2&gt;
  
  
  The story: one journey through six libraries
&lt;/h2&gt;

&lt;p&gt;The product is a multi-tenant work-tracking SaaS. Follow one journey and every library shows up exactly where a real system would reach for it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An org invites a teammate&lt;/strong&gt; — one &lt;code&gt;@Transactional()&lt;/code&gt; method writes the user, membership, project, and audit rows &lt;em&gt;and&lt;/em&gt; enqueues a &lt;code&gt;user.invited&lt;/code&gt; event, atomically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They work tasks&lt;/strong&gt; — create → assign → complete; each write emits &lt;code&gt;task.created&lt;/code&gt; / &lt;code&gt;task.assigned&lt;/code&gt; / &lt;code&gt;task.completed&lt;/code&gt; in the same transaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events flow over the backbone&lt;/strong&gt; — a background claimer relays committed events (in-process by default; Kafka in production), and consumers build an &lt;strong&gt;activity feed&lt;/strong&gt; read-model, deduplicated against redelivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The contracts are published&lt;/strong&gt; — an AsyncAPI 3.0 catalog at &lt;code&gt;/asyncapi&lt;/code&gt; documents every event, generated from the same Zod schemas the code validates with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI reads the activity&lt;/strong&gt; — a streaming assistant turns a project's recent activity into a status update, token by token.&lt;/li&gt;
&lt;/ol&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 the feature modules (auth, users, projects, tasks, activity, onboarding, audit, outbox, inbox, events-catalog, assistant, trpc, db) via glowing teal paths; two red arcs trace the cross-cutting concerns — transactions and auth request context" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Its job in the story&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@nest-native/drizzle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persistence: repositories (&lt;code&gt;@DrizzleRepository&lt;/code&gt;, &lt;code&gt;@InjectTransaction&lt;/code&gt;), tenant-scoped queries, migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@nest-native/trpc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The typed API: &lt;code&gt;@Router&lt;/code&gt;/&lt;code&gt;@Query&lt;/code&gt;/&lt;code&gt;@Mutation&lt;/code&gt;, generated &lt;code&gt;AppRouter&lt;/code&gt;, superjson, guards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@nest-native/messaging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reliable events: transactional outbox + idempotent inbox on Drizzle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@nest-native/kafka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The backbone: &lt;code&gt;KafkaOutboxTransport&lt;/code&gt; + &lt;code&gt;@KafkaConsumer&lt;/code&gt; read-models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@nest-native/asyncapi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The event catalog other teams integrate against&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@nest-native/ai-sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The streaming assistant (&lt;code&gt;@AiStream&lt;/code&gt;), offline-testable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The central proof: five writes, one transaction, zero lost events
&lt;/h2&gt;

&lt;p&gt;The onboarding workflow is still the crown jewel. &lt;code&gt;inviteUser()&lt;/code&gt; writes five rows — user, membership, project link, audit event, &lt;strong&gt;and the outbox event&lt;/strong&gt; — inside a single &lt;code&gt;@Transactional()&lt;/code&gt; method:&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;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="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="nx"&gt;users&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="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="c1"&gt;// ...audit row...&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="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                        &lt;span class="c1"&gt;// a plain typed interface — no casts&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;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="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;A throw anywhere rolls back &lt;em&gt;everything&lt;/em&gt; — no phantom event. A crash after commit loses nothing — the event is durably in the database. The tests prove both directions (happy path, rollback safety, crash recovery), because "trust me" is not a delivery guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  No lost events, no double effects
&lt;/h2&gt;

&lt;p&gt;The dual-write problem ("write the row, then publish — and pray nothing crashes in between") is solved by &lt;a href="https://github.com/nest-native/messaging" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/messaging&lt;/code&gt;&lt;/a&gt;: the enqueue above is just an insert into an &lt;code&gt;outbox_events&lt;/code&gt; table, and a background &lt;strong&gt;claimer&lt;/strong&gt; relays committed rows with retry/backoff and stuck-claim recovery:&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%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;The consumer half is the mirror image: delivery is at-least-once by contract, so the &lt;strong&gt;idempotent inbox&lt;/strong&gt; dedups redeliveries via a unique &lt;code&gt;(source, message_key)&lt;/code&gt; row written &lt;em&gt;in the same transaction as the side effect&lt;/em&gt; — exactly-once effects, provable in the database. The app's activity feed is built exactly this way, and the test suite redelivers messages on purpose and asserts the side effect ran once.&lt;/p&gt;

&lt;p&gt;Two profiles, one codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default&lt;/strong&gt; — no broker: the outbox dispatches through an in-process topic→handler registry (&lt;code&gt;@nest-native/messaging/in-process&lt;/code&gt;). SQLite in a file. This is what CI runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka&lt;/strong&gt; — set &lt;code&gt;KAFKA_BROKERS&lt;/code&gt; and the same domain code relays through &lt;code&gt;KafkaOutboxTransport&lt;/code&gt;, with &lt;code&gt;@KafkaConsumer&lt;/code&gt;s (via &lt;a href="https://github.com/nest-native/kafka" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/kafka&lt;/code&gt;&lt;/a&gt;, on Confluent's official client) on the other side. Same event bodies, same dedup keys, same wire headers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A typed API that doesn't lie
&lt;/h2&gt;

&lt;p&gt;The tRPC layer (&lt;a href="https://github.com/nest-native/trpc" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/trpc&lt;/code&gt;&lt;/a&gt;) is decorator-first (&lt;code&gt;@Router('tasks')&lt;/code&gt;, &lt;code&gt;@Query&lt;/code&gt;, &lt;code&gt;@Mutation&lt;/code&gt;, Zod I/O) with a generated &lt;code&gt;AppRouter&lt;/code&gt; consumed by a typed client in CI. Three details worth stealing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;superjson end-to-end&lt;/strong&gt; — &lt;code&gt;activity.list&lt;/code&gt; returns real &lt;code&gt;Date&lt;/code&gt; objects, and the generated router carries a type-level transformer marker: a client that forgets to configure superjson &lt;strong&gt;fails to compile&lt;/strong&gt;. Both directions are CI steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation errors clients can use&lt;/strong&gt; — an &lt;code&gt;errorFormatter&lt;/code&gt; flattens &lt;code&gt;ZodError&lt;/code&gt;s so a failing mutation surfaces &lt;code&gt;error.data.zodError.fieldErrors.title&lt;/code&gt; on the typed client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache headers where they're safe&lt;/strong&gt; — &lt;code&gt;responseMeta&lt;/code&gt; sets &lt;code&gt;cache-control&lt;/code&gt; only on the public &lt;code&gt;ping&lt;/code&gt; query; everything tenant-scoped stays uncached, and tests assert both presence and absence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Your events, documented
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nest-native/asyncapi" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/asyncapi&lt;/code&gt;&lt;/a&gt; publishes an &lt;strong&gt;AsyncAPI 3.0&lt;/strong&gt; document at &lt;code&gt;/asyncapi&lt;/code&gt; (plus &lt;code&gt;-json&lt;/code&gt;/&lt;code&gt;-yaml&lt;/code&gt;) describing all four domain events. The payload schemas are &lt;strong&gt;defined once&lt;/strong&gt; as Zod objects — the same definitions derive the TypeScript types (&lt;code&gt;z.infer&lt;/code&gt;), the runtime guards (&lt;code&gt;safeParse&lt;/code&gt;), and the catalog schemas. Producer, consumer, and documentation cannot drift apart, and the document validates against &lt;code&gt;@asyncapi/parser&lt;/code&gt; in the test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI over your data, tested offline
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;POST /projects/:id/assistant&lt;/code&gt; streams a status update summarizing the project's recent activity, via &lt;a href="https://github.com/nest-native/ai-sdk" rel="noopener noreferrer"&gt;&lt;code&gt;@nest-native/ai-sdk&lt;/code&gt;&lt;/a&gt;'s &lt;code&gt;@AiStream&lt;/code&gt; (SSE, abort on client disconnect). By default it streams from an &lt;strong&gt;offline mock model&lt;/strong&gt; built from the real activity digest — deterministic, no API key, CI-safe. Set &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; and a real provider swaps in with no code change. (Adopting the AI SDK is also why the app requires &lt;strong&gt;Node ≥ 22&lt;/strong&gt; — &lt;code&gt;ai@7&lt;/code&gt; demands it.)&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;reference-app
nvm use            &lt;span class="c"&gt;# Node &amp;gt;= 22&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 npm run start:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;tRPC at &lt;code&gt;/trpc&lt;/code&gt;, health at &lt;code&gt;/health&lt;/code&gt;, the AsyncAPI catalog at &lt;code&gt;/asyncapi&lt;/code&gt;, the AI assistant at &lt;code&gt;POST /projects/1/assistant&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Seed login: &lt;code&gt;admin@acme.test&lt;/code&gt; / &lt;code&gt;admin123!&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The outbox worker runs as its own process: &lt;code&gt;npm run start:worker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No Docker, no broker, no API keys required for any of it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The loop that makes it honest
&lt;/h2&gt;

&lt;p&gt;The app isn't just a showcase — it's the &lt;strong&gt;feedback loop&lt;/strong&gt; for the libraries, and it has teeth. Recent examples, all shipped upstream because building this app surfaced them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The in-process outbox transport was hand-rolled here first, proved generic, and was &lt;strong&gt;extracted into&lt;/strong&gt; &lt;code&gt;@nest-native/messaging/in-process&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The app's hand-written AI mock became &lt;code&gt;@nest-native/ai-sdk/testing&lt;/code&gt; (&lt;code&gt;createMockLanguageModel&lt;/code&gt;) — and deleted every copy-pasted mock in the family.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enqueue()&lt;/code&gt; becoming generic (killing &lt;code&gt;as unknown as Record&amp;lt;string, unknown&amp;gt;&lt;/code&gt; casts), the in-memory Kafka broker gaining an awaitable &lt;code&gt;idle()&lt;/code&gt; (killing &lt;code&gt;sleep(50)&lt;/code&gt; in tests), and AsyncAPI accepting Zod schemas directly — all of it started as friction in this codebase.&lt;/li&gt;
&lt;li&gt;The app's install even caught a broken peer-dependency range in a fresh release before any user hit it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If something feels awkward here, that's treated as a library bug — fixed upstream, in its own PR, with its own tests.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a starter kit or boilerplate&lt;/strong&gt; — it optimizes for being read, not forked. Copy patterns, not the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not 100%-covered&lt;/strong&gt; — the 100% bar belongs to the libraries; the app's suite is pragmatic and targets the guarantees (rollback, crash recovery, dedup, catalog validity, stream framing).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a CDC system&lt;/strong&gt; — the outbox is the app-level answer; Debezium-style log tailing is a different trade-off and an explicit non-goal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Not affiliated with the NestJS core team.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;The app: &lt;a href="https://github.com/nest-native/reference-app" rel="noopener noreferrer"&gt;github.com/nest-native/reference-app&lt;/a&gt; — start with the README's story, then &lt;code&gt;docs/architecture.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The org + all six packages: &lt;a href="https://github.com/nest-native" rel="noopener noreferrer"&gt;github.com/nest-native&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs hub: &lt;a href="https://nest-native.dev/" rel="noopener noreferrer"&gt;nest-native.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback, issues, and "this seam is still awkward" reports are the most useful thing you can send — that's literally the mechanism this whole stack improves by.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>drizzle</category>
      <category>trpc</category>
    </item>
    <item>
      <title>Introducing @nest-native/drizzle: 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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Renamed:&lt;/strong&gt; this project was originally published as &lt;code&gt;nest-drizzle-native&lt;/code&gt; and is now the scoped &lt;strong&gt;&lt;code&gt;@nest-native/drizzle&lt;/code&gt;&lt;/strong&gt; (repo: &lt;a href="https://github.com/nest-native/drizzle" rel="noopener noreferrer"&gt;&lt;code&gt;nest-native/drizzle&lt;/code&gt;&lt;/a&gt;). Same library, same public API — only the package name changed. To migrate: &lt;code&gt;npm uninstall nest-drizzle-native &amp;amp;&amp;amp; npm install @nest-native/drizzle&lt;/code&gt;, then update imports to &lt;code&gt;@nest-native/drizzle&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;@nest-native/drizzle&lt;/strong&gt; is 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-native/drizzle&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-native/drizzle&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-native/drizzle&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 to avoid re-creating 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.dev/drizzle/" rel="noopener noreferrer"&gt;https://nest-native.dev/drizzle/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/nest-native/drizzle" rel="noopener noreferrer"&gt;https://github.com/nest-native/drizzle&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-native/drizzle" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@nest-native/drizzle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Releases:&lt;/strong&gt; &lt;a href="https://github.com/nest-native/drizzle/releases/latest" rel="noopener noreferrer"&gt;https://github.com/nest-native/drizzle/releases/latest&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Community feedback from NestJS and Drizzle users is highly encourage — 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>
