404 Not Found

404 Not Found


nginx

Laravel Requests and Responses

Requests and responses are Laravel's "inputs and outputs"—data is extracted when a request comes in, and data is packaged when a response goes out; both processes require precise control.

1. What You'll Learn


2. A True Story from an API Developer

(1) Pain Point: Each endpoint returns data in a different format

Bob wrote 10 endpoints for the ShopMetrics API—some return {data: [...]}, some return {shops: [...]}, and others return arrays directly. When integrating the API into the front end, Alice had to write different parsing logic for each endpoint. To make matters worse, paginated data was returned as either total_pages or last_page, making it impossible to standardize the front-end pagination component.

(2) Methods for Solving Standardized Responses

Laravel's API Resource and response macros standardize the output format—all endpoints return the same JSON structure, and paginated data follows a consistent format.

PHP
// Standardized response — every endpoint follows this format
return ShopResource::collection($shops);
// {
//   "data": [...],
//   "meta": {"current_page": 1, "total": 50},
//   "links": {"next": "...", "prev": "..."}
// }

(3) Revenue

After Alice adopted a unified format, the number of front-end parsing logic sets was reduced from 10 to 1, and the reuse rate of the pagination component reached 100%.


3. The Request Object

(1) Get Input

PHP
// Get single input
$name = $request->input('name');
$name = $request->input('name', 'default value');

// Get from query string only
$sort = $request->query('sort', 'created_at');

// Get multiple inputs
$filtered = $request->only(['name', 'email', 'status']);
$filtered = $request->except(['password', '_token']);

// Check existence
if ($request->has('search')) { ... }
if ($request->filled('search')) { ... }  // has + not empty
if ($request->missing('search')) { ... }

// Type conversion
$page = $request->integer('page', 1);
$active = $request->boolean('active', false);
$date = $request->date('published_at', 'Y-m-d');
Method Description Difference
input() All inputs (query+body) Most common
query() Query string only GET parameters
post() POST body only Form data
only() Extract Specified Fields Whitelist
except() Exclude Specified Fields Blacklist
has() Field exists Contains null values
filled() Field exists and is not empty Exclude null values

(1) ▶ Example: Processing ShopMetrics API Requests

PHP
// app/Http/Controllers/Api/ShopController.php
public function index(Request $request): JsonResponse
{
    $query = Shop::where('tenant_id', tenant()->id);

    // Search filter
    if ($request->filled('search')) {
        $query->where('name', 'like', "%{$request->search}%");
    }

    // Status filter
    if ($request->filled('status')) {
        $query->where('status', $request->status);
    }

    // Sort
    $sortField = $request->input('sort', 'created_at');
    $sortDir = $request->input('direction', 'desc');
    $query->orderBy($sortField, $sortDir);

    // Paginate
    $perPage = $request->integer('per_page', 15);
    $shops = $query->paginate($perPage);

    return ShopResource::collection($shops);
}

Output:

TEXT
// Execution Successful

4. File Upload

(1) Basic Upload Processing

PHP
// Validate file upload
$validated = $request->validate([
    'logo' => 'required|image|mimes:jpeg,png,webp|max:2048',
]);

// Store file
$path = $request->file('logo')->store('shops/logos', 'public');
// => "shops/logos/abc123.jpg"

// Store with custom name
$path = $request->file('logo')->storeAs(
    'shops/logos',
    $shop->slug . '.' . $request->file('logo')->extension(),
    'public',
);

// Get file info
$file = $request->file('logo');
$file->getClientOriginalName();
$file->getClientOriginalExtension();
$file->getSize();          // bytes
$file->getMimeType();
Method Description
store() Save to specified directory with a random filename
storeAs() Save to a specified directory with a custom filename
storePublicly() Save to the public directory
isValid() Check if the upload was successful
PHP
// app/Http/Controllers/ShopLogoController.php
class ShopLogoController extends Controller
{
    public function update(Request $request, Shop $shop): RedirectResponse
    {
        $validated = $request->validate([
            'logo' => 'required|image|mimes:jpeg,png,webp|max:2048',
        ]);

        // Delete old logo if exists
        if ($shop->logo_path) {
            Storage::disk('public')->delete($shop->logo_path);
        }

        // Store new logo
        $path = $request->file('logo')->store("shops/{$shop->id}/logos", 'public');

        $shop->update(['logo_path' => $path]);

        return back()->with('success', 'Logo updated.');
    }
}

Output:

TEXT
// Execution Successful

5. Response Builder

(1) Response Type

PHP
// JSON response
return response()->json(['message' => 'Created', 'data' => $shop], 201);

// View response
return response()->view('shops.show', compact('shop'), 200);

// Redirect
return redirect()->route('shops.show', $shop);
return back()->withInput()->withErrors($errors);
return redirect()->away('https://external-site.com');

