🎯

api-response-patterns

🎯Skill

from thapaliyabikendra/ai-artifacts

VibeIndex|
What it does

api-response-patterns skill from thapaliyabikendra/ai-artifacts

api-response-patterns

Installation

Install skill:
npx skills add https://github.com/thapaliyabikendra/ai-artifacts --skill api-response-patterns
1
AddedJan 27, 2026

Skill Details

SKILL.md

"API response wrapper patterns for consistent, predictable REST APIs in ABP Framework. Use when: (1) designing uniform API response contracts, (2) implementing success/error response wrappers, (3) handling pagination and metadata, (4) standardizing error responses."

Overview

# API Response Patterns

Master API response wrapper patterns for building consistent, predictable REST APIs that clients love.

When to Use This Skill

  • Designing uniform API response contracts
  • Implementing success/error response wrappers
  • Standardizing error response formats
  • Adding metadata to API responses (pagination, timing, version)
  • Building APIs consumed by multiple client types (web, mobile, third-party)

Why Response Wrappers?

| Benefit | Description |

|---------|-------------|

| Consistency | All endpoints return same structure |

| Predictability | Clients know what to expect |

| Metadata | Include timing, pagination, version info |

| Error Handling | Uniform error format across all endpoints |

| Debugging | Include correlation IDs for tracing |

Response Wrapper Patterns

1. Basic ResponseModel

```csharp

// Application.Contracts/Models/ResponseModel.cs

public class ResponseModel

{

public bool IsSuccess { get; set; }

public T Data { get; set; }

public string Message { get; set; }

public List Errors { get; set; } = new();

protected ResponseModel() { }

protected ResponseModel(bool isSuccess, T data, string message, List errors)

{

IsSuccess = isSuccess;

Data = data;

Message = message;

Errors = errors ?? new List();

}

public static ResponseModel Success(T data, string message = null)

=> new(true, data, message, null);

public static ResponseModel Failure(string message)

=> new(false, default, message, new List { message });

public static ResponseModel Failure(List errors)

=> new(false, default, errors.FirstOrDefault(), errors);

}

// Non-generic version for operations without return data

public class ResponseModel : ResponseModel

{

public static ResponseModel Success(string message = null)

=> new() { IsSuccess = true, Message = message };

public new static ResponseModel Failure(string message)

=> new() { IsSuccess = false, Message = message, Errors = new List { message } };

}

```

2. Extended ApiResponse with Metadata

```csharp

// Application.Contracts/Models/ApiResponse.cs

public class ApiResponse

{

public bool Success { get; set; }

public T Data { get; set; }

public string Message { get; set; }

public ApiResponseMeta Meta { get; set; }

public List Errors { get; set; } = new();

public static ApiResponse Ok(T data, string message = null) => new()

{

Success = true,

Data = data,

Message = message,

Meta = new ApiResponseMeta()

};

public static ApiResponse Fail(string message, string code = null) => new()

{

Success = false,

Message = message,

Errors = new List

{

new ApiError { Code = code ?? "ERROR", Message = message }

},

Meta = new ApiResponseMeta()

};

public static ApiResponse Fail(List errors) => new()

{

Success = false,

Message = errors.FirstOrDefault()?.Message,

Errors = errors,

Meta = new ApiResponseMeta()

};

}

public class ApiResponseMeta

{

public string Version { get; set; } = "1.0";

public DateTime Timestamp { get; set; } = DateTime.UtcNow;

public string RequestId { get; set; } = Guid.NewGuid().ToString("N")[..8];

public long? ProcessingTimeMs { get; set; }

}

public class ApiError

{

public string Code { get; set; }

public string Message { get; set; }

public string Field { get; set; }

public object Details { get; set; }

}

```

3. Paginated Response

