Mert Özen Further With Every Line
Home Blog HTTP Status Codes and Returning the Right Response: IActionResult and Results
Backend .NET Core Web API — #05

HTTP Status Codes and Returning the Right Response: IActionResult and Results

Mert Özen Jul 25, 2026 11 min 4 views

We cover HTTP status codes and the ways to return the right response. Which code for which situation, the difference between IActionResult and Results, and small details that help API consumers.

In the previous article we wrote our first controller and used helpers like Ok and NotFound in passing while returning responses. Today we focus on the logic behind those helpers, namely HTTP status codes. Because returning the right data is half the job; returning that data with the right status code is the other half. The detail that makes an API professional is often hidden exactly here.

Why Does the Status Code Matter So Much?

Every HTTP response starts with a three-digit number. This number summarizes the outcome of the request at a glance. The side making the request can understand what happened just by looking at this number, without needing to read the body of the response. Was the operation successful, was the thing they were looking for not found, or is there a problem with the data they sent; it's all hidden in this small number.

Think about this: a mobile app sent a request to your API and you returned 200 in every case, even when there was an error. Then how will the app understand that something went wrong? It would be forced to look inside the response and parse the text, which is both fragile and tedious. Whereas if you had returned 404, the app could say "resource not found" in a single line. Status codes are a shared and clear language between the sides.

The Most Frequently Used Status Codes

There are dozens of status codes, but in daily life a handful of them cover most of your work. The codes are divided into logical groups: those starting with 2 mean success, those starting with 4 mean client error, and those starting with 5 mean server error. The ones you'll encounter most are these:

200 OK — The request is successful, here's your response. You'll return this most often when you fetch data.

201 Created — A new resource was created. This is the right one especially after POST requests.

204 No Content — The operation is successful but there's no data to return. Often used after delete operations.

400 Bad Request — There's a problem with the data the client sent. Like a missing field or wrong format.

404 Not Found — The requested resource doesn't exist. Returned when a non-existent id is requested.

500 Internal Server Error — An unexpected error occurred on the server side. You usually don't return this by hand; .NET returns it automatically when something blows up.

The Helpers ControllerBase Offers

The good news is: you don't need to set these codes by hand. Because we inherit from the ControllerBase class, we have an easy-to-read helper method for each status code at our fingertips. Let's see with a small example; a method that fetches a user by their id and returns a proper response if it can't find one:

[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    if (id <= 0)
    {
        return BadRequest("Invalid id value.");
    }

    var user = _users.FirstOrDefault(u => u.Id == id);

    if (user is null)
    {
        return NotFound();
    }

    return Ok(user);
}

This method can follow three different paths. If the id is invalid it returns 400, if the user doesn't exist it returns 404, and if everything's fine it returns 200 with the data. Notice that the code itself reads almost like a sentence: "if the id is less than zero return a bad request, if the user isn't found return not found, otherwise say okay." This is how the right status codes make code so readable.

Returning 201 Created Correctly

When you create a new resource, returning just 201 isn't enough; a good API also tells you where the created resource can be found. There's the CreatedAtAction helper for this. In a method that adds a new user, it looks like this:

[HttpPost]
public IActionResult Create(User newUser)
{
    _users.Add(newUser);

    return CreatedAtAction(
        nameof(GetById),
        new { id = newUser.Id },
        newUser);
}

Three things happen here. The status code is set to 201, the created user is added to the body of the response, and it's written into the response's header from which address this new user can be fetched. So the side using the API gets the information "I created the user, here's the address where you can reach it" ready-made. A small detail, but it seriously eases the work of whoever uses the API.

IActionResult or Results?

Until now we've always used IActionResult. But in .NET there's also an approach called Results that you'll see frequently, especially on the Minimal API side. Both do the same job: producing a response containing the right status code and data. The difference is more about where you use it and how it's written.

While inside a controller, you call methods like Ok, NotFound, and BadRequest directly; these come from ControllerBase. On the Minimal API side, since you're not a controller, you access these helpers through the Results class: like Results.Ok(...), Results.NotFound(). In short, the same idea at two different addresses. In this series, which goes the controller-based route, we'll mainly use the former, but it's good to know both so you don't feel like a stranger when Results comes up.

A Common Mistake

There's a trap beginners fall into a lot: returning 200 for everything. The code runs, the data comes, and everything seems fine. But an API that returns 200 even when a resource isn't found misleads the side using it. If "found" and "not found" give the same signal, the other side can never build the right behavior. So adopt this habit from the start: when returning each response, pause for a moment and ask "which is the right code for this situation?" This small reflex is one of the differences that carries you from amateur to professional.

A Small Experiment

Let's return to the UsersController left over from the previous article. Keep a small user list on hand (a fixed array of a few names is enough). Then make the GetById method three-way, like in the example above: 400 for an invalid id, 404 for a non-existent user, 200 for a found one. Try all three scenarios from the browser or an API testing tool and observe the returned status codes. The same endpoint returning different codes depending on the situation is the most fundamental behavior of a solid API.

In the next article we'll get into model binding: we'll see step by step how .NET automatically maps the data inside an incoming request (from the route, from the query string, from the body) into your method parameters. The status codes you learned today are exactly the foundation you need to return the right response while processing that data.