Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

dennisprudlo's avatar

Web Routes, Api Route and Single Source of Truth

Hey guys,

so I am developing an application wich will have an API (for a native mobile app) and a web interface. I think its pretty clear where to put my routes (web.php and api.php). But how should I structure my controllers? I often read that the best way is to have like a web-controller and an api-controller for each resource but I wouldn't want to duplicate my business logic.

I ended up with a way that helps me to use one controller method for both routes. I added an api-function to the BaseController that simply checks if the api middleware was used, since only the api-routes will use it.

public function api() : bool {
    return in_array('api', request()->route()->computedMiddleware);
}

In my controller methods I use this function to determine whether the request was made as an api-request or a web-request.

/**
 * Display a listing of the resource.
 *
 * @return \Illuminate\Http\Response
 */
public function index()
{
    //
    // Some general business logic to retrieve the data
    $users = User::all()->where('...')->get();

    if ($this->api()) {
        return UserResource::collection($users);
    } else {
        return view('users.index', [
            'users' => $users
        ]);
    }
}

This allows me to use one controller method for either request type and use the $this->api()-function whenever I have to handle something differently for api requests (such as returning the requested data).

Now my question: Is this a valid (or event recommended) way of handling this? As I said I often hear to use two different controllers and basically copy the logic from one into another but that would be way harder to maintain.

I would appreciate it if someone has had the same "issue" and found an acceptable solution. Especially when it comes to different api versions. How would you recommend to implement this in a neat way without copying everything and make minor adjustments for the new version.

Thanks in advance Dennis

0 likes
9 replies
martinbean's avatar
Level 80

@dennisprudlo I’m not a fan of this approach, as it then means practically every controller action is now going to have that if/else statement, which is going to be repetitive and isn’t very “DRY”.

There’s nothing wrong with having one controller class serve web requests and another serve API requests. There are patterns you can use to DRY out the business logic: repositories or service classes for example.

It also means your web and API requests are very tightly coupled. You can’t use different query string filters in your web requests from your API requests now. If you want to use different filters in web requests vs. API requests, guess what: more conditional logic.

Another drawback is, web requests usually have more data than an API request. So you might have filters in your web UI, with options retrieved from the database. If you’re reusing your web controller for API requests, then API requests are going to invoke this query as well even if the options aren’t actually presented. You also can’t do things like use different pagination limits for web and API requests (i.e. you might want to show 30 items at a time in your API, but only 10 at a time in your web UI).

The best rule of thumb is to not try and be too “clever” when it comes to code. Premature optimisation and overzealous DRY-ing out of code can just lead to code that’s more difficult to maintain and work with. It’s always good to strive for DRY code, but here I think you’ve looked at DRY-ing it at the wrong level: you’re looking to DRY out the symptoms (the controllers) rather than the actual cause (the business logic). Stick to conventions, as they don’t tend to come about by accident.

2 likes
dennisprudlo's avatar

Yeah thats true I guess over time the controller method would be full of if ($this->api()) lines.

When you said service classes where you thinking of like a SubscriptionService-class which has functions to create, cancel, renew, up/downgrade, ... a subscription or more of multiple classes that handle only one particular task such as a RenewSubscription and ChangeSubscriptionPlan class?

I am asking because I found this article on service classes: https://medium.com/@remi_collin/keeping-your-laravel-applications-dry-with-single-action-classes-6a950ec54d1d

jlrdw's avatar

If mobile is displaying the same exact stuff to a user, why would you need separate API routes and Web routes. Just make the mobile portion mobile-friendly.

Then all controllers and models can be the same.

You made an API for your own data, normally an API interface is when you're getting data from someone else's site to display on your site, or vice versa.

dennisprudlo's avatar

The api is for a native mobile app to consume not a responsive version of the web app so I couldn’t just return the view which renders the whole page including navigation elements, JavaScripts etc.

The api should only return the resource as a json formatted using Eloquent API Resources.

Besides the user will be authenticated differently within the api routes.

trin's avatar

if you can adapt your view to render Resource, then everything is simple:

add to BaseController function

public function send ($data, $view) {
	if ($this->api()) {
		return $data;
	} else {
		return view($view, $data);
	}
}

and in your controller:

public function index()
{
    //
    // Some general business logic to retrieve the data
    $users = User::all()->where('...')->get();
	return $this->send([
		'users' => UserResource::collection($users)
	], 'users.index');
}
…
public function get(User $user)
{
	return $this->send([
		'user' => new UserResource($user)
	], 'user.info');
}
dennisprudlo's avatar

I think this would still be an issue for the reasons @martinbean has elaborated in his post. It certainly would "hide" the if ($this->api()) condition in the controller method but what for example if I want to do something with the user resource before returning it only for api requests. This would end up in more conditions, right?

trin's avatar

yeah, but it is means, you have different controllers methods for api/view ) @jlrdw recommended use one mthod and generate mobile-friendly html. my position -- have only api controllers methods and generate web version on api )) and I understand that this solution, similar to the solution from @jlrdw, is not available to you.

martinbean's avatar

have only api controllers methods and generate web version on api

@trin So your solution is to instead do two HTTP requests to render a page? One for the initial page request, and then have the user wait around whilst that page does a second HTTP request to an API to get the data for the page…?

1 like
trin's avatar

yes, is it problem? in 2021, really? you know every js, css, image, etc. is this a separate request? “The only difference is that this is a static requirement,” you say. and I'll say "like the page generated by the frontend." the idea is that html should and can be cached as opposed to api requests. which is very easy to control with this approach. opening any popular site like Google, Facebook, Instagram and others, you will see many xhr requests. moreover, by opening the inspector on this page, you will see that it is generated from xhr requests to the api, and not generated on the server.

and I will repeat what I wrote in the near topic. I'm not trying to impose my opinion on you. it's just a discussion

Please or to participate in this conversation.