```csharp

// Application.Contracts/Models/PagedApiResponse.cs

public class PagedApiResponse : ApiResponse>

{

public PaginationInfo Pagination { get; set; }

public static PagedApiResponse Ok(

List items,

long totalCount,

int pageNumber,

int pageSize,

string message = null) => new()

{

Success = true,

Data = items,

Message = message,

Meta = new ApiResponseMeta(),

Pagination = new PaginationInfo

{

TotalCount = totalCount,

PageNumber = pageNumber,

PageSize = pageSize,

TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),

HasPreviousPage = pageNumber > 1,

HasNextPage = pageNumber * pageSize < totalCount

}

};

}

public class PaginationInfo

{

public long TotalCount { get; set; }

public int PageNumber { get; set; }

public int PageSize { get; set; }

public int TotalPages { get; set; }

public bool HasPreviousPage { get; set; }

public bool HasNextPage { get; set; }

}

```

Usage in AppServices

1. Basic CRUD Operations

```csharp

public class ProductAppService : ApplicationService, IProductAppService

{

private readonly IRepository _productRepository;

private readonly ILogger _logger;

public async Task> GetAsync(Guid id)

{

var product = await _productRepository.FirstOrDefaultAsync(x => x.Id == id);

if (product == null)

{

return ApiResponse.Fail(

"Product not found",

"PRODUCT_NOT_FOUND");

}

var dto = ObjectMapper.Map(product);

return ApiResponse.Ok(dto);

}

public async Task> GetListAsync(

PagedAndSortedResultRequestDto input,

ProductFilter filter)

{

var queryable = await _productRepository.GetQueryableAsync();

var query = queryable

.WhereIf(!filter.Name.IsNullOrWhiteSpace(),

x => x.Name.Contains(filter.Name))

.WhereIf(filter.CategoryId.HasValue,

x => x.CategoryId == filter.CategoryId);

var totalCount = await AsyncExecuter.CountAsync(query);

var products = await AsyncExecuter.ToListAsync(

query

.OrderBy(input.Sorting ?? "Name")

.PageBy(input.SkipCount, input.MaxResultCount));

var dtos = ObjectMapper.Map, List>(products);

return PagedApiResponse.Ok(

dtos,

totalCount,

pageNumber: (input.SkipCount / input.MaxResultCount) + 1,

pageSize: input.MaxResultCount);

}

public async Task> CreateAsync(CreateProductDto input)

{

try

{

// Validate uniqueness

var exists = await _productRepository.AnyAsync(

x => x.ProductCode == input.ProductCode);

if (exists)

{

return ApiResponse.Fail(

$"Product code '{input.ProductCode}' already exists",

"DUPLICATE_PRODUCT_CODE");

}

var product = new Product(

GuidGenerator.Create(),

input.ProductCode,

input.Name,

input.Price);

await _productRepository.InsertAsync(product);

var dto = ObjectMapper.Map(product);

return ApiResponse.Ok(dto, "Product created successfully");

}

catch (Exception ex)

{

_logger.LogError(ex, "Failed to create product");

return ApiResponse.Fail(ex.Message, "CREATE_FAILED");

}

}

public async Task> DeleteAsync(Guid id)

{

var product = await _productRepository.FirstOrDefaultAsync(x => x.Id == id);

if (product == null)

{

return ApiResponse.Fail("Product not found", "NOT_FOUND");

}

await _productRepository.DeleteAsync(product);

return ApiResponse.Ok(true, "Product deleted successfully");

}

}

```

2. Bulk Operations

```csharp

public async Task> BulkDeleteAsync(List ids)

{

var products = await _productRepository.GetListAsync(x => ids.Contains(x.Id));

if (!products.Any())

{

return ApiResponse.Fail(

"No products found",

"NOT_FOUND");

}

await _productRepository.DeleteManyAsync(products);

var result = new BulkOperationResult

{

RequestedCount = ids.Count,

ProcessedCount = products.Count,

SkippedCount = ids.Count - products.Count

};

return ApiResponse.Ok(

result,

$"Deleted {products.Count} of {ids.Count} products");

}

public class BulkOperationResult

{

public int RequestedCount { get; set; }

public int ProcessedCount { get; set; }

public int SkippedCount { get; set; }

public List SkippedIds { get; set; } = new();

}

```

Error Response Patterns

1. Validation Error Response

