Responses

drf-commons enforces a standardized JSON response envelope across all API endpoints. This eliminates the need for client-side conditional parsing based on response structure.

Response Envelope Structure

All responses conform to the following schema:

Success:

{
  "success": true,
  "timestamp": "2026-01-15T10:30:00.000000Z",
  "message": "Operation completed successfully.",
  "data": { ... }
}

Error:

{
  "success": false,
  "timestamp": "2026-01-15T10:30:00.000000Z",
  "message": "Validation failed.",
  "errors": {
    "title": ["This field is required."],
    "email": ["Enter a valid email address."]
  },
  "data": null
}
Envelope Fields

Field

Type

Description

success

bool

true on 2xx responses, false on error responses

timestamp

str

ISO 8601 UTC timestamp of response generation

message

str

Human-readable description; may be empty on success

data

any

Response payload; null on error

errors

object

Field-level or non-field errors; present only on error responses

The timestamp field uses Django’s DjangoJSONEncoder, ensuring UTC output in ISO 8601 format regardless of the server’s local timezone configuration.

Response Utility Functions

from drf_commons.response import success_response, error_response

success_response

success_response(
    data=None,
    message: str = "",
    status_code: int = 200,
    **kwargs,
) -> Response

Constructs a DRF Response with the success envelope.

Parameters:

  • data — The response payload. Passed through DjangoJSONEncoder serialization. Can be any JSON-serializable value.

  • message — Optional human-readable success message.

  • status_code — HTTP status code. Defaults to 200.

  • **kwargs — Additional fields merged into the response envelope.

Examples:

# Simple success
return success_response(data=serializer.data)

# With message and non-200 status
return success_response(
    data={"created": 15},
    message="Bulk create completed.",
    status_code=201,
)

# With additional envelope fields
return success_response(
    data=serializer.data,
    meta={"total_pages": 10, "current_page": 1},
)

error_response

error_response(
    message: str = "An error occurred.",
    status_code: int = 400,
    errors=None,
    **kwargs,
) -> Response

Constructs a DRF Response with the error envelope.

Parameters:

  • message — Human-readable error description.

  • status_code — HTTP status code. Defaults to 400.

  • errors — Dict of field-level or non-field errors.

  • **kwargs — Additional fields merged into the response envelope.

Examples:

# Validation error
return error_response(
    message="Validation failed.",
    errors=serializer.errors,
    status_code=400,
)

# Not found
return error_response(
    message="Article not found.",
    status_code=404,
)

# Permission denied
return error_response(
    message="You do not have permission to perform this action.",
    status_code=403,
)

Automatic Response Formatting

When using drf-commons ViewSets, response formatting is automatic. The CRUD and bulk action mixins call success_response() and error_response() internally. No per-view code is required for standard operations.

Custom views and actions can use the utilities directly:

from rest_framework.decorators import action
from drf_commons.response import success_response, error_response
from drf_commons.views import BaseViewSet

class ReportViewSet(BaseViewSet):
    @action(detail=False, methods=["post"], url_path="generate")
    def generate_report(self, request):
        try:
            result = ReportService.generate(request.data)
            return success_response(
                data=result,
                message="Report generated successfully.",
            )
        except ValueError as exc:
            return error_response(
                message=str(exc),
                status_code=422,
            )

Pagination Response Format

Paginated list responses include standard pagination metadata alongside the data array:

{
  "success": true,
  "timestamp": "2026-01-15T10:30:00.000000Z",
  "message": "",
  "count": 150,
  "next": "https://api.example.com/articles/?page=3",
  "previous": "https://api.example.com/articles/?page=1",
  "data": [ ... ]
}

For unpaginated list responses (?paginated=false or when pagination_class = None), the count, next, and previous fields are absent and data contains the full array.