2 min read

HTTP Status Codes Aren’t Optional: Stop Using 200 for Everything

If you’ve ever written an API where everything returns `200 OK`, this article explains why that’s a bad idea and encourages using proper HTTP status codes to make your API easier to debug, maintain, and work with.

https://giphy.com/gifs/hoppip-loki-movie-hoppip-bN4Gf6GEs9OtW
https://giphy.com/gifs/hoppip-loki-movie-hoppip-bN4Gf6GEs9OtW

If you’ve ever written an API where everything returns 200 OK, I’ve got bad news: your frontend devs are quietly cursing your name.

HTTP already gives you a rich set of status codes. Use them properly, and your API becomes way easier to debug, maintain, and work with.


Rule #1: Use the Right Status Code for the Job

Here’s your status code starter pack:

CodeUse When
200Request was successful
201Resource created (after POST)
204No content to return (DELETE, etc)
400Bad request (client messed up)
401Unauthorized (missing/invalid auth)
403Forbidden (valid auth, no access)
404Resource not found
409Conflict (e.g., duplicate entries)
500Server error (you messed up)

Example: Using Real Status Codes (TypeScript + Express)

example.ts
app.post('/users', (req, res) => {
  const { name } = req.body;
  if (!name) {
    return res.status(400).json({
      error: {
        code: 'NAME_REQUIRED',
        message: 'Name field is required.'
      }
    });
  }
 
  const user = createUser(name);
  res.status(201).json(user);
});

This tells the frontend exactly what went wrong — and uses the proper 201 Created when things go right.


Rule #2: Structure Your Errors

Your error responses should be as consistent as your success responses.

Recommended format:

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested user does not exist."
  }
}

✔️ Easy to parse
✔️ Friendly for internationalization
✔️ Works great with logs and metrics


Example: Standardized 404 Response

example.ts
app.get('/users/:id', (req, res) => {
  const user = findUserById(req.params.id);
  if (!user) {
    return res.status(404).json({
      error: {
        code: 'USER_NOT_FOUND',
        message: `No user found with ID ${req.params.id}`
      }
    });
  }
  res.json(user);
});

Rule #3: Handle Query Parameters with Grace

For filtering, sorting, and pagination, always use query params — but make sure to validate them.

Example:

GET /posts?author=alex&sort=latest&page=2&pageSize=10
example.ts
app.get('/posts', (req, res) => {
  const { author, sort = 'newest', page = 1, pageSize = 10 } = req.query;
  const posts = getFilteredPosts({ author, sort, page, pageSize });
  res.json(posts);
});

🔧 Use default values
🔒 Sanitize inputs
🧼 Keep responses consistent


Recap

  • Use status codes like you mean it — don’t abuse 200
  • Error responses should be structured, not chaos in JSON form
  • Query params are your friends — just keep them clean and validated

Coming Up Next:

Part 4: “REST API Pro Tips: Headers, HATEOAS, and When to Break the Rules”
We’ll dig into headers, how much REST is too much REST, and where you can break the rules like a pro.