```csharp

public class ValidationErrorResponse

{

public bool Success => false;

public string Message => "Validation failed";

public string Code => "VALIDATION_ERROR";

public List Errors { get; set; } = new();

public static ValidationErrorResponse Create(List errors) => new()

{

Errors = errors

};

}

public class FieldError

{

public string Field { get; set; }

public string Message { get; set; }

public object AttemptedValue { get; set; }

public FieldError() { }

public FieldError(string field, string message, object attemptedValue = null)

{

Field = field;

Message = message;

AttemptedValue = attemptedValue;

}

}

```

2. Exception Filter for Consistent Errors

```csharp

public class ApiExceptionFilter : IExceptionFilter

{

private readonly ILogger _logger;

public ApiExceptionFilter(ILogger logger)

{

_logger = logger;

}

public void OnException(ExceptionContext context)

{

var response = context.Exception switch

{

EntityNotFoundException ex => CreateNotFoundResponse(ex),

AbpValidationException ex => CreateValidationResponse(ex),

BusinessException ex => CreateBusinessErrorResponse(ex),

UnauthorizedAccessException => CreateUnauthorizedResponse(),

_ => CreateInternalErrorResponse(context.Exception)

};

context.Result = new ObjectResult(response)

{

StatusCode = GetStatusCode(context.Exception)

};

context.ExceptionHandled = true;

}

private int GetStatusCode(Exception ex) => ex switch

{

EntityNotFoundException => 404,

AbpValidationException => 400,

BusinessException => 422,

UnauthorizedAccessException => 401,

_ => 500

};

private ApiResponse CreateNotFoundResponse(EntityNotFoundException ex) =>

ApiResponse.Fail(ex.Message, "NOT_FOUND");

private ApiResponse CreateValidationResponse(AbpValidationException ex) =>

ApiResponse.Fail(

ex.ValidationErrors.Select(e => new ApiError

{

Code = "VALIDATION_ERROR",

Field = e.MemberNames.FirstOrDefault(),

Message = e.ErrorMessage

}).ToList());

private ApiResponse CreateBusinessErrorResponse(BusinessException ex) =>

ApiResponse.Fail(ex.Message, ex.Code ?? "BUSINESS_ERROR");

private ApiResponse CreateUnauthorizedResponse() =>

ApiResponse.Fail("Unauthorized access", "UNAUTHORIZED");

private ApiResponse CreateInternalErrorResponse(Exception ex)

{

_logger.LogError(ex, "Unhandled exception");

return ApiResponse.Fail("An internal error occurred", "INTERNAL_ERROR");

}

}

```

Controller Patterns

1. Standard Controller

```csharp

[ApiController]

[Route("api/products")]

public class ProductController : AbpController

{

private readonly IProductAppService _productAppService;

public ProductController(IProductAppService productAppService)

{

_productAppService = productAppService;

}

[HttpGet("{id}")]

[ProducesResponseType(typeof(ApiResponse), 200)]

[ProducesResponseType(typeof(ApiResponse), 404)]

public async Task GetAsync(Guid id)

{

var response = await _productAppService.GetAsync(id);

if (!response.Success)

{

return NotFound(response);

}

return Ok(response);

}

[HttpGet]

[ProducesResponseType(typeof(PagedApiResponse), 200)]

public async Task GetListAsync(

[FromQuery] PagedAndSortedResultRequestDto input,

[FromQuery] ProductFilter filter)

{

var response = await _productAppService.GetListAsync(input, filter);

return Ok(response);

}

[HttpPost]

[ProducesResponseType(typeof(ApiResponse), 201)]

[ProducesResponseType(typeof(ApiResponse), 400)]

public async Task CreateAsync([FromBody] CreateProductDto input)

{

var response = await _productAppService.CreateAsync(input);

if (!response.Success)

{

return BadRequest(response);

}

return CreatedAtAction(

nameof(GetAsync),

new { id = response.Data.Id },

response);

}

[HttpDelete("{id}")]

[ProducesResponseType(typeof(ApiResponse), 200)]

[ProducesResponseType(typeof(ApiResponse), 404)]

public async Task DeleteAsync(Guid id)

{

var response = await _productAppService.DeleteAsync(id);

if (!response.Success)

{

return NotFound(response);

}

return Ok(response);

}

}

```

