DEV Community

Kamil Mysliwiec for NestJS

Posted on

NestJS Observe vs Sentry, Datadog and OpenTelemetry: which one for a NestJS app?

One disclosure up front: I created NestJS and my team builds Observe, so this comparison has a clear point of view. The statements about the other tools were checked against their documentation in September 2026; those details can change, so verify them for your own setup.

Every monitoring tool can trace a NestJS app. Only one of them was built from inside the framework, and that changes what a trace can show you. That's the short version of this whole post.

The four realistic options for a Nest backend:

  1. NestJS Observe, the framework's own
  2. Sentry, which is error tracking first with performance monitoring grown around it
  3. A full-platform APM like Datadog, New Relic or Dynatrace
  4. OpenTelemetry plus a backend: Grafana's stack, SigNoz, Jaeger, or any vendor that ingests OTLP

The short answer

If your backend is NestJS, Observe is a practical starting point: it is quick to set up and shows application classes and methods without hand-written spans. Add or switch tools when one of these requirements applies:

If you also... Then
need browser or mobile errors and session replay add Sentry for the frontend
run several languages and want them in one trace, or need to watch infrastructure a full-platform APM
must keep telemetry in your own network, or need portable, vendor-neutral instrumentation OpenTelemetry

The rest of the post is the reasoning, starting with the difference that matters most.

The same slow request, in each tool

POST /orders takes 1.8 seconds. The controller calls OrdersService, which loads the customer, asks PricingService for a quote, and has PaymentsService charge a card. Here's what each tool shows you.

