Implement a Common API Response Structure in ASP.NET Core


Building APIs in ASP.NET Core becomes much easier to maintain when all endpoints return a consistent response structure.

A common API response format helps:

  • Frontend developers consume APIs more easily
  • Standardize success and error responses
  • Improve debugging and logging
  • Simplify API documentation
  • Create cleaner enterprise-grade applications

Why Use a Common API Response Structure?

Without a standard format, APIs often return inconsistent responses like:

{
  "name": "John"
}

or

{
  "message": "User created successfully"
}

or

{
  "error": "Something went wrong"
}

This inconsistency creates problems for frontend applications and API consumers.

Instead, use a unified response structure like:

{
  "success": true,
  "message": "User fetched successfully",
  "data": {
    "id": 1,
    "name": "John"
  },
  "errors": null
}

Benefits of Standardized API Responses

A common structure provides:

Benefit Description
Consistency Every endpoint behaves similarly
Better Frontend Integration Easier parsing on client side
Improved Debugging Centralized error handling
Scalability Easier to maintain large projects
Cleaner Documentation Predictable API contracts

Step 1: Create a Generic API Response Model

Create a folder named Models and add the following class.

ApiResponse.cs

namespace DemoAPI.Models
{
    public class ApiResponse<T>
    {
        public bool Success { get; set; }

        public string Message { get; set; }

        public T Data { get; set; }

        public List<string> Errors { get; set; }

        public ApiResponse()
        {
            Errors = new List<string>();
        }
    }
}

Understanding the Properties

Property Purpose
Success Indicates request success/failure
Message Human-readable response message
Data Actual response payload
Errors Validation or exception details

Using generics (T) makes this reusable for all APIs.

Step 2: Create a Sample DTO

UserDto.cs

namespace DemoAPI.Models
{
    public class UserDto
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public string Email { get; set; }
    }
}

Step 3: Implement in Controller

UsersController.cs

using DemoAPI.Models;
using Microsoft.AspNetCore.Mvc;

namespace DemoAPI.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class UsersController : ControllerBase
    {
        [HttpGet("{id}")]
        public IActionResult GetUser(int id)
        {
            var user = new UserDto
            {
                Id = id,
                Name = "John Doe",
                Email = "john@example.com"
            };

            var response = new ApiResponse<UserDto>
            {
                Success = true,
                Message = "User fetched successfully",
                Data = user
            };

            return Ok(response);
        }
    }
}

Sample Success Response

{
  "success": true,
  "message": "User fetched successfully",
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "errors": []
}

Step 4: Handle Error Responses

Example Error Response

[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
    if (id <= 0)
    {
        var errorResponse = new ApiResponse<object>
        {
            Success = false,
            Message = "Invalid user id",
            Errors = new List<string>
            {
                "User id must be greater than zero"
            }
        };

        return BadRequest(errorResponse);
    }

    return Ok();
}

Sample Error Output

{
  "success": false,
  "message": "Invalid user id",
  "data": null,
  "errors": [
    "User id must be greater than zero"
  ]
}

Step 5: Create a Reusable Response Helper (Optional)

To reduce repetitive code, create a helper class.

ApiResponseHelper.cs

namespace DemoAPI.Helpers
{
    public static class ApiResponseHelper
    {
        public static ApiResponse<T> Success<T>(
            T data,
            string message = "Request successful")
        {
            return new ApiResponse<T>
            {
                Success = true,
                Message = message,
                Data = data
            };
        }

        public static ApiResponse<T> Fail<T>(
            string message,
            List<string> errors = null)
        {
            return new ApiResponse<T>
            {
                Success = false,
                Message = message,
                Errors = errors ?? new List<string>()
            };
        }
    }
}

Cleaner Controller Code

[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
    if (id <= 0)
    {
        return BadRequest(
            ApiResponseHelper.Fail<object>(
                "Invalid user id",
                new List<string>
                {
                    "Id must be greater than zero"
                }));
    }

    var user = new UserDto
    {
        Id = id,
        Name = "John Doe",
        Email = "john@example.com"
    };

    return Ok(
        ApiResponseHelper.Success(
            user,
            "User fetched successfully"));
}

Step 6: Global Exception Handling

For production-grade applications, use middleware for centralized exception handling.

ExceptionMiddleware.cs

using System.Net;
using System.Text.Json;
using DemoAPI.Models;

namespace DemoAPI.Middleware
{
    public class ExceptionMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<ExceptionMiddleware> _logger;

        public ExceptionMiddleware(
            RequestDelegate next,
            ILogger<ExceptionMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);

                context.Response.ContentType = "application/json";
                context.Response.StatusCode =
                    (int)HttpStatusCode.InternalServerError;

                var response = new ApiResponse<object>
                {
                    Success = false,
                    Message = "Internal server error",
                    Errors = new List<string>
                    {
                        ex.Message
                    }
                };

                var json = JsonSerializer.Serialize(response);

                await context.Response.WriteAsync(json);
            }
        }
    }
}

Register Middleware

In Program.cs:

app.UseMiddleware<ExceptionMiddleware>();

Final API Response Standard

A robust enterprise response model typically includes:

{
  "success": true,
  "message": "Operation completed successfully",
  "data": {},
  "errors": [],
  "statusCode": 200,
  "timestamp": "2026-05-16T10:00:00Z"
}

You can extend your response model with:

  • Status codes
  • Pagination metadata
  • Correlation IDs
  • Request tracking IDs
  • Timestamps

Best Practices

  • Keep Responses Predictable
    • Every endpoint should follow the same structure.
  • Avoid Returning Raw Exceptions
    • Never expose sensitive internal details in production.
  • Use Proper HTTP Status Codes
    • Examples:
      • 200 OK
      • 201 Created
      • 400 Bad Request
      • 401 Unauthorized
      • 404 Not Found
      • 500 Internal Server Error
  • Use DTOs
    • Avoid exposing database entities directly.
  • Implement Validation
    • Use FluentValidation or DataAnnotations.

Common Mistakes to Avoid

Mistake Problem
Returning inconsistent JSON Hard for frontend parsing
Exposing stack traces Security risk
Using exceptions for validation Poor performance
Returning HTTP 200 for errors Misleading API behavior
0 Comments Report