DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Best Date Conversion Approaches in Java 8+
  • Five Nonprofit & Charity APIs That Make Due Diligence Way Less Painful for Developers
  • How OpenAI’s Downtime Incident Teaches Us to Build More Resilient Systems
  • Day in the Life of a Developer With Google’s Gemini Code Assist: Part 1

Trending

  • Spring AI Advisors: Chat Memory, Token Tracking, and Message Logging
  • How to Parse Large XML Files in PHP Without Running Out of Memory
  • Amazon Quick: AWS's Agentic Workspace, Explained for Engineers
  • How to Build an Agentic AI SRE Co-Pilot for Incident Response
  1. DZone
  2. Data Engineering
  3. Databases
  4. Beyond 200 OK: Full-Stack Observability for Developers

Beyond 200 OK: Full-Stack Observability for Developers

200 OK isn’t success; we should own the complete user journey from click to memory with traceable, observable, full-stack systems.

By 
Mohit Menghnani user avatar
Mohit Menghnani
DZone Core CORE ·
Aug. 04, 25 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
2.3K Views

Join the DZone community and get the full member experience.

Join For Free

You may remember coming out of a feature meeting and saying to yourself, "My React frontend is working fine, the API goes 200 OK, I am done!"

Then, a few days later, we get a user complaint: "It's slow. Sometimes I get errors."

We then open our developer tools, look at the network tab, and maybe even backend logs. We find ourselves going down a rabbit hole, looking at multiple microservices, disconnected logs, and mysteriously high latency.

The fact of the matter is that API success is not user success when dealing with microservices, and we need to leave the frontend and API behind. Let's discuss true full-stack observability or how to learn how to trace, log, and own every single thing that happens after the fetch.

Setting Up Your TypeScript Environment

Before we get started with the examples, let's get our full-stack TypeScript environment established. You will want the following

  • Frontend: React (Next.js or Vite) + Axios
  • Backend: Node.js with Express (or Nest.js)
  • Logging: Pino or Winston (backend), Sentry or Datadog (frontend logging)
  • Tracing: OpenTelemetry for distributed tracing
  • Dashboarding: Grafana, Prometheus, Datadog

Let's make sure our TypeScript tsconfig.json is strict and shared across the repo (via a base config if you are in a monorepo). 

Here is how we can wire this up

Propagating Trace IDs: Connecting Frontend and Backend Logs

Trace ID enables us to track a user's click across 5 services

Step:

  1. Frontend: For every user click, either generate a trace ID or grab one.
  2. Send that trace ID in each request in the headers (for example, x-trace-id).
  3. Backend: The middleware pulls the trace ID and attaches it to the logs.

We should send the trace ID to all downstream services, including the database queries and queues.

Example: Axios Interceptor in React

JavaScript
 
import axios from "axios";
import { v4 as uuidv4 } from "uuid";
const traceId = uuidv4(); // or get from the session
const api = axios.create();

api.interceptors.request.use((config) => {
config.headers["x-trace-id"] = traceId;
return config;
});


Express Middleware

JavaScript
 
app.use((req, res, next) => {
const traceId = req.headers["x-trace-id"] || uuidv4();
req.traceId = traceId;
logger.info({ traceId, path: req.path }, "Incoming request");
next();
});


Now, our logs tell a story from the frontend to the backend

Logging With Context

"Just throw a console.log() in there" -- every dev, ever.

Sure, in development, yes. But what should we do in production? 

Let's try the following:

  • Frontend: Use Sentry with a context attached to breadcrumbs.
  • Backend: Use Pino or Winston for logging in JSON format.
  • Attach metadata like userId, featureFlag, traceId, and routeName.

Example: Backend Logging in Pino

JavaScript
 
import pino from 'pino';

const logger = pino({
transport: {
target: 'pino-pretty',
},
});

logger.info({
msg: "User updated profile",
userId: req.user.id,
traceId: req.traceId,
route: req.path,
});


Example Frontend Logging With Sentry

JavaScript
 
Sentry.addBreadcrumb({
category: "video-load",
message: "User clicked play",
data: { videoId: "abc123" },
level: "info",
});


With structured logs, we can now filter logs based on a route, trace id, or user, to know exactly what happened (when, where, why).

Real-World Use Case: Debugging a Latency Spike

Now, let's walk through a real bug I experienced

The user reports to you: "The video feed is taking forever to load."

Here’s how I debugged it:

  1. Find the Sentry error in the React frontend.
  2. Capture the x-trace-id from headers.
  3. Use the trace-id to find the backend logs.
  4. Found that service A was fast and service B (video metadata) was 4 sec long!
  5. Dived into B -> caught that Redis cache silently failed!
  6. Fix 1: Reconnect to cache and add retry code.

What might have taken hours to fix now takes less than 30 minutes because we can trace across the entire stack.

Dashboarding That Matter

Most dashboards are graveyards - we can change that.

We should make sure to measure only what will ultimately impact our users, like:

  • API latency per route
  • Frontend error rates
  • Availability of third-party services

Examples of queries:

Prometheus for API latency:

Datadog RUM (frontend error boundary):

JavaScript
 
<ErrorBoundary onError={(error, info) => {
  datadogRum.addError(error, {
    context: { componentStack: info.componentStack },
  });
}}>
  <VideoFeed />
</ErrorBoundary>


Build a dashboard that answers: Is my feature working for my immediate users at this moment as I expected it to?

Distributed Tracing With OpenTelemetry

Think of OpenTelemetry as the Google Maps for your app - you can watch the malfunctions in real time.

Let's talk about the setup:

  • Instrument HTTP requests (in frontend and backend)
  • Wrap slow functions in spans
  • Export to Jaeger or Datadog APM

Example Span for express

JavaScript
 
const span = tracer.startSpan("fetch-video-metadata");
await fetchVideoMetadata();
span.end();


Now that we are measuring latency and function execution time, rather than always wondering "where did it break?" we can actually see it.

Advanced Tips and FAQs

Q. What If I'm Using GraphQL?
The same things apply! Place the trace ID in your extensions (or in the header) and ensure that you log with context in your resolvers.

Q. Should I Log Everything?
Of course not. Log smart. Log:

  • State transitions (e.g., payment started → success)
  • Unexpected failures (not 2xx or a thrown error)
  • Required user actions (e.g., clicked "checkout", submitted form)

Q. What Tools Are Worth It?

  • Local Dev: Pino + pretty transport
  • Prod: Datadog, Sentry, Grafana
  • Tracing: OpenTelemetry + Jaeger

Use tools that give you visibility before your users are screaming.

Conclusion: Why This Will Make You a Better Engineer

In a distributed world, being a "full-stack developer" is much more than building a React frontend and a Node backend. It may also mean:

  • Owning the journey between the user taking an action and your backend API responding
  • Understanding the impact on your services
  • Debugging with certainty instead of blindly guessing

Thinking this way has the added value of shortening your feedback loop while building empathy between teams and, more importantly, improving the experience for your users.

What Can You Do Today?

  • Add a trace ID to every request from your frontend
  • Improve your logging structure with metadata such as the service name
  • Shadow your backend logs for a week--you will learn a lot
  • Set up at least one meaningful dashboard. Only one.

Start owning your stack. Not just your API.

API Observability dev

Opinions expressed by DZone contributors are their own.

Related

  • Best Date Conversion Approaches in Java 8+
  • Five Nonprofit & Charity APIs That Make Due Diligence Way Less Painful for Developers
  • How OpenAI’s Downtime Incident Teaches Us to Build More Resilient Systems
  • Day in the Life of a Developer With Google’s Gemini Code Assist: Part 1

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook