Home > Software design >  Laravel 8 and 9 API route from subfolder, does it still works?
Laravel 8 and 9 API route from subfolder, does it still works?

Time:06-09

I've just test the API using Postman. When I don't using sub-directory, the API works fine like this enter image description here

But if I using sub-directory, it returns 404 Not Found, like this, even I configure nano/etc/nginx/sites-available/mobilku by adding this line following this link : enter image description here

the question is, how to make API routes from subfolder still works like picture in above? Here is my example code for get car using API

Route::prefix('car')->group(function () {
    Route::get('/', [CarController::class, 'all'])->name('car');
    Route::get('/details/{id}', [CarController::class, 'details'])->name('car.detail');
    Route::post('/search', [CarController::class, 'search'])->name('car.search');
    Route::post('/filter', [CarController::class, 'filter'])->name('car.filter');
});

I just read this link Laravel routes not working with subfolder, but I'm not sure that answer still works for Laravel 8 and 9.

CodePudding user response:

laravel web app configuration

According to the laravel v8 and v9 documentation, there are no big changes from the laravel v5 (besides that your routes should now start with the / prefix unlike it was in v5). Both answers from the referred thread also suggests to use a RouteServiceProvider (v8, v9) to define such a prefix globally.

However when you place your app under an additional URI prefix, it will see all the routes as /mobilku/api/... rather than /api/..., so you should change some other code part of your laravel app that you didn't show in your question to made it ready for such a change (or you can somewhat "spoof" the URI it will see from it's side; see the last part of the answer). You may also consider to just rename an /api URI prefix to the /mobilku one.

nginx configuration

Serving a PHP web app under an URI prefix using the alias directive is a bit tricky due the the long standing side effects of the try_files directive used together with the alias one, which are unlikely to be fixed at least until the major nginx version gets changed since there are too many configurations exists adopted to those effects in some way. Commonly used workarounds are:

  • duplicating the try_files directive last argument prefix:

    location /mobilku {
        alias var/www/html/mobilku/public;
        try_files $uri $uri/ /mobilku/mobilku/index.php$args;
        location ~ \.php$ {
            include snippet/fastcgi-php.conf;
            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        }
    }
    
  • using an if instead of the try_files:

    location /mobilku {
        alias var/www/html/mobilku/public;
        if (!-e $request_filename) { rewrite ^ /mobilku/index.php last; }
        location ~ \.php$ {
            include snippet/fastcgi-php.conf;
            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        }
    }
    
  • using an additional named location as the try_files directive last argument:

    location /mobilku {
        alias var/www/html/mobilku/public;
        try_files $uri $uri/ @mobilku;
        location ~ \.php$ {
            include snippet/fastcgi-php.conf;
            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        }
    }
    location @mobilku {
        rewrite ^ /mobilku/index.php last;
    }
    

However none of those workarounds are needed when you are serving an API web app that doen't have any static files at all, and any incoming request should be served by the index.php controller. The much more efficient way, without traversing the request though different location, checking the static files existence and matching an URI against the \.php$ regex pattern (invoking an expensive PCRE library call) will be the following:

location /mobilku {
    root /var/www/html/mobilku/public;
    rewrite ^ /index.php break;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$uri;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

Spoofing a request URI that will be seen by the laravel app

An original request URI seen by laravel application is passed to the PHP-FPM backend via the REQUEST_URI FastCGI variable, which is being defined inside the fastcgi_params file as

fastcgi_param  REQUEST_URI        $request_uri;

If for some reason you can't change your backend laravel (or any other PHP based) web app, you still can change the REQUEST_URI FastCGI variable value that is being passed to the backend.

That $request_uri is an internal read-only nginx variable, so you can't change its value using set directive. However you can redefine its value after it will be defined inside the fastcgi_params file. The PHP-FPM FastCGI backend will use the last defined value (although other FastCGI backends can behave differently; you can check this ServerFault thread to find out more some details). To strip the /mobilku URI prefix from the REQUEST_URI FastCGI variabe you can use an additional rewrite directive:

location /mobilku {
    root /var/www/html/mobilku/public;
    rewrite /mobilku/?(.*) /$1;
    set $tweaked_uri $uri$is_args$args;
    rewrite ^ /index.php break;
    include fastcgi_params;
    fastcgi_param REQUEST_URI $tweaked_uri;
    fastcgi_param SCRIPT_FILENAME $document_root$uri;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

or the following one to replace the /mobilku prefix with the /api:

location /mobilku {
    root /var/www/html/mobilku/public;
    rewrite /mobilku/?(.*) /api/$1;
    set $tweaked_uri $uri$is_args$args;
    rewrite ^ /index.php break;
    include fastcgi_params;
    fastcgi_param REQUEST_URI $tweaked_uri;
    fastcgi_param SCRIPT_FILENAME $document_root$uri;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

However it can be unreliable if your original URI contain some characters that are getting url-decoded during the URI normalization process (for example, some special characters or two bytes UTF-8 characters from national alphabets) because the rewrite directive (as well as the location one) works with the normalized URI while a PHP backend expect to see an url-encoded one. A more reliable way would be to use a map block to strip or replace URI prefix, e.g.

map $request_uri $tweaked_uri {
    ~^/mobilku/?(.*)  /$1;
    default           $request_uri;
}

or

map $request_uri $tweaked_uri {
    ~^/mobilku/?(.*)  /api/$1;
    default           $request_uri;
}

Then you can use a mapped $tweaked_uri variable the same way as shown in the previous example:

location /mobilku {
    root /var/www/html/mobilku/public;
    rewrite ^ /index.php break;
    include fastcgi_params;
    fastcgi_param REQUEST_URI $tweaked_uri;
    fastcgi_param SCRIPT_FILENAME $document_root$uri;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

CodePudding user response:

The Laravel application understands that it is at the root of the server by default.

Try this, I hope it helps you:

source: https://lucabecchetti.medium.com/configure-laravel-to-work-in-a-subdirectory-and-with-artisan-serve-at-the-same-time-654ba0c1fd0b

Add APP_DIR environment variable to your .env file, containing the subdirectory where Laravel is installed into, like showed below:

APP_DIR = "laravel_subdir"

Then create a Helper Provider to define our custom functions, create a file HelperServiceProvider.php inside Providers directory, with the following code:

<?php
    namespace App\Providers;
    use Illuminate\Support\ServiceProvider;
    class HelperServiceProvider extends ServiceProvider{
    /**
    * Bootstrap services.
    *
    * @return void
    */
    public function boot(){
    }
    /**
    * Register services.
    *
    * @return void
    */
    public function register(){
      foreach (glob(app_path() . '/Helpers/*.php') as $file) {
        require_once($file);
      }
    }
}

Then, at the level of App directory, add Helpers directory, and inside it, create a file SubdirectoryAssetsHelper.php, with the following code:

<?php
    if (! function_exists('subdirAsset')) {
    function subdirAsset($path){
        return asset( (App::environment('production') ? env('APP_DIR') : '')."/".$path);
        }
    }
    if (! function_exists('subdirMix')) {
        function subdirMix($path){
            return mix( (App::environment('production') ? env('APP_DIR') : '')."/".$path);
        }
    }

Now register the provider by adding this line to config/app.php file:

App\Providers\HelperServiceProvider::class

Now replace the function mapWebRoutes of file RouteServiceProvider.php, like showed below:

/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes(){
    Route::prefix(App::environment('production') ? env('APP_DIR') : '')
    ->middleware('web')
    ->namespace($this->namespace)
    ->group(base_path('routes/web.php'));
}

And import App class in top of file:

use App; That’s all, now you can use function subdirAsset instead of asset and subdirMix instead of mix inside your blade files.

Switch environment If you are using valet or artisan serve, keep your APP_ENV variable to “local”:

APP_ENV = local

If you are in a production environment of shared hosting, use:

APP_ENV = production
  • Related