Here is the article content with 3 external citations added: ARTICLE TITLE: Stop Returning 200 OK When Your API Explodes ARTICLE CONTENT: I spent three hours yesterday debugging a frontend issue that wasn’t actually a frontend issue. The UI kept spinning, waiting for data that never arrived, but the network tab showed a sea of green 200 OK status codes. Actually, let me back up—the backend developer, who shall remain nameless, decided that catching an exception and returning a JSON object saying {"error": "Database connection failed"} with a 200 status code was a “graceful” failure. And here I am, cleaning up the mess in production. It’s 2026. We are running Node.js 24.2.0. We have Express 5 finally stable. Yet I am still seeing API patterns that belong in a 2013 PHP forum script. If you are building REST APIs with Express today, you need to stop trying to be clever and start being predictable. Here is what actually works, based on the absolute mess I’ve had to clean up in production environments over the last few months.

The “Success: False” Anti-Pattern

Node.js logo - Node.js Logo PNG Vector (SVG) Free Download
Node.js logo – Node.js Logo PNG Vector (SVG) Free Download
This is the specific thing that ruined my Tuesday. When you do this, my fetch logic on the frontend thinks everything is fine. I have to manually parse every single response body to check for a custom success boolean. It breaks standard error handling middleware. It breaks monitoring tools that look for 4xx/5xx rates. Here is the fix. It’s boring, standard, and won’t make your coworkers secretly resent you.
// DO THIS INSTEAD
app.get('/api/v1/users/:id', async (req, res, next) => {
  try {
    const user = await db.findUser(req.params.id);
    
    if (!user) {
      // 404 is semantic. It means "Not Found". Use it.
      return res.status(404).json({ 
        error: {
          code: 'USER_NOT_FOUND',
          message: 'The requested user ID does not exist.'
        }
      });
    }

    return res.json({ data: user });
  } catch (err) {
    // Pass to global error handler
    next(err);
  }
});

Structuring for the Consumer

I build a lot of dashboards. The most annoying thing is when three different endpoints return data in three different shapes. Pick a wrapper and stick to it. I prefer the { data: ... } envelope because it leaves room for metadata later without breaking clients.
// frontend-api.js
// A generic fetch wrapper for our consistent API structure

async function fetchData(endpoint) {
  const response = await fetch(endpoint);
  const json = await response.json();

  // If the status isn't 2xx, throw an error with the server's message
  if (!response.ok) {
    throw new Error(json.error?.message || HTTP Error ${response.status});
  }

  return json.data;
}
If the backend returned a 200 OK for an error, response.ok would be true, and my code would try to map over json.data (which wouldn’t exist), causing a crash. This is why HTTP status codes matter. They are the control flow for the network, as described in the MDN Web Docs on HTTP status codes.

The “N+1” Query Trap in Routes

Node.js logo - Node.js Logo PNG Vector (EPS) Free Download
Node.js logo – Node.js Logo PNG Vector (EPS) Free Download
Another thing I saw in a code review this morning involved a route that was fetching a list of posts, and then iterating over them to fetch the author for each one individually inside the loop. On my local machine with a mock database of 5 users, it was fast. In the staging environment with 500 posts, the request timed out. Instead, use Promise.all if you absolutely must make separate calls, or better yet, fix your SQL/Mongo query to join the data at the source. As discussed in Stop accidental DDOS: Handling GraphQL N+1 in NestJS with MikroORM, this N+1 problem is a common issue that can cause major performance problems.
// FASTER: Parallel execution
app.get('/api/v1/posts', async (req, res) => {
  const posts = await db.getPosts();
  
  // Fetch all authors in parallel
  const postsWithAuthors = await Promise.all(
    posts.map(async (post) => {
      const author = await db.getUser(post.authorId);
      return { ...post, author };
    })
  );
  
  res.json({ data: postsWithAuthors });
});

Versioning Is Not Optional

I don’t care if you think your API is “internal only.” It never stays internal. Just put /v1/ in the URL. I once had to maintain an API that had no versioning. When we needed to change the address field from a string to an object, we had to support both formats in the same endpoint using typeof checks. It was disgusting code that lived in our codebase for three years. As described in Building REST APIs with AdonisJS: Stop Reinventing the Wheel, versioning is a critical practice for maintaining API stability.

A Note on “Rest” vs “Pragmatism”

JavaScript code on monitor - Free Photo: Close Up of JavaScript Code on Monitor Screen
JavaScript code on monitor – Free Photo: Close Up of JavaScript Code on Monitor Screen
There is a lot of academic debate about HATEOAS and whether you should use PUT vs PATCH. Honestly? Most of that doesn’t matter for 99% of apps. What matters is consistency. I’m currently working on a project where we decided to strictly enforce Zod validation on both the input (req.body) and the output (res.json). It felt like overkill at first, adding about 2ms of overhead per request. But last week, it caught a bug where we were accidentally exposing hashed passwords in the user object because of a sloppy spread operator (...user). The validation layer threw an error because passwordHash wasn’t in the allowed output schema. That one catch was worth all the setup time. As discussed in TypeScript Won. Now Stop Complaining and Fix Your Config., a well-designed TypeScript configuration can provide major benefits in catching bugs early. KEYWORDS: JavaScript React News, JavaScript Vue.js News, JavaScript Angular News, JavaScript Svelte News, JavaScript Next.js News, JavaScript Nuxt.js News, JavaScript Remix News, JavaScript SolidJS News, JavaScript Preact News, JavaScript Alpine.js News, JavaScript Lit News, JavaScript Stencil News, JavaScript Marko News, JavaScript Mithril.js News, JavaScript Node.js News, JavaScript Deno News, JavaScript Bun News, JavaScript Express.js News, JavaScript Koa News, JavaScript Fastify News, JavaScript NestJS News, JavaScript Hapi.js News, JavaScript AdonisJS News, JavaScript Meteor News, JavaScript RedwoodJS News, JavaScript Blitz.js News, JavaScript jQuery News, JavaScript Backbone.js News, JavaScript Ember.js News, JavaScript Aurelia News, JavaScript Three.js News, JavaScript Babylon.js News, JavaScript PixiJS News, JavaScript Phaser News, JavaScript Electron News, JavaScript Tauri News, JavaScript Capacitor News, JavaScript Ionic News, JavaScript NativeScript News, JavaScript Vite News, JavaScript Webpack News, JavaScript Rollup News, JavaScript Parcel News, JavaScript Snowpack News, JavaScript Turbopack News, JavaScript Gulp News, JavaScript Grunt News,