// File download
return response()->download(storage_path('app/reports/report.pdf'));
return response()->streamDownload(function () {
    echo generateCsv();
}, 'orders.csv', ['Content-Type' => 'text/csv']);

// No content
return response()->noContent(); // 204
Response Type Method HTTP Status Code
JSON response()->json() 200/201/422
View response()->view() 200
Redirect redirect() 302
Download response()->download() 200
Stream Download response()->streamDownload() 200
No content response()->noContent() 204

(1) ▶ Example: ShopMetrics CSV Export Streaming Response

PHP
// app/Http/Controllers/ExportController.php
class ExportController extends Controller
{
    public function exportOrders(Request $request, Shop $shop): StreamedResponse
    {
        $this->authorize('view', $shop);

        return response()->streamDownload(function () use ($shop) {
            $csv = fopen('php://output', 'w');
            fputcsv($csv, ['Order ID', 'Customer', 'Total', 'Status', 'Date']);

            $shop->orders()
                ->with('user')
                ->orderBy('created_at', 'desc')
                ->chunk(500, function ($orders) use ($csv) {
                    foreach ($orders as $order) {
                        fputcsv($csv, [
                            $order->order_number,
                            $order->user->name,
                            $order->total,
                            $order->status,
                            $order->created_at->format('Y-m-d'),
                        ]);
                    }
                });

            fclose($csv);
        }, "orders-{$shop->slug}-" . now()->format('Y-m-d') . '.csv', [
            'Content-Type' => 'text/csv',
        ]);
    }
}

Output:

TEXT
// Execution Successful

6. API Pagination

(1) Comparison of Pagination Methods

Method Number of Queries Returned Information Use Cases
paginate() 2 (COUNT+SELECT) total/last_page/links Total number of pages required
simplePaginate() 1 (SELECT) next/prev links Total pages not required
cursorPaginate() 1 (SELECT+WHERE) next/prev cursor large dataset

(2) Pagination in JSON Format

JSON
{
    "data": [
        {"id": 1, "name": "Alice Store"},
        {"id": 2, "name": "Bob Electronics"}
    ],
    "current_page": 1,
    "per_page": 15,
    "total": 50,
    "last_page": 4,
    "from": 1,
    "to": 15,
    "links": {
        "first": "/api/v1/shops?page=1",
        "last": "/api/v1/shops?page=4",
        "next": "/api/v1/shops?page=2",
        "prev": null
    }
}

(3) Cursor Pagination

PHP
// More efficient for large datasets
$shops = Shop::cursorPaginate(15);
// Uses "cursor" parameter instead of "page"
// URL: /api/shops?cursor=eyJpZCI6MTV9

// Next page URL
$shops->nextPageUrl();
// /api/shops?cursor=eyJpZCI6MzB9

(1) ▶ Example: ShopMetrics API Pagination and Filtering

PHP
// app/Http/Controllers/Api/OrderController.php
public function index(Request $request): JsonResponse
{
    $query = Order::where('tenant_id', tenant()->id)
        ->with(['shop', 'user', 'items.product']);

    // Filters
    if ($request->filled('status')) {
        $query->where('status', $request->status);
    }
    if ($request->filled('shop_id')) {
        $query->where('shop_id', $request->shop_id);
    }
    if ($request->filled('date_from')) {
        $query->where('created_at', '>=', $request->date('date_from'));
    }
    if ($request->filled('date_to')) {
        $query->where('created_at', '<=', $request->date('date_to'));
    }

    // Sort
    $query->orderBy(
        $request->input('sort_by', 'created_at'),
        $request->input('sort_dir', 'desc'),
    );

    // Choose pagination strategy
    $perPage = $request->integer('per_page', 15);

    $orders = $request->boolean('cursor', false)
        ? $query->cursorPaginate($perPage)
        : $query->paginate($perPage);

    return OrderResource::collection($orders);
}

Output:

TEXT
// Execution Successful

7. Response Macros and Format Standardization

(1) Define a response macro

PHP
// app/Providers/AppServiceProvider.php
public function boot(): void
{
    Response::macro('apiSuccess', function (mixed $data, string $message = 'Success', int $status = 200) {
        return response()->json([
            'success' => true,
            'message' => $message,
            'data' => $data,
        ], $status);
    });

    Response::macro('apiError', function (string $message, int $status = 400, array $errors = []) {
        return response()->json([
            'success' => false,
            'message' => $message,
            'errors' => $errors,
        ], $status);
    });
}

(2) Using Response Macros

PHP
// In controller
return response()->apiSuccess($shop, 'Shop created', 201);
return response()->apiError('Shop not found', 404);
return response()->apiError('Validation failed', 422, $validator->errors()->toArray());

(1) ▶ Example: ShopMetrics API Standard Response Format

PHP
// app/Traits/ApiResponse.php
trait ApiResponse
{
    protected function success(mixed $data, string $message = 'Success', int $status = 200): JsonResponse
    {
        return response()->json([
            'success' => true,
            'message' => $message,
            'data' => $data,
            'timestamp' => now()->toISOString(),
        ], $status);
    }

