Request & Response

XinAdmin defines a unified request-response format, with frontend axios and backend Laravel collaborating to implement automated request handling, error prompting, and exception handling.

Response Data Structure

Standard Response Format

interface ResponseStructure<T = any> {
  success: boolean      // Whether the request succeeded
  msg: string          // Response message
  data?: T             // Response data
  errorCode?: number   // Error code
  showType?: number    // Display type: 0-success message, 1-warning message, 2-error message, 3-success notification, 4-warning notification, 5-error notification, 99-silent
  status?: number      // HTTP status code
  description?: string // Detailed description
  placement?: string   // Notification position: top/topLeft/topRight/bottom/bottomLeft/bottomRight
}

Success Response Example

{
  "success": true,
  "data": {
    "id": 1,
    "name": "Administrator"
  },
  "msg": "ok",
  "showType": 0
}

Failure Response Example

{
  "success": false,
  "data": [],
  "msg": "Username or password incorrect",
  "showType": 1
}

ShowType Display Types

ShowType is used to control how frontend prompts are displayed, defined in App\Enum\ShowType.

ValueConstantDescriptionFrontend Component
0SUCCESS_MESSAGESuccess messageMessage.success
1WARN_MESSAGEWarning messageMessage.warning
2ERROR_MESSAGEError messageMessage.error
3SUCCESS_NOTIFICATIONSuccess notificationNotification.success
4WARN_NOTIFICATIONWarning notificationNotification.warning
5ERROR_NOTIFICATIONError notificationNotification.error
99SILENTSilent handlingNo prompt

Backend Response Trait

Controllers inheriting from BaseController can use response methods provided by the RequestJson trait.

Success Response

// Return success response with data
return $this->success(['id' => 1, 'name' => 'test']);

// Return success response with message
return $this->success('Operation successful');

// Return success response with empty data
return $this->success();

Error Response

// Return error response with message
return $this->error('Operation failed');

// Return error response with data
return $this->error(['field' => 'Username already exists'], 'Failed to create user');

Warning Response

// Return warning response
return $this->warn('Data expired, please refresh page');

Notification Response

// Notification response, supports custom position and type
return $this->notification(
    'Operation Reminder',              // Notification title
    'Your session is about to expire', // Notification description
    ShowType::WARN_NOTIFICATION,      // Notification type
    'topRight'                        // Notification position
);

Throwing Responses

Instead of returning responses, you can throw responses to interrupt execution:

// Throw success response and interrupt
$this->throwSuccess(['id' => 1], 'Created successfully');

// Throw error response and interrupt
$this->throwError('Insufficient permissions');

// Throw warning response and interrupt
$this->throwWarn('Data not saved');

Use Case: Interrupt execution in the middle of business logic without manually return.

public function create(Request $request)
{
    // Throw directly on validation failure
    if (!$request->name) {
        $this->throwError('Name cannot be empty');
    }

    // Continue execution if validation passes
    $this->service->create($request->all());
    return $this->success();
}

Frontend Request Wrapper

Frontend uses axios wrapper to uniformly handle request responses.

Request Configuration

// Create axios instance
const instance = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL || '',
  timeout: 10000,
  responseType: 'json',
  withCredentials: false,
});

Automatic Handling

Frontend automatically handles:

  • Auto-attach Token: Except for login endpoint, automatically adds Authorization: Bearer {token} to request header
  • Auto-attach Language: Automatically attaches User-Language to request header
  • Request Deduplication: Duplicate requests with same parameters are automatically cancelled
  • Error Prompting: Automatically displays Message or Notification based on showType
  • 401 Handling: Automatically redirects to login page

HTTP Status Code Handling

Status CodeDescription
401Unauthorized, clears Token and redirects to login page
400Incorrect parameters
403No permission to operate
404Resource not found
408Request timeout
409Data conflict
500Internal server error
502Gateway error
503Service unavailable
504Service temporarily inaccessible

Business Error Handling

When success: false, handled according to showType:

switch (showType) {
  case 0:  // Message success prompt
    if (msg) message.success(msg);
    break;
  case 1:  // Message warning prompt
    if (msg) message.warning(msg);
    break;
  case 2:  // Message error prompt
    if (msg) message.error(msg);
    break;
  case 3:  // Notification success
    if (msg) notification.success({ message: msg });
    break;
  case 4:  // Notification warning
    if (msg) notification.warning({ message: msg });
    break;
  case 5:  // Notification error
    if (msg) notification.error({ message: msg });
    break;
  case 99: // Silent, no prompt displayed
    break;
}

