Home / News / Service Responses | From Chaos to Clean APIs

Service Responses | From Chaos to Clean APIs

1. Consistency: The class now has a consistent structure for all responses, making it easy for clients to understand what to expect and debug when encountering errors or unexpected data. The `withMessage` method allows for chaining multiple responses with the same message, ensuring that all messages are grouped together in a single response.

2. Chained Methods: The class introduces a fluent syntax for building responses step by step, enabling developers to easily chain multiple responses together. This syntax allows for a clean and readable approach to error handling, making it easier to identify and resolve issues in a structured fashion.

3. Error Aggregation: The class enables the aggregation of multiple errors into a single response, providing a clear and concise way to handle nested or more complex errors. This feature allows for a comprehensive view of the error’s origin, making it easier to identify and fix issues that may have originated in multiple layers of the system.

4. Flexible Data: The class allows any payload to be returned, including strings, objects, and arrays, while maintaining the response structure intact. This flexibility allows developers

I used to build backend services the “quick way.” Every function returned something different: sometimes arrays, sometimes strings, sometimes just a boolean. Clients never knew what to expect. Debugging? Nightmare. Error handling? Confusing.

Then I realized I needed structure. That’s when I built a ServiceResponse class. Now, every response is predictable, consistent, and fully traceable.

Here’s what it looks like:

<?php

namespace Usmanzahid\ServiceResponse;

class ServiceResponse {
    private ?string $message = null;
    private bool $success = true;
    private mixed $data = null;
    private array $errors = [];
    private ?self $previous = null;

    public function withMessage(string $message): self {
        $this->message = $message;
        return $this;
    }

    public function withSuccess(bool $success): self {
        $this->success = $success;
        return $this;
    }

    public function withError(string $key, string $message): self {
        $this->errors[$key][] = $message;
        return $this;
    }

    public function withErrors(array $errors): self {
        foreach ($errors as $key => $messages) {
            foreach ((array) $messages as $message) {
                $this->withError($key, $message);
            }
        }
        return $this;
    }

    public function withData(mixed $data): self {
        $this->data = $data;
        return $this;
    }

    public function withPrevious(self $previous): self {
        $this->previous = $previous;
        return $this;
    }

    public function getMessage(): ?string {
        return $this->message;
    }

    public function wasSuccessful(): bool {
        return $this->success===true;
    }

    public function wasNotSuccessful(): bool {
        return !$this->wasSuccessful();
    }

    public function getErrors(): array {
        return $this->errors;
    }

    public function getAllErrors(): array {
        $all = $this->errors;
        $prev = $this->previous;
        while ($prev) {
            $all = array_merge_recursive($prev->getErrors(), $all);
            $prev = $prev->previous;
        }
        return $all;
    }

    public function getData(): mixed {
        return $this->data;
    }

    public function getRootCause(): self {
        $origin = $this;
        while ($origin->previous!==null) {
            $origin = $origin->previous;
        }
        return $origin;
    }

    public function toArray(): array {
        return [
            'success' => $this->wasSuccessful(),
            'message' => $this->message,
            'data' => $this->data,
            'errors' => $this->getAllErrors(),
        ];
    }
}

Why It Changed Everything

1. Consistency

Every response now has the same shape: success, message, data, errors. No surprises.

2. Chained Methods

Build responses step by step with fluent syntax. For example:

return (new ServiceResponse())
    ->withSuccess(false)
    ->withMessage('Something went wrong')
    ->withError('email', 'Invalid email address');

3. Error Aggregation

Multiple errors? Nested errors? Fully traceable. I finally know why something failed, not just that it did.

4. Flexible Data

Any payload can be returned—strings, objects, arrays—while keeping the response structure intact.

5. Root Cause Tracking

If an error bubbles up through multiple layers, the original source is always available. Debugging becomes easier, and logs actually make sense.

The Takeaway

What started as a small class to standardize API responses turned into a tool that made my backend predictable, debuggable, and professional. No more random return types. No more guessing. Now every request and response has full context, and handling errors feels natural.

Usman Zahid

Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *