soulbork's avatar

How to separate views from controller methods?

My question is: How do I tell my controller to sometimes return a view, and sometimes return JSON?

I created a resource controller called GameController . The Index method shows a list of all games the user has joined at the URL http://127.0.0.1/games.

class GameController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //Get all games user is part of
		$games = Game::addSelect(...)->get();
        return view('games.all', ['games', $games']);
    }
	...
}

This works great when I want to display a list of the users games in the browser.

Now I want to write some JavaScript to set up some event listeners, so that the user is notified when an event happens in their game. Something like below in app.js:

//Listen for some game events
$.get("/games", function( games ) {
	for (let i = 0; i < games.length; i++) {
		Echo.private(`Game.` + games[i]['id'] + `.Started`).listen('GameStarted', (e) => {
			console.log('Game started');
		});
	}
}

The problem is my GameController::index method at the /games URL returns a view, when my JavaScript obviously needs the JSON.

So this brings me back to my question at the top of the page - how do I tell my controller to sometimes return a view, and sometimes return JSON?

I suspect this is where I need to start using the api.php routes, but I am unsure how to do this. Any code examples would be hugely appreciated. Thanks in advance!

0 likes
12 replies
tisuchi's avatar

@soulbork You can use route parameters to specify whether to return a view or JSON data. For example:

Route::get('/games/{format}', 'GameController@index');

Then, in your controller method, you can check the value of the format parameter and return either a view or JSON data accordingly:

public function index($format)
{
    $games = Game::addSelect(...)->get();
    if ($format == 'json') {
        return response()->json($games);
    }
    return view('games.all', ['games', $games']);
}

You can then use the following URLs to get either a view or JSON data:

http://127.0.0.1/games/view (returns a view) http://127.0.0.1/games/json (returns

1 like
soulbork's avatar

@tisuchi Thanks for your response.

I did consider something like this, however I didn't want to resort to having to put an if / else statement at the end of every one of my methods. However if this is in fact the best way to do it then that's what I'll do, thanks!

tisuchi's avatar

@soulbork I am afraid that it may not possible easily without conditioning because you are mixing two return types.

With that, maybe some other people have different solutions.

1 like
vincent15000's avatar

You can also try something like this.

if ($request->expectsJson()) {
	// return a JSON object
} else {
	// return the view
}

Or like this.

if ($request->ajax()) {
	// return a JSON object
} else {
	// return the view
}
soulbork's avatar

@vincent15000 Thanks Vincent. I just made the same reply to tisuchi that I was hoping to avoid if / else statements everywhere, but if they are unavoidable then that's what I'll use. Thanks!

1 like
Tray2's avatar

@soulbork Then the only way to do that as far as I know is to use different routes and controllers.

//web.php
Route::get('/somemodel', [SomeController::class, 'index'])->name('somemodel.index');

//api.php
Route::get('somemodel', [ApiSomeController::class, 'index']);
4 likes
vincent15000's avatar

@soulbork @tray2 My suggestion is only relevant if you have exactly the same data to send to the view or as a JSON object. Otherwise @tray2 has shared with you a better approach.

PovilasKorop's avatar

@soulbork Mixing WEB and API Controllers may be a bad practice, because it goes against the "Separation of Concerns" principle: they serve different purposes, return different responses, so should be separated, in my opinion.

app/Http/Controllers/UserController.php:

class UserController extends Controller {
    public function index() {
        $users = User::all();
        return view('users.index', compact('users'));
    }
}

routes/web.php:

use App\Http\Controllers\UserController;

Route::get('users', [UserController::class, 'index']);

app/Http/Controllers/Api/UserController.php:

class UserController extends Controller {
    public function index() {
        $users = User::all();
        return response()->json($users); 
    }
}

routes/api.php:

use App\Http\Controllers\Api\UserController;

Route::get('users', [UserController::class, 'index']);

Then the web page can be accessed with /users, and the API endpoint can be called with /api/users, as all the routes in routes/api.php file get the "api" prefix automatically.

I've also listed other options mentioned by @tisuchi and @vincent15000 above, in a short article, for future reference to answer other similar questions.

3 likes
soulbork's avatar

@PovilasKorop thanks Povilas. Great article, just check your last paragraph. You've used a UserController example in your main article, but still reference the /games link in the conclusion

1 like
vincent15000's avatar

@PovilasKorop Yes I agree with you. The best approach is also to have only one return and don't mix several purposes in the same function. And I rarely don't respect this rule.

1 like

Please or to participate in this conversation.