DEV Community

Champ of Greatness
Champ of Greatness

Posted on

Using ApiResponse<T> in Azure Functions and TypeScript: A Comprehensive Guide

Introduction

Standardizing API responses is critical for building scalable and maintainable systems. This article explores the implementation of a generic ApiResponse<T> class in C# for Azure Functions and its corresponding TypeScript handling. We’ll also discuss the challenges of not having a standardized response, the benefits of error collections, and best practices for implementation.

Implementing ApiResponse in C

Updated ApiResponse Class

The updated implementation supports robust error handling, tracking via CorrelationId, and human-readable errors.

public class ApiResponse<T> : BaseApiResponse
{
    public T Result { get; set; }

    public ApiResponse() { }

    public ApiResponse(T result)
    {
        Result = result;
    }
}

public abstract class BaseApiResponse
{
    public string CorrelationId { get; set; } = Guid.NewGuid().ToString();
    public IReadOnlyList<ApiError> Errors { get; protected set; } = new List<ApiError>();

    public void AddError(ApiError error)
    {
        ((List<ApiError>)Errors).Add(error);
    }

    public int ErrorCount => Errors.Count;
    public bool IsSuccessful => !Errors.Any();
}

public class ApiError
{
    public string PropertyName { get; set; }
    public string ErrorMessage { get; set; }
    public string ErrorCode { get; set; }

    public ApiError(string propertyName, string errorMessage, string errorCode = null)
    {
        PropertyName = propertyName;
        ErrorMessage = errorMessage;
        ErrorCode = errorCode;
    }
}
Enter fullscreen mode Exit fullscreen mode

Example Azure Function

[FunctionName("GetData")]
public static async Task<IActionResult> GetData(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "data")] HttpRequest req,
    ILogger log)
{
    log.LogInformation("Processing GetData request.");

    var data = new { Id = 1, Name = "Sample Data" };

    var response = new ApiResponse<object>(data);

    return new OkObjectResult(response);
}
Enter fullscreen mode Exit fullscreen mode

Handling ApiResponse in TypeScript

Updated TypeScript Interfaces

The TypeScript interfaces mirror the updated C# implementation, supporting CorrelationId, error collections, and the IsSuccessful flag.

interface ApiError {
  propertyName: string;
  errorMessage: string;
  errorCode?: string;
}

interface BaseApiResponse {
  correlationId: string;
  errors: ApiError[];
  errorCount: number;
  isSuccessful: boolean;
}

interface ApiResponse<T> extends BaseApiResponse {
  result: T;
}
Enter fullscreen mode Exit fullscreen mode

Example Function to Consume the API

async function fetchApiResponse<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);

  const data: ApiResponse<T> = await response.json();

  if (!data.isSuccessful) {
    console.error("Errors:", data.errors);
  }

  return data;
}

// Example usage
interface Data {
  id: number;
  name: string;
}

fetchApiResponse<Data>("https://<your-function-app-name>.azurewebsites.net/api/data")
  .then(apiResponse => {
    if (apiResponse.isSuccessful) {
      console.log("Data fetched successfully:", apiResponse.result);
    }
  })
  .catch(error => console.error("Error fetching data:", error));
Enter fullscreen mode Exit fullscreen mode

Challenges of Not Having a Standardized Response

  1. Inconsistent Client Handling: Without a standard response, clients must implement custom parsing logic for each endpoint, increasing complexity.
  2. Ambiguous Error Handling: Errors returned inconsistently can confuse clients, especially if only HTTP status codes are used without additional details.
  3. Reduced Observability: Without fields like CorrelationId, debugging distributed systems becomes challenging.
  4. Business Rule Violations: Lack of clear communication for business rule violations (e.g., "Insufficient balance") complicates client-side error handling.

Advantages of an Error Collection

  1. Human-Readable Errors: Errors can map directly to fields in the request body, improving user experience.
   {
     "errors": [
       { "propertyName": "email", "errorMessage": "Email is invalid." },
       { "propertyName": "password", "errorMessage": "Password must be at least 8 characters." }
     ]
   }
Enter fullscreen mode Exit fullscreen mode
  1. Business Rule Violations: Clearly communicate issues like insufficient permissions or invalid operations.
   {
     "errors": [
       { "propertyName": "businessRule", "errorMessage": "Insufficient balance to complete the transaction." }
     ]
   }
Enter fullscreen mode Exit fullscreen mode
  1. Improved Debugging with Correlation ID: Track requests end-to-end across systems.
  2. Scalability: Add new error types without breaking existing clients.

Best Practices for Implementation

  • Include Correlation ID: Pass it as a header or query parameter for end-to-end tracking.
  • Separate Validation and Business Rule Errors: Use PropertyName for validation errors and businessRule for logic violations.
  • Log Errors with Correlation ID: Facilitate debugging and analytics.
  • Document the Error Structure: Ensure clients understand how to handle responses.

Conclusion

Using a standardized ApiResponse<T> structure with error collections and correlation IDs improves consistency, maintainability, and debugging capabilities. While it introduces slight payload overhead, the benefits far outweigh the drawbacks, especially for complex or distributed systems. By following best practices, you can create APIs that are robust, scalable, and easy to integrate with.

FAQs

  1. Why use ApiResponse<T> instead of raw responses?
    ApiResponse<T> provides a consistent structure, improving maintainability and error handling.

  2. How does CorrelationId help?
    It enables tracking and debugging of requests across distributed systems.

  3. What is the purpose of the IsSuccessful flag?
    It provides a quick way to check if the operation succeeded, independent of HTTP status codes.

  4. Can I add more fields to ApiResponse<T>?
    Yes, you can extend it with fields like ErrorCode or AdditionalData as needed.

  5. How to handle breaking changes in the response structure?
    Use versioning in your API to ensure backward compatibility for existing clients.

Top comments (0)