A generic Node.js agent (any platform APM, or OpenTelemetry's auto-instrumentation) sees the process from the outside. The HTTP server, the router under Nest, the database driver, the outbound call.

POST /orders                          1,812 ms
  express.middleware  (x6)                4 ms
  pg.query  SELECT customers             11 ms
  http.request  POST api.stripe.com   1,747 ms
Enter fullscreen mode Exit fullscreen mode

That view is accurate and may be enough for this particular case. (OpenTelemetry's Nest instrumentation adds two more per request: one for the request context and one for the route handler.) It does not identify work inside your application, though. If those 1.7 seconds move from Stripe into a loop inside PricingService, the trace still shows a 1.8-second request without identifying the loop.

Sentry goes further into Nest than the generic agents do. Its SDK adds spans for guards, pipes, interceptors and the route handler. So now you see the pipeline around your code, but still not the services behind the controller, and that's usually where the time is.

Observe shows the application you wrote:

POST /orders                          1,812 ms
  AuthGuard.canActivate                   3 ms
  OrdersController.create             1,806 ms   self 1 ms
    OrdersService.create              1,805 ms   self 6 ms
      CustomersService.findById          12 ms
        SELECT customers                 11 ms
      PricingService.quote               38 ms
      PaymentsService.charge          1,749 ms
        POST api.stripe.com           1,747 ms
Enter fullscreen mode Exit fullscreen mode

No span was added manually for PricingService.quote; every provider method that runs during the request is a span. That attributes self time to your own classes and allows the data to be aggregated into a ranking of classes by time. The other options can provide this depth with additional service-layer instrumentation, which then has to be maintained.

NestJS Observe waterfall

Why generic agents don't do this

Nest creates every provider through its DI container and exposes a hook at exactly that moment: instrument.instanceDecorator, an option of NestFactory.create(). Observe uses it to wrap each controller, service, guard, interceptor and pipe the container creates. (Instances you create inline, like @UsePipes(new MyPipe()), bypass the container and get no span.)

The hook is public, so anyone could build on it. Generic Node agents don't: they patch libraries they know by name, like Express or pg, and have no way of knowing that PricingService exists. The closest thing out there is a community package for Datadog that wraps providers. It's not the vendor's agent, and you maintain it.

This also explains the setup. There's nothing to preload: your own classes are handed over by the container, and the few libraries we do patch (the database drivers and the queue clients) are patched on their prototypes when the module starts, so load order doesn't matter:

// app.module.ts
export const { ObserveModule, ObserveInstrument } = createObserveModule();

@Module({
  imports: [
    ObserveModule.forRoot({
      appKey: process.env.OBSERVE_APP_KEY!,
      appSecret: process.env.OBSERVE_APP_SECRET!,
      serviceId: 'orders-api',
    }),
  ],
})
export class AppModule {}

// main.ts
const app = await NestFactory.create(AppModule, { instrument: ObserveInstrument });
Enter fullscreen mode Exit fullscreen mode

One package, two env variables, one option (NestFactory.createMicroservice() takes the same one). No instrument file that has to be imported first, no agent process, no collector, no exporter.

What else Observe does

Knowing the framework is also why the rest needs no config. HTTP, microservices and GraphQL come in through hooks in Nest's adapters; BullMQ, Bull, @nestjs/schedule and gateways through the Nest integration packages, which we also maintain. So the agent knows what a queue consumer, a @Cron() handler, a @SubscribeMessage() and a GraphQL resolver are.

Database queries and outbound HTTP show up inside the trace, under the method that made the call. Queries go through pg, mysql2 and mongodb, so TypeORM, Drizzle, MikroORM, Mongoose and Prisma's pg driver adapter work untouched. Outbound HTTP is read from Node's diagnostics channels, so fetch, axios and SDKs built on node:http are covered without patching anything. We record statement shapes only, never values. And these spans aren't billed.

Queues, cron, GraphQL, microservices and WebSocket gateways are each recorded as what they are, not as a strange looking HTTP request. A BullMQ or Bull job shares a trace with the request that enqueued it, automatically (producer and worker on 0.3 or later; scheduled jobs and FlowProducer flows start their own trace).

Errors are split into handled and unhandled (a thrown NotFoundException is your API working, not an incident): by status code over HTTP, and by whether it's one of Nest's intrinsic exceptions elsewhere. They're grouped into defects, they come with the failing source line, and you get an email the first time a new unhandled one appears. On every plan.

You can capture the input of failed or slow requests, redacted inside your process, with bodies being opt-in. There's a service map drawn from real traffic. And there's an MCP server, so your coding agent can investigate a failure with the same data you'd use.

Pricing is simple and per event: every request, job, error, log line and span from your own code is one event, while query and outbound HTTP spans are free. Free covers 300,000 events a month with 3 days of retention and a hard cap. Pro is $29 a month for 25 million events and 30 days. Scale is $99 for 200 million and 90 days. Overage on those two is $0.60 and $0.40 per extra million. Error monitoring, tracing and auto-instrumentation are on every plan, and so are trace ids in your ConsoleLogger lines; log forwarding and alert rules start on Pro. I'm not going to quote other vendors' prices here, because they change and because per-host, per-seat and per-gigabyte models don't convert cleanly. But do price your own traffic on each of them before you decide. I'm comfortable with how that comparison comes out.

Where the others are the better choice

Observe does one thing, NestJS backends, and outside of that the alternatives are simply better.

Sentry

What it's best at: errors. Sentry has had over a decade to get error tracking right and it shows. Grouping, release health, suspect commits, source map handling, a mature issue workflow, and integrations with everything: GitHub, Jira, Linear, Slack, PagerDuty. It covers browsers, mobile and backends in one product, with session replay on the frontend. You can self-host it.

For NestJS there's an official @sentry/nestjs SDK. It uses OpenTelemetry under the hood, which means an instrumentation file that has to be imported before anything else, plus SentryModule and a global filter. Out of the box you get spans for incoming and outgoing HTTP, for database calls through the common drivers (pg, mysql2, MongoDB and Mongoose, ioredis, GraphQL, while Prisma needs an extra integration), and for Nest's request pipeline: middleware, guards, pipes, interceptors, filters and route handlers, as long as they have @Injectable() on them. That's further into Nest than any generic agent goes.

Where it's weaker: Sentry's centre of gravity is the error, not the structure of your application. Performance data is there, but "which of my providers holds the time across all traffic" isn't what it was designed around. Your service layer only shows up in a trace where you add spans yourself.

Pick it when errors across a web or mobile frontend and a backend are your main concern, or when you need its integrations and workflow.

Full-platform APMs (Datadog, New Relic, Dynatrace and friends)

What they're best at: breadth. Application traces next to host metrics, containers and Kubernetes, database monitoring, log management, synthetic checks, real-user monitoring, security signals, all correlated, across every language you run. Their auto-instrumentation covers a huge range of drivers and clients. For a big organisation with a platform team this is the right kind of tool, and nothing in this post competes with it.

For NestJS these agents instrument Node.js generically: the HTTP server and the router underneath Nest (Express or Fastify), database and cache drivers, outbound calls, and more and more often queues (Datadog's tracer lists BullMQ as supported). Nest itself gets less attention. It's not among the frameworks Datadog's Node.js tracer lists, and their docs mention it only as a framework with a custom entry point that needs the tracer preloaded. New Relic's agent recognises Nest for naming transactions. You get an accurate trace that's well connected to the infrastructure around it, written in the vocabulary of libraries (express.middleware, pg.query) and not of your application. To see OrdersService.create you add spans yourself, or you reach for a community package. nestjs-ddtrace, for example, can wrap controllers and providers in Datadog spans.

Where they can be a less natural fit is cost and operational weight. There may be an agent or collector to deploy, pricing may combine hosts, ingested volume, and retention, and the product surface is large enough to need an owner. For a small team with three Nest services, that can be more platform than the immediate problem requires.

Pick one when you have more than one language, real infrastructure to watch next to the application, and the budget and people to run it.

OpenTelemetry plus a backend

What it's best at: independence. Instrument once with an open standard, send the data anywhere, change your mind later without touching application code. The collector can run inside your network, so telemetry never has to leave it. Library coverage is the widest of any option and every serious vendor ingests it.

For NestJS the auto-instrumentation bundle covers HTTP, the common drivers and Nest's entry points. Same as with the agents above, your own providers are yours to instrument.

Where it can require more effort is that it is a toolkit rather than a single product. You choose and run the SDK, instrumentations, exporter, collector, and backend, then build the dashboards and alerts and own the upgrades. Each step is manageable, but together they require sustained ownership. I wrote a whole post about this trade-off: Why we didn't just use OpenTelemetry.

Pick it when vendor-neutrality, data residency or a polyglot stack is a requirement and not a preference.

What Observe doesn't do

So nothing surprises you later.

It's NestJS on Node.js only. A Go or Python service in the same request path won't show up in the trace, and services are correlated with a request id header, not W3C traceparent (automatic over HTTP; over microservice transports you carry the id in the payload yourself). There's no frontend SDK and no infrastructure monitoring (it reports the process's own runtime metrics, not your hosts or your database server). It's hosted and it's not an open standard: no self-hosted edition, no OTLP in or out today. Library coverage is narrow on purpose. Prisma on its own query engine, Redis and Kafka client calls don't appear as spans, and cloud SDK calls appear only as the HTTP requests they make (POST s3.amazonaws.com), not as named operations. Your own @MessagePattern() handlers on a Kafka or Redis transport are recorded; the clients underneath aren't. Anything else goes through the manual span API. And alert rules (Pro and up) go to email, Slack, a webhook or the built-in issue tracker; the free plan gets the new-error email only. There's no native PagerDuty or Opsgenie integration yet.

It launched in 2026, so it's younger than everything else on this page. It's also built and maintained by the same people who build the framework, and it runs on NestJS 11.1+ and 12, Node 20.19+, with Express or Fastify. When Nest changes, we're the ones changing it, and we're the ones keeping the agent working.

Side by side

NestJS Observe Sentry Full-platform APM OpenTelemetry + backend
Time to first useful trace Minutes An hour or two Hours to days, plus an agent Days
Setup One package, one module, one option Preloaded instrument file, module, filter Agent plus preloaded tracer SDK, exporter, collector, backend
Spans for your own providers, automatically Yes No, manual spans Not built in (community package for Datadog) No, manual spans
Self time per class across all traffic, without manual spans Yes No No No
Spans for Nest's pipeline (guards, pipes, interceptors) Yes Yes No No
Queue job in the same trace as the request that enqueued it Automatic (BullMQ, Bull; not flows or scheduled jobs) Check per queue library Datadog lists BullMQ, check others Depends on what you add
Database queries and outbound HTTP in traces Yes, and not billed Yes (also Redis, GraphQL) Yes (widest) Yes (widest)
Something to operate Nothing Nothing Agent Collector and backend
Error grouping and workflow Good Best in class Good Depends on backend
Breadth of library instrumentation Narrow Wide Widest Widest
Frontend / mobile / session replay No Yes Yes Partly
Infrastructure monitoring No No Yes With more components
Languages NestJS only Many Many Many
Open standard / portable No OTel-based tracing OTLP ingest, proprietary agents Yes
Self-hostable No Yes No Yes
On-call integrations Email, Slack, webhook (alert rules on Pro and up) Many Many Depends on backend

They work well together

Since Observe is narrow, it sits nicely next to the others.

Observe for the NestJS backend and Sentry for the frontend: provider-level depth where your logic lives, browser errors and replay where Observe has nothing to offer. Observe for the application layer and a platform APM for infrastructure: the generic agent's view stops at the router, and that's where Observe's starts. Or Observe this week and OpenTelemetry as the long-term standard. One takes an afternoon and the other takes a quarter, and nothing stops you from doing both.

If two agents hook the same database driver, test the combination in staging before using it in production.

How I'd decide in ten minutes

Is your backend NestJS, and is the current state basically "we have logs"? Install Observe today. It's free to start and takes minutes, and everything below can still happen later.

Is your biggest pain browser or mobile errors? Add Sentry for the frontend.

Is something in the request path not NestJS, and does it have to be in the same trace? Then a platform APM or OpenTelemetry. Observe alone won't cover it.

Does telemetry have to stay in your network, or does the instrumentation have to be portable? OpenTelemetry.

Do you have infrastructure to watch and a team to run the tooling? A platform APM, with Observe next to it if you want to see inside your services.

The worst choice is the one that stays on the backlog. And for a NestJS team, the shortest way off the backlog is the thing the framework's own team built for exactly that.

You can look at real traces, errors and the service map without signing up: the NestJS Observe demo. Spotted something wrong about another tool here? support@nestjs.com and I'll correct it.

Top comments (0)