Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
YouTube Security VideosGoogle Cloud Tech: Gemini is coming to your city(24.09.2026 um 15:00 Uhr)
AI & KI NachrichtenGoogle’s latest moonshot to put machine learning in space(24.09.2026 um 15:12 Uhr)
Windows Tipps & SecurityPoll: What's your favorite Surface of 2026?(24.09.2026 um 14:58 Uhr)
Sichere ProgrammierungStreaming Materialized Views for Live Read Models (2026)(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA Day Is Not 86400 Seconds: The DST Bug in Your Date Math(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungSetting up Traefik: reverse proxy with automatic HTTPS(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA 200 OK response does not prove a secret leak(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungHow hot do you like it?(24.09.2026 um 15:05 Uhr)
YouTube Security VideosGoogle Cloud Tech: Gemini is coming to your city(24.09.2026 um 15:00 Uhr)
AI & KI NachrichtenGoogle’s latest moonshot to put machine learning in space(24.09.2026 um 15:12 Uhr)
Windows Tipps & SecurityPoll: What's your favorite Surface of 2026?(24.09.2026 um 14:58 Uhr)
Sichere ProgrammierungStreaming Materialized Views for Live Read Models (2026)(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA Day Is Not 86400 Seconds: The DST Bug in Your Date Math(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungSetting up Traefik: reverse proxy with automatic HTTPS(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungA 200 OK response does not prove a secret leak(24.09.2026 um 15:02 Uhr)
Sichere ProgrammierungHow hot do you like it?(24.09.2026 um 15:05 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

The dual-write problem in NestJS, solved with Drizzle: a transactional outbox + idempotent inbox

Every event-driven backend eventually writes this method: async placeOrder(input: PlaceOrderInput) { await this.db.insert(orders).values(input); // 1. write the row await this.kafka.send({ topic: 'order.placed', … }); // 2. …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Every event-driven backend eventually writes this method:




async placeOrder(input: PlaceOrderInput) {
await this.db.insert(orders).values(input); // 1. write the row
await this.kafka.send({ topic: 'order.placed', }); // 2. publish the event
}






And every one of them has the same 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 try/catch arrangement that fixes this, because a database and a broker cannot commit atomically. This is the dual-write problem.



The boring, proven fix is the transactional outbox: don't publish in step 2. Instead, write the event into an outbox_events table in the same database transaction 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.



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



I maintain nest-native, a set of NestJS integrations, and after implementing this pair by hand inside a reference application I extracted it into a library: @nest-native/messaging. It's the outbox/inbox pattern for the Drizzle ORM + NestJS stack — a niche the existing NestJS outbox libraries (which target TypeORM and MikroORM — see nestjs-outbox and nestjs-inbox-outbox, both solid) don't cover.






The producer half: enqueue inside your transaction



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:




// schema.ts
export { outboxEvents, inboxEvents } from '@nest-native/messaging/sqlite'; // or /postgres, /mysql






Transactions ride on @nestjs-cls/transactional with its Drizzle adapter — the same @Transactional() decorator you'd use anyway:




MessagingModule.forRoot({
drizzleInstanceToken: DRIZZLE, // your Drizzle DI token
outboxStore: new SqliteOutboxStore(), // or PostgresOutboxStore / MysqlOutboxStore
inboxStore: new SqliteInboxStore(),
transport, // where the claimer relays to — below
}),






Then the business code:




@Injectable()
export class OrderService {
constructor(
@InjectTransaction() private readonly db: AppDatabase,
private readonly producer: OutboxProducer<SqliteOutboxStore>,
) {}

@Transactional()
placeOrder(id: string, item: string) {
this.db.insert(orders).values({ id, item }).run();
this.producer.enqueue({
topic: 'order.placed',
payload: { id, item },
idempotencyKey: `order:${id}`,
});
}
}






The order row and the outbox row commit together, or roll back together. A throw after enqueue produces no phantom event; a crash after commit loses nothing, because the event is durably in your database.



(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 enqueue returns the row directly inside the sync transaction body; on Postgres you await it. Same code shape either way.)






Relaying: the claimer and the worker



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:




// scripts/start-worker.ts
const app = await NestFactory.createApplicationContext(AppModule);
await runWorkerLoop(app.get(OutboxClaimer), {
pollIntervalMs: 2_000,
signal: shutdownSignal, // AbortSignal wired to SIGTERM
});






For Kafka the transport is one line, built on @nest-native/kafka (Confluent's official JS client underneath):




MessagingModule.forRootAsync({
drizzleInstanceToken: DRIZZLE,
outboxStore: new SqliteOutboxStore(),
inboxStore: new SqliteInboxStore(),
inject: [KafkaProducerService],
useTransport: (producer) => new KafkaOutboxTransport(producer),
}),






Don't have Kafka yet? There's an in-process transport (@nest-native/messaging/in-process) — 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.






The consumer half: exactly-once effects



Kafka is at-least-once by contract, so redelivery is a when, not an if. The inbox primitive is a single method:




const outcome = await inbox.runOnce(dedupKey, source, () => {
// your side effect — runs in the SAME transaction as the dedup row
this.audit.record({ });
});
// 'processed' on the first delivery, 'duplicate' on any redelivery






runOnce inserts a (source, message_key) row protected by a unique index and runs your side effect in the same transaction. A redelivery violates the index → 'duplicate' → 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.



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 @KafkaConsumer shell:




@KafkaConsumer('order.placed', { groupId: 'orders-service' })
export class OrderConsumer {
constructor(private readonly inbox: KafkaInboxConsumer, private readonly audit: OrderAuditService) {}

@KafkaHandler()
async handle(@KafkaMessage() payload: unknown, @KafkaHeaders() headers: Headers, @KafkaCtx() ctx: KafkaContext) {
await this.inbox.consume<OrderPlaced>({
source: 'order.placed:orders-service',
context: ctx, headers, payload,
validate: isOrderPlaced, // poison message → DLQ, then ack
sideEffect: (order, dedupKey) => this.audit.record(order, dedupKey),
dlqTopic: 'order.placed.DLQ',
});
}
}






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






Testing without a broker



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




await broker.emit('order.placed', publishedMessage); // redeliver the same message
await broker.idle(); // wait for handler pipelines to settle
expect(auditRows).toHaveLength(1); // side effect ran exactly once






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






See the whole thing running



The pattern is one chapter of a larger, runnable story: the nest-native reference app is a multi-tenant work-tracking SaaS where every task write emits task.created/assigned/completed through this outbox, a consumer builds an activity feed through this inbox, the event contracts are published as an AsyncAPI 3.0 catalog, and a streaming AI assistant summarizes the activity — six libraries, one coherent journey, green tests, no Docker required for the default profile.






Honest scope




  • This is the app-level outbox. At larger scale you may prefer CDC (Debezium + Kafka Connect) tailing the WAL — different trade-offs, no app code, more infrastructure.

  • If you're on TypeORM or MikroORM, the libraries linked above already serve you well. @nest-native/messaging exists specifically because nothing covered Drizzle.

  • Delivery is at-least-once end to end; the inbox gives you exactly-once effects, which is the guarantee that actually matters — nothing gives you exactly-once delivery.



Docs: nest-native.dev/messaging · Source: github.com/nest-native/messaging. Feedback and issues welcome — especially if you hit a case the pattern doesn't cover.

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
SOC Incident Playbook: Remote Code Execution (RCE) Defense
title: Detect Exploitation - The dual-write problem in NestJS, solved with Drizzle: a transactional outbox + idempotent inbox
id: 6bdf6089-99d2-4240-b387-d55c3a1b5071
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "The dual-write problem in Nest" ascii wide
    condition:
        any of them
}
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich The dual-write problem in NestJS, solved.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten The dual-write problem in NestJS, solved with Drizzle: a transactional outbox + idempotent inbox

Thematisch verwandte Begriffe: dualwrite, problem, NestJS, solved · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-97152 | Nanomsg versions 0.5-beta through 1.x before 1.2.3 has a remotely exploi…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick