Isak Berglind

Isak Berglind

External api routes in laravel

Isak Berglind • September 26, 2017

php

Almost all of the apps i write nowadays are communicating with an external API of some sort. It can be an API i wrote myself, or some service used to get or post data.

Some of these services provides a SDK to make this process easy, but some doesn’t.

In the latter case I find myself hard coding the URLs of the service in a gateway layer, controller or similar. Although this is not a major concern, it feels kinda dirty for several reasons.

  1. Hard coded strings almost always look and feel dirty.
  2. If these endpoints change at some point, you have to find all the places you’ve referenced them.
  3. You potentially have to write logic for building URLs with dynamic parameters, which can be cumbersome.

For the internal endpoints in your applications you can define and name your routes in the route/web.php file and then get the URL with the route() helper function. Why not do the same with external routes?

Lets try it!

Create a new file in the “routes/” directory. For this example i use the name “external.php”.

In this file, let’s create some routes for an imaginative service.

<?php
Route::group([
   "domain" => "http://www.external-service.com"
], function () {
    Route::get("api/user/")->name("external.user.index");
    Route::get("api/user/{id}")->name("external.user.show");
    Route::post("api/user/{id}")->name("external.user.store");
    Route::put("api/user/{id}")->name("external.user.update");
    Route::delete("api/user/{id}")->name("external.user.delete");
});
// Note that the “domain” url could be put in the .env file.

As it turns out, you don’t need to specify an action for your routes, the route() helper will work fine without it.

Now, we need to register out new routes file in the RouteServiceProvicer.php

in the map() function, add this line:

$this->mapExternalRoutes();

and then create the function:

/**
 * Define the "external" routes for the application.
 *
 * @return void
 */
protected function mapExternalRoutes()
{
   Route::namespace($this->namespace)
        ->group(base_path('routes/external.php'));
}

You can now access your endpoints like this:

route('external.user.show',1);
// http://www.external-service.com/api/user/1

Pretty cool right?

But.. I bet you see a problem here.. What if a user tries to visit one of the external routes? that will throw a weird error, and that we don’t want.

EDIT: This step is not necessary, since hitting the external routes will be impossible anyway. Didn’t think about that at the time ;) I’ll leave it here anyway since it might help someone.

Lets solve it using a middleware.

Create one using artisan:

php artisan make:middleware Inaccessible

In the newly created middleware, add this to the handle method:

/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next)
{
     abort(404);
}

Probably the simplest middleware ever written ;) All it does is if someone tries to access any of these routes, it will throw a 404. Just like it would if the route wasn’t defined.

Now we need to register the middleware in the Http/kernel.php

protected $routeMiddleware = [
    //....
    \App\Http\Middleware\Inaccessible::class,
];

.. And make a new middleware group in the $middlewareGroups array

'external' => [
     'inaccessible'
]

Now all that’s left is using the middleware for our external routes. lets apply them in the RouteServiceProvicer.php

/**
 * Define the "external" routes for the application.
 *
 * @return void
 */
protected function mapExternalRoutes()
{
    Route::namespace($this->namespace)
        ->middleware("external") // <--- add this
        ->group(base_path('routes/external.php'));
}

Done! You will now be able to use a route file for your external routes and use the built in route() helper to access them.

I haven’t really used this solution in any of my applications yet, I was just playing around with the problem and came up with this. Maybe there is some major flaw i haven’t thought about. What do you think?

Please write me a line on twitter at @Isak_Berglind :)