Section 4 of 6

HTTP Status Codes

🎯 What You'll Learn

  • HTTP status code categories (1xx-5xx)
  • Common status codes and their meanings
  • When to use each status code
  • How to return status codes in ASP.NET Core
  • Best practices for API responses
  • Complete InvenTrack examples

Status Code Categories

Range Category Meaning
1xx Informational Request received, continuing process
2xx Success Request successfully received and processed
3xx Redirection Further action needed to complete request
4xx Client Error Request contains bad syntax or cannot be fulfilled
5xx Server Error Server failed to fulfill a valid request

2xx Success Codes

Code Name When to Use ASP.NET Core Method
200 OK Successful GET, PUT, PATCH Ok(data)
201 Created Resource successfully created (POST) Created(uri, data)
204 No Content Successful DELETE, PUT (no response body) NoContent()

Examples

200 OK C#
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
    var product = await _productService.GetByIdAsync(id);
    return Ok(product); // 200 OK
}
201 Created C#
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto dto)
{
    var product = await _productService.CreateAsync(dto);
    
    return CreatedAtAction(
        nameof(GetProduct),
        new { id = product.Id },
        product); // 201 Created + Location header
}
204 No Content C#
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
    await _productService.DeleteAsync(id);
    return NoContent(); // 204 No Content
}

4xx Client Error Codes

Code Name When to Use ASP.NET Core Method
400 Bad Request Invalid request data/validation failed BadRequest()
401 Unauthorized Not authenticated (missing/invalid token) Unauthorized()
403 Forbidden Authenticated but not authorized Forbid()
404 Not Found Resource doesn't exist NotFound()
409 Conflict Resource conflict (duplicate, version mismatch) Conflict()
422 Unprocessable Entity Validation error (semantic issues) UnprocessableEntity()

Examples

400 Bad Request C#
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto dto)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState); // 400 Bad Request
    
    // ...
}
401 Unauthorized vs 403 Forbidden C#
[HttpGet("admin")]
public IActionResult GetAdminData()
{
    // 401: User not logged in
    if (!User.Identity.IsAuthenticated)
        return Unauthorized(); // 401
    
    // 403: User logged in but not an admin
    if (!User.IsInRole("Admin"))
        return Forbid(); // 403
    
    return Ok("Admin data");
}
404 Not Found C#
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
    var product = await _productService.GetByIdAsync(id);
    
    if (product == null)
        return NotFound(); // 404 Not Found
    
    return Ok(product);
}
409 Conflict C#
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto dto)
{
    var exists = await _productService.ExistsAsync(dto.Sku);
    
    if (exists)
        return Conflict(new { message = "Product with this SKU already exists" }); // 409
    
    // ...
}
ℹ️ 401 vs 403

401 Unauthorized: "Who are you?" (not logged in)
403 Forbidden: "I know who you are, but you can't do this" (insufficient permissions)

5xx Server Error Codes

Code Name When to Use ASP.NET Core Method
500 Internal Server Error Unhandled exception StatusCode(500)
502 Bad Gateway Invalid response from upstream server StatusCode(502)
503 Service Unavailable Server temporarily unavailable StatusCode(503)
⚠️ 5xx Errors

5xx errors indicate server problems, not client problems. These should be logged and monitored. Use exception handling middleware to catch and return 500 errors.

Complete InvenTrack Example

Controllers/ProductsController.cs C#
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    // 200 OK
    [HttpGet]
    public async Task<IActionResult> GetProducts()
    {
        var products = await _productService.GetAllAsync();
        return Ok(products); // 200
    }

    // 200 OK or 404 Not Found
    [HttpGet("{id}")]
    public async Task<IActionResult> GetProduct(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        
        if (product == null)
            return NotFound(new { message = $"Product with ID {id} not found" }); // 404
        
        return Ok(product); // 200
    }

    // 201 Created, 400 Bad Request, or 409 Conflict
    [HttpPost]
    public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto dto)
    {
        // 400: Validation failed
        if (!ModelState.IsValid)
            return BadRequest(ModelState); // 400
        
        // 409: Duplicate SKU
        var exists = await _productService.ExistsAsync(dto.Sku);
        if (exists)
            return Conflict(new { message = "Product with this SKU already exists" }); // 409
        
        // 201: Success
        var product = await _productService.CreateAsync(dto);
        
        return CreatedAtAction(
            nameof(GetProduct),
            new { id = product.Id },
            product); // 201
    }

    // 204 No Content, 400 Bad Request, or 404 Not Found
    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateProduct(
        int id,
        [FromBody] UpdateProductDto dto)
    {
        // 400: Validation failed
        if (!ModelState.IsValid)
            return BadRequest(ModelState); // 400
        
        // 404: Product not found
        var success = await _productService.UpdateAsync(id, dto);
        if (!success)
            return NotFound(); // 404
        
        // 204: Success
        return NoContent(); // 204
    }

    // 204 No Content or 404 Not Found
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteProduct(int id)
    {
        var success = await _productService.DeleteAsync(id);
        
        if (!success)
            return NotFound(); // 404
        
        return NoContent(); // 204
    }

    // 401 Unauthorized or 403 Forbidden
    [HttpGet("admin/stats")]
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> GetAdminStats()
    {
        // [Authorize] handles 401/403 automatically
        var stats = await _productService.GetStatsAsync();
        return Ok(stats);
    }
}

Best Practices

  • Use the right code: Don't return 200 for errors
  • Be consistent: Use the same codes for similar scenarios
  • Include error details: Return helpful error messages
  • 201 Created: Always include Location header
  • 204 No Content: Don't return a body
  • 400 vs 422: 400 for syntax errors, 422 for semantic errors
  • 401 vs 403: 401 for authentication, 403 for authorization
  • Log 5xx errors: Server errors should be monitored
  • Don't expose internals: Generic 500 messages in production

Key Takeaways

  • 2xx: Success (200 OK, 201 Created, 204 No Content)
  • 4xx: Client error (400, 401, 403, 404, 409)
  • 5xx: Server error (500, 502, 503)
  • 200 OK: Successful GET, PUT, PATCH
  • 201 Created: Successful POST (with Location header)
  • 204 No Content: Successful DELETE, PUT (no body)
  • 400 Bad Request: Validation failed
  • 401 Unauthorized: Not authenticated
  • 403 Forbidden: Not authorized
  • 404 Not Found: Resource doesn't exist
  • 409 Conflict: Resource conflict
  • 500 Internal Server Error: Unhandled exception
🎯 Next Steps

You now understand HTTP status codes! In the next section, we'll explore Headers and Content Types—how to send and receive different data formats, custom headers, and content negotiation.