joostr02's avatar

Best practice for using the same controller function for web and API users.

Hi there,

This is my first time making an post here and I hope I've put it under the right channel. But I'm strugling with making an function of my controller available for API users and web users. I want to make an API endpoint which returns data with pagination and where the user is able to filter through JSON that he posts to the endpoint. I also want to make an headless web application with vue + nuxt where he can view and filter the data in a browser while also being able to extract it through the API. What is the best way to approach this? Is it for example creating/adding middleware for session and token based authentication or exposing that function to a route in the api.php file and web.php file or put an API access token in the users browser cookies or could it be something else?

Thanks in advance.

0 likes
11 replies
Tray2's avatar

I would suggest using different controllers, using the same will give you fat controllers and unclean code. Common code can then be pushed either into the models, or into service classes.

1 like
joostr02's avatar

Hi @Tray2 does this also apply if the logic of the functions in the two controllers remain the same? The main issue I run into is the authentication part that's different between the API and web endpoints. I've also made a controller for every model, should I do a different approach?

Tray2's avatar

@joostr02 Each model should have at least one controller handling the basic crud. The authentication is usually placed on the route as a middleware, or defined in the controller.

I like placing them in the controllers constructor like so, but then I am using invokable controllers aka single action controllers.

class BooksEditController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

When it comes to api authentication, I suggest using Sanctum

https://laravel.com/docs/10.x/sanctum

1 like
JabatoForever's avatar

hi @joostr02 , i do this all the time for indexes that need to be used as index pages and autocompletes enpoints API in Inertia, Here's an example:

public function index(Request $request)
{
    $clients = QueryBuilder::for(Client::class)->select('clients.name')->allowedFilters(['name', 'email']);

    if ($request->wantsJson()) {
        return $clients->limit(10)->get();
    }

    return Inertia::render('Client/Index', [
        'clients' => IndexResource::collection(
                            $clients->paginate($request->get('per_page', 10)
              )),
    ]);
}

as u can see u check if the $request->wantsJson() and if so i just return a json response, in my case i use the same route since i'm using inertia and i don't really need to separate the routes, but in your case i would suggest creating 2 separate routes one for Api and The other for the web that points to the same controller, then this condition:

if($request->wantsJson()){
  ///
  return [json.respnse]
}
return view('your-view')

should handle what case are we looking at and return the data accordingly.

JabatoForever's avatar

@krisi_gjika Yes, you're right. You could use the resource for both. I didn't need it for the autocomplete in this instance. The returned data didn't require any modifications for the autocomplete part.

krisi_gjika's avatar

@Snapey I meant why not wrap $clients->limit(10)->get() in IndexResource::collection also

joostr02's avatar

Hi @JabatoForever How do you handle the authentication on that endpoint for API users and web users? Or are those public endpoints?

jaseofspades88's avatar

If you're going to consolidate your view and api controllers, why not just simply make one controller that can deal with all routes, views, apis and everything? Oh yes, because that would be a ludicrous idea.

The real question here is 'How do I separate my concerns within a controller?' and @tray2 already gave you a concise and clean answer above.

1 like
JabatoForever's avatar

@joostr02, if you are using sanctum, use the middleware to protect API routes. In your api.php routes file, wrap your routes within the auth:sanctum middleware group like this:

Route::middleware('auth:sanctum')->group(function () {
      Route::resource('/myroute', MyController::class);
});

and the same for the web route in the web.php file with the auth middleware

Route::middleware('auth')->group(function () {
   Route::resource('/myroute', MyController::class);
});
2 likes

Please or to participate in this conversation.