    protected function error(string $message, int $status = 400, array $errors = []): JsonResponse
    {
        return response()->json([
            'success' => false,
            'message' => $message,
            'errors' => $errors,
            'timestamp' => now()->toISOString(),
        ], $status);
    }

    protected function paginated($resource, string $message = 'Success'): JsonResponse
    {
        return response()->json([
            'success' => true,
            'message' => $message,
            'data' => $resource->resolve(),
            'meta' => [
                'current_page' => $resource->resource->currentPage(),
                'per_page' => $resource->resource->perPage(),
                'total' => $resource->resource->total(),
                'last_page' => $resource->resource->lastPage(),
            ],
            'links' => [
                'first' => $resource->resource->url(1),
                'last' => $resource->resource->url($resource->resource->lastPage()),
                'next' => $resource->resource->nextPageUrl(),
                'prev' => $resource->resource->previousPageUrl(),
            ],
        ]);
    }
}

Output:

TEXT
// Execution Successful

8. Comprehensive Example: The Complete Process of Handling ShopMetrics API Requests

PHP
// ============================================
// Comprehensive: ShopMetrics API Request-Response
// Covers: input handling, file upload, pagination, response format
// ============================================

// app/Http/Controllers/Api/ProductController.php
class ProductController extends Controller
{
    use ApiResponse;

    public function index(Request $request): JsonResponse
    {
        $query = Product::where('shop_id', $request->shop_id)
            ->with('categories');

        if ($request->filled('search')) {
            $query->where('name', 'like', "%{$request->search}%");
        }
        if ($request->filled('category')) {
            $query->whereHas('categories', fn ($q) => $q->where('slug', $request->category));
        }
        if ($request->filled('min_price')) {
            $query->where('price', '>=', $request->float('min_price'));
        }
        if ($request->filled('max_price')) {
            $query->where('price', '<=', $request->float('max_price'));
        }
        if ($request->filled('in_stock') && $request->boolean('in_stock')) {
            $query->where('stock', '>', 0);
        }

        $query->orderBy(
            $request->input('sort', 'created_at'),
            $request->input('direction', 'desc'),
        );

        $products = $query->paginate($request->integer('per_page', 15));

        return $this->paginated(ProductResource::collection($products));
    }

    public function store(StoreProductRequest $request): JsonResponse
    {
        $product = Product::create($request->validated());

        if ($request->hasFile('image')) {
            $path = $request->file('image')->store("products/{$product->id}", 's3');
            $product->update(['image_path' => $path]);
        }

        return $this->success(new ProductResource($product), 'Product created', 201);
    }

    public function export(Request $request, Shop $shop): StreamedResponse
    {
        $this->authorize('view', $shop);

        return response()->streamDownload(function () use ($shop) {
            echo ProductExporter::toCsv($shop);
        }, "products-{$shop->slug}.csv", ['Content-Type' => 'text/csv']);
    }
}

❓ FAQ

Q What is the difference between input() and query()?
A input() retrieves data from all sources (query string + request body), while query() retrieves data only from the URL query string. It's clearer to use input() for POST form data and query() for GET parameters.
Q How do I choose between cursorPaginate and paginate?
A Use paginate for datasets smaller than 100,000 (requires displaying the total number of pages); use cursorPaginate for datasets larger than 100,000 (no COUNT query required, better performance). Infinite scrolling UI is best suited for cursor.
Q How should the API response format be standardized?
A Create an ApiResponse trait or response macro to encapsulate the success, error, and paginated methods. All controllers should use these standardized response methods to ensure consistency in field names, status codes, and pagination formats.
Q Should I use download() or streamDownload() for downloading large files?
A For small files, use download() to read them directly into memory; for large files, use streamDownload() for streaming, which keeps memory usage constant. streamDownload() must be used for scenarios such as CSV export.
Q How do you handle differences in response formats between API versions?
A Each API version uses a separate Resource class (V1/ShopResource vs. V2/ShopResource); the output fields are controlled in the Resource's toArray() method, and the routing layer selects the appropriate version.
Q What is the difference between storing uploaded files locally and on S3?
A Local storage is on the server's disk (free but not scalable), while S3 is in the cloud (pay-as-you-go, CDN acceleration, unlimited scalability). S3 is recommended for production environments.

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Implement the GET /api/v1/shops endpoint for the ShopMetrics API, supporting the search, status, sort, and direction filter parameters, and use paginate to return JSON in paginated chunks.

  2. Advanced Exercise (⭐⭐): Create an ApiResponse trait that encapsulates the success, error, and paginated methods, and use it in all API controllers to ensure a consistent response format.

  3. Challenge (⭐⭐⭐): Implement an API for a paginated order list using a cursor (GET /api/v1/orders?cursor=xxx), and compare the query performance differences between paginate and cursorPaginate when processing 100,000 records.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