This document explains the simplification applied to the CreateOrder endpoint response structure.
{
"success": true,
"message": "Order created and published successfully",
"data": {
"model": {
"id": "19b85ea1-be19-4aa8-a228-bdfaaa49273c",
"customerName": "Alice T",
"productName": "Wireless Mouse",
"quantity": 2,
"totalAmount": 54545,
"status": "Created",
"createdAt": "2025-11-21T19:27:57.6633428Z"
}
},
"errors": null,
"timestamp": "2025-11-21T19:27:57.6681873Z"
}Response Type: ApiResponse<CreateResponseDto<OrderResponseDto>>
{
"success": true,
"message": "Order created and published successfully",
"data": {
"id": "19b85ea1-be19-4aa8-a228-bdfaaa49273c",
"customerName": "Alice T",
"productName": "Wireless Mouse",
"quantity": 2,
"totalAmount": 54545,
"status": "Created",
"createdAt": "2025-11-21T19:27:57.6633428Z"
},
"errors": null,
"timestamp": "2025-11-21T19:27:57.6681873Z"
}Response Type: ApiResponse<OrderResponseDto>
- Removed unnecessary nesting (
data.model→data) - Order properties are now directly accessible
- Easier to parse and consume by client applications
// Before
const orderId = response.data.model.id;
const customerName = response.data.model.customerName;
// After
const orderId = response.data.id;
const customerName = response.data.customerName;- Other endpoints (payment, shipping, delivery, cancel) already return flat structures
- All endpoints now follow the same pattern
- Fewer types to maintain
- Simpler mapping logic
- Less cognitive overhead for developers
- Slightly smaller JSON payload
- Faster serialization/deserialization
// Before
public async Task<ActionResult<ApiResponse<CreateResponseDto<OrderResponseDto>>>> CreateOrder(...)
// After
public async Task<ActionResult<ApiResponse<OrderResponseDto>>> CreateOrder(...)// Before
var responseData = order.ToCreateResponseDto(); // Returns CreateResponseDto<OrderResponseDto>
var response = ApiResponse<CreateResponseDto<OrderResponseDto>>.SuccessResponse(responseData, "...");
// After
var responseData = order.ToResponseDto(); // Returns OrderResponseDto directly
var response = ApiResponse<OrderResponseDto>.SuccessResponse(responseData, "...");// ToCreateResponseDto() marked as obsolete
[Obsolete("This method is no longer used. Use ToResponseDto() directly for a simpler response structure.")]
public static CreateResponseDto<OrderResponseDto> ToCreateResponseDto(this Order order)If you're consuming this API from a client application:
// Before
const order = response.data.model;
// After
const order = response.data;// Before
var apiResponse = await client.GetFromJsonAsync<ApiResponse<CreateResponseDto<OrderResponseDto>>>("/api/orders");
var order = apiResponse.Data.Model;
// After
var apiResponse = await client.GetFromJsonAsync<ApiResponse<OrderResponseDto>>("/api/orders");
var order = apiResponse.Data;# Before
order = response.json()["data"]["model"]
# After
order = response.json()["data"]The CreateResponseDto<T> wrapper should be used when you need to return additional creation metadata beyond the created resource itself.
Scenario 1: Include Creation Location
public class CreateResponseDto<T>
{
public T Model { get; set; }
public string CreatedAtUrl { get; set; } // e.g., "/api/orders/123"
public string EventId { get; set; } // RabbitMQ event ID
}Scenario 2: Include Processing Status
public class CreateResponseDto<T>
{
public T Model { get; set; }
public string QueuePosition { get; set; }
public TimeSpan EstimatedProcessingTime { get; set; }
}Scenario 3: Include Related Resources
public class CreateResponseDto<T>
{
public T Model { get; set; }
public List<string> RelatedResourceUrls { get; set; }
}Since we don't have additional metadata to return, the simplified structure is better.
If you need to revert to the nested structure:
// In OrdersController.cs CreateOrder method:
// Replace:
var responseData = order.ToResponseDto();
var response = ApiResponse<OrderResponseDto>.SuccessResponse(responseData, "...");
// With:
var responseData = order.ToCreateResponseDto();
var response = ApiResponse<CreateResponseDto<OrderResponseDto>>.SuccessResponse(responseData, "...");
// And update method signature:
public async Task<ActionResult<ApiResponse<CreateResponseDto<OrderResponseDto>>>> CreateOrder(...)- Navigate to http://localhost:8080/swagger (or your configured port)
- Test
POST /api/ordersendpoint - Verify response structure:
- ✅
datashould contain order properties directly - ✅ No
data.modelnesting
- ✅
curl -X POST "http://localhost:8080/api/orders" \
-H "Content-Type: application/json" \
-d '{
"customerName": "John Doe",
"productName": "Laptop",
"quantity": 1,
"totalAmount": 1299.99
}'Expected Response:
{
"success": true,
"message": "Order created and published successfully",
"data": {
"id": "...",
"customerName": "John Doe",
"productName": "Laptop",
"quantity": 1,
"totalAmount": 1299.99,
"status": "Created",
"createdAt": "..."
},
"errors": null,
"timestamp": "..."
}The response structure simplification:
- ✅ Removes unnecessary nesting
- ✅ Improves client developer experience
- ✅ Maintains consistency across endpoints
- ✅ Reduces complexity
- ✅ Follows REST best practices
The CreateResponseDto<T> type remains available for future use if additional creation metadata is needed, but is marked as obsolete for current usage.
Document Version: 1.0
Last Updated: 2025-01-21
Author: Dariem Carlos Macias