2. Response Helper Extension

```csharp

public static class ControllerExtensions

{

public static IActionResult ToActionResult(

this ControllerBase controller,

ApiResponse response)

{

if (response.Success)

{

return controller.Ok(response);

}

var errorCode = response.Errors.FirstOrDefault()?.Code ?? "ERROR";

return errorCode switch

{

"NOT_FOUND" => controller.NotFound(response),

"VALIDATION_ERROR" => controller.BadRequest(response),

"UNAUTHORIZED" => controller.Unauthorized(response),

"FORBIDDEN" => controller.StatusCode(403, response),

_ => controller.BadRequest(response)

};

}

}

// Usage

[HttpGet("{id}")]

public async Task GetAsync(Guid id)

{

var response = await _productAppService.GetAsync(id);

return this.ToActionResult(response);

}

```

OpenAPI Documentation

```csharp

// Swagger configuration

services.AddSwaggerGen(c =>

{

c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });

// Add response examples

c.OperationFilter();

});

public class ApiResponseOperationFilter : IOperationFilter

{

public void Apply(OpenApiOperation operation, OperationFilterContext context)

{

// Add common error responses

operation.Responses.TryAdd("400", new OpenApiResponse

{

Description = "Bad Request - Validation Error"

});

operation.Responses.TryAdd("401", new OpenApiResponse

{

Description = "Unauthorized"

});

operation.Responses.TryAdd("404", new OpenApiResponse

{

Description = "Not Found"

});

operation.Responses.TryAdd("500", new OpenApiResponse

{

Description = "Internal Server Error"

});

}

}

```

Best Practices

  1. Use consistent response structure - All endpoints should return the same wrapper
  2. Include error codes - Machine-readable codes alongside human messages
  3. Add metadata - Timestamps, request IDs for debugging
  4. Paginate lists - Always include pagination info for collections
  5. Document with OpenAPI - Generate accurate API documentation
  6. Handle all exceptions - Use exception filters for consistency
  7. Use appropriate HTTP status codes - Map response status to HTTP codes
  8. Include timing information - Help clients understand performance
  9. Version your API - Include version in responses and routes
  10. Log correlation IDs - Make debugging distributed systems easier

References

  • [Error Code Standards](references/error-codes.md)
  • [Pagination Patterns](references/pagination.md)

More from this repository10

🎯
clean-code-dotnet🎯Skill

Provides clean code guidelines and refactoring techniques for C#/.NET, focusing on improving code readability, maintainability, and adherence to SOLID principles.

🎯
abp-api-implementation🎯Skill

Implements comprehensive REST APIs in ABP Framework with robust AppServices, DTOs, pagination, filtering, and authorization for .NET applications.

🎯
abp-entity-patterns🎯Skill

Implements domain layer patterns for ABP Framework, providing robust entity, aggregate, repository, and domain service implementations following DDD principles.

🎯
fluentvalidation-patterns🎯Skill

Validates input DTOs in ABP Framework using FluentValidation with async checks, conditional rules, custom validators, and localized error messages.

🎯
abp-infrastructure-patterns🎯Skill

abp-infrastructure-patterns skill from thapaliyabikendra/ai-artifacts

🎯
efcore-patterns🎯Skill

Configures and optimizes Entity Framework Core patterns for ABP Framework, focusing on entity configuration, migrations, and relationship design with PostgreSQL.

🎯
content-retrieval🎯Skill

content-retrieval skill from thapaliyabikendra/ai-artifacts

🎯
system-design-patterns🎯Skill

Designs scalable, reliable distributed systems by applying proven architectural patterns and evaluating trade-offs across performance, consistency, and availability.

🎯
abp-contract-scaffolding🎯Skill

Generates ABP Application.Contracts layer scaffolding, enabling parallel development by creating standardized interfaces, DTOs, and permissions for .NET microservices.

🎯
openiddict-authorization🎯Skill

Implements permission-based OAuth 2.0 authorization for ABP Framework using OpenIddict, enabling fine-grained access control and multi-tenant security.