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
Locationheader - 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.