Laravel API returns a view 404 error instead of JSON error

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Laravel API Returns a View 404 Instead of JSON Error: A Deep Dive into Exception Handling

Building robust RESTful APIs with Laravel is an extremely common task. When dealing with resource fetching, developers often encounter a frustrating scenario: attempting to fetch a non-existent resource results in a standard HTTP 404 response, but instead of the expected JSON error payload, the application serves a full HTML view (a View 404). This discrepancy breaks client-side integration and makes debugging difficult.

This post will thoroughly explore why this happens and provide concrete, developer-approved strategies for ensuring your Laravel API consistently returns structured JSON errors when things go wrong. We will look beyond simple route fallbacks and focus on the core of exception handling in a modern Laravel application.

The Root Cause: Laravel's Default Error Flow

The behavior you are observing stems from how Laravel handles unhandled exceptions within its default request lifecycle. When an Eloquent query fails to find a record (e.g., findOrFail() throws a ModelNotFoundException), and this exception is not explicitly caught or handled by specific API logic, the framework defaults to rendering an error page based on its general application configuration, which often results in sending an HTML view rather than a raw JSON response.

For true API development, we must intercept these internal errors and manually construct the desired HTTP response format (JSON with the correct status code). As we strive for clean, predictable responses—a core principle of good API design, much like what is promoted by the principles behind laravelcompany.com—we need to take control of this flow.

Strategy 1: Handling Model Not Found Exceptions Directly in Controllers

Before diving into global exception handlers, the most idiomatic Laravel approach for resource fetching failures is to handle the potential failure directly within your controller logic. This keeps the business logic tightly coupled with the request handling and is often cleaner than relying solely on catching exceptions globally.

When you use methods like findOrFail(), Laravel automatically throws a ModelNotFoundException. You can wrap this call in a try-catch block or, more elegantly for API contexts, leverage method chaining if your framework supports it, although the most straightforward way is often to catch it within the controller itself:

use App\Models\Post;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function show(Request $request, $id)
    {
        try {
            $post = Post::findOrFail($id);
            return response()->json($post); // Success: Return JSON
        } catch (ModelNotFoundException $e) {
            // Failure: Catch the specific exception and return a 404 JSON response
            return response()->json([
                'message' => 'Resource not found.',
                'status' => 404
            ], 404);
        }
    }
}

This approach is highly localized and ensures that any failure related to resource existence results in a clean, predictable JSON response.

Strategy 2: Implementing a Custom Exception Handler (The Global Fix)

While the controller method above solves specific cases, you want a global safety net for all 404s. This is where modifying the exception handler becomes crucial, as you correctly identified. By extending App\Exceptions\Handler, you can modify the render method to specifically check if the request is an API request (e.g., checking for AJAX content or specific headers) before rendering a full view.

Your attempt to modify the render method is the right direction. The key is ensuring that when an exception occurs, we check the context of the request:

use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Throwable;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    // ... existing methods

    /**
     * Render an exception into an HTTP response.
     */
    public function render($request, Throwable $exception)
    {
        // Check if the request is targeting an API endpoint (e.g., AJAX request)
        if ($request->wantsJson() || $request->is('api/*')) {

            if ($exception instanceof ModelNotFoundException) {
                return response()->json([
                    'message' => 'Resource not found.',
                    'error' => 'Model Not Found',
                ], 404);
            }

            // Handle other exceptions similarly if needed...
        }

        // Fallback to the default Laravel behavior for non-API requests or unhandled errors
        return parent::render($request, $exception);
    }
}

By using $request->wantsJson() or checking specific URI prefixes ($request->is('api/*')), you instruct Laravel to bypass its default view rendering and instead construct a JSON response tailored for API consumption. This pattern is essential for maintaining the consistency expected in any modern application, keeping with best practices advocated by laravelcompany.com.

Conclusion

The issue of receiving an HTML 404 instead of a JSON error is rarely a simple configuration glitch; it's usually a mismatch between how the framework handles internal exceptions and how the external client expects API responses. The most robust solution involves a layered approach: handle specific resource-not-found exceptions directly in your controllers for immediate control, and implement a sophisticated custom exception handler to ensure that all other unforeseen errors are also consistently formatted as JSON for API consumers. By mastering this flow, you ensure your Laravel APIs are predictable, reliable, and fully compliant with RESTful principles.