List Response Format

Paginated Data

type ListResponse<T> = {
  data: T[]      // Data list
  page: number   // Current page number
  total: number  // Total record count
  per_page: number  // Items per page
  current_page: number  // Current page number
}

Response Example

{
  "success": true,
  "data": {
    "data": [
      { "id": 1, "name": "User1" },
      { "id": 2, "name": "User2" }
    ],
    "page": 1,
    "total": 100,
    "per_page": 10,
    "current_page": 1
  },
  "msg": "ok",
  "showType": 0
}

Usage Examples

Backend Controller

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\BaseController;
use App\Services\Admin\UserService;
use Illuminate\Http\JsonResponse;
use Xin\AnnoRoute\RequestAttribute;
use Xin\AnnoRoute\Route\GetRoute;
use Xin\AnnoRoute\Route\PostRoute;

#[RequestAttribute('/admin/user', 'admin.user')]
class UserController extends BaseController
{
    public function __construct(
        protected UserService $service
    ) {}

    #[GetRoute('/{id}')]
    public function show(int $id): JsonResponse
    {
        $user = $this->service->find($id);
        if (!$user) {
            return $this->error('User not found');
        }
        return $this->success($user);
    }

    #[PostRoute]
    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'name' => 'required|string|max:50',
            'email' => 'required|email|unique:users',
        ]);

        try {
            $user = $this->service->create($data);
            return $this->success($user, 'User created successfully');
        } catch (\Exception $e) {
            return $this->error($e->getMessage());
        }
    }

    #[PostRoute('/batch-import')]
    public function batchImport(Request $request): JsonResponse
    {
        $file = $request->file('file');
        if (!$file) {
            return $this->throwError('Please upload file');
        }

        // Large data import, may take longer
        $result = $this->service->import($file);

        if ($result['failed'] > 0) {
            // Has failed data, show warning
            return $this->warn($result, 'Import completed, but ' . $result['failed'] . ' records failed');
        }

        return $this->success($result, 'Import successful');
    }
}

Frontend Call

import createAxios from '@/utils/request';

// Get user details
async function getUser(id: number) {
  const response = await createAxios({
    url: `/admin/user/${id}`,
    method: 'get',
  });
  return response.data.data;
}

// Create user
async function createUser(data: UserData) {
  const response = await createAxios({
    url: '/admin/user',
    method: 'post',
    data,
  });
  return response.data.data;
}

// Batch import
async function importUsers(file: File) {
  const formData = new FormData();
  formData.append('file', file);

  const response = await createAxios({
    url: '/admin/user/batch-import',
    method: 'post',
    data: formData,
    headers: { 'Content-Type': 'multipart/form-data' },
  });
  return response.data.data;
}

Exception Handling

Global Exception Handling

ExceptionsHandler uniformly handles uncaught exceptions:

// Validation exception
if ($exception instanceof ValidationException) {
    return $this->error($exception->errors(), 'Parameter validation failed');
}

// Business exception
if ($exception instanceof RepositoryException) {
    return $this->error($exception->getMessage());
}

// Authentication exception
if ($exception instanceof AuthenticationException) {
    return $this->error([], 'Please login first')->setStatusCode(401);
}

HttpResponseException

Business code can directly return responses by throwing HttpResponseException:

use App\Exceptions\HttpResponseException;

public function update(Request $request, int $id)
{
    $user = User::find($id);
    if (!$user) {
        throw new HttpResponseException([
            'success' => false,
            'msg' => 'User not found',
            'showType' => ShowType::ERROR_MESSAGE,
        ]);
    }

    // ...
}

Best Practices

1. Unified Response Format

Always use $this->success() or $this->error() to return responses in controllers, maintaining consistency.

2. Choose showType Appropriately

  • Regular operation feedback: Use default Message
  • Important operations requiring user confirmation: Use Notification
  • Background silent processing: Use SILENT

3. Error Message Localization

// Use language pack
return $this->error(__('user.not_found'));

4. Frontend Unified Error Handling

Avoid manually handling response errors in business code, rely on frontend's unified interceptor handling.