Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save almokhtarbr/80c8bc09b5ea17db4d1e7b58e42529fc to your computer and use it in GitHub Desktop.
Save almokhtarbr/80c8bc09b5ea17db4d1e7b58e42529fc to your computer and use it in GitHub Desktop.
Oftentimes you would have a public area, an admin (dashboard) area, and sometimes an API - all inside a single Laravel project. A really good idea would be to separate the files for each area so that your project structure is more clean and there are no unexpected mix-ups. Let's do just that.
The Routes
Having all the routes in the routes/web.php file can get messy and confusing. Let's start with organizing routes for different areas of your web app into separate files. This can be done in the app/Providers/RouteServiceProvider.php file - open it. By the way, I'm working in a fresh Laravel 5.6 project here.
Anyway, if you scroll down a bit you will see the map() method which gets called when a user refreshes a page. It simply calls 2 further methods, which in turn create 2 route groups - one for the API and one for the web (so the web and the API routes are actually separated by default).
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}
For now, we are only interested in the mapWebRoutes() method. You probably know how to create route groups in Laravel, so the code should raise no questions for you. The only difference is, instead of providing a callback function to the ->group() method we're providing a path to a file with routes.
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
You could've had all these different groups inside routes/web.php. Instead, we're going to define them inside this mapWebRoutes() method. First, let's rename web.php to public.php - just to be more specific. Don't forget to rename the file itself inside the routes folder.
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/public.php'));
I'm a big fan of named routes. So, why don't we give all the named routes inside public.php a prefix like this:
Route::middleware('web')
->name('public.')
->namespace($this->namespace)
->group(base_path('routes/public.php'));
Now if you have a route inside routes/public.php, for example:
Route::get('/news/', 'NewsController@index')->name('news');
... its actual name would be public.news and not just news, so you can access it as route('public.news'). This just saves you a bit of time and space, especially if you decide to change the prefix one day.
Next, let's create a route group for the admin area in a similar manner. I'll call my admin area "dashboard".
Route::middleware(['web', 'auth'])
->name('dashboard.')
->prefix('dashboard')
->namespace($this->namespace)
->group(base_path('routes/dashboard.php'));
Note that we've added an additional middleware here, the auth middleware, since unauthenticated website visitors should not be able to access the admin dashboard. And the prefix() method will prefix each of the routes in the dashboard.php file with /dashboard, so your routes would look like this:
Route::get('/news/', 'NewsController@index')->name('news');
... while the actual route would be /dashboard/news. This is beneficial for the same reasons route name prefixes are. Don't forget to manually create an empty routes/dashboard.php php file. Now go create some routes in both public.php and dashboard.php files - make sure everything works as expected.
Controllers
There's a big probability that you'll have different controllers to work with the same entities both in public and admin areas, like the NewsController in our examples above. To separate things you'd have to prefix these controllers, for example DashboardNewsController and PublicNewsController. Not only these long names are ugly, you'd also have a huge bunch of controllers in a single folder. Let's work on that.
Go back to the RouteServiceProvider file. Take a look at our public route group:
Route::middleware('web')
->name('public.')
->namespace($this->namespace)
->group(base_path('routes/public.php'));
The namespace() method is what we're looking for as this sets a PHP namespace for where the controllers for our route group will be. By default it equals to $this->namespace, which in turn (as you can see if you scroll to the top of the file) equals to App\Http\Controllers. Let's simply append to that to have our controllers separated as well.
Route::middleware('web')
->name('public.')
->namespace($this->namespace . '\Publik')
->group(base_path('routes/public.php'));
Route::middleware(['web', 'auth'])
->name('dashboard.')
->prefix('dashboard')
->namespace($this->namespace . '\Dashboard')
->group(base_path('routes/dashboard.php'));
I intentionally made a typo in the word Publik. The thing is we can't use Public in namespaces in PHP since public is a reserved keyword. Maybe you can come up with a better name, doesn't matter at this point. Do the same for the api.php file if your project has an API, for example like this:
...
->namespace($this->namespace . '\Api')
...
Again, we're just making things shorter and more unified here, e.g. now if you have a route defined inside public.php like this:
Route::get('/news/', 'NewsController@index')->name('news');
... it will point to App\Http\Controllers\Publik\NewsController and not App\Http\Controllers\NewsController. Of course, you have to create the corresponding folder structure on your own and specify the correct namespaces for your controllers. Or better just create your controllers by running, for example, php artisan make:controller 'Public\NewsController' or php artisan make:controller 'Dashboard\NewsController'.
Just like with the routes I encourage you to create some dummy controllers and test things out.
Assets
You might want to separate your assets in case you are using Laravel Mix. There's a big chance you'd want completely different styles for your admin dashboard and the main website. Open the webpack.mix.js file. This is what you have by default:
let mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css');
The .sass('resources/assets/sass/app.scss', 'public/css') line takes the resources/assets/sass/app.scss and compiles it into public/css/app.css. I guess you already see what we'll have to do?
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/dashboard.scss', 'public/css')
.sass('resources/assets/sass/public.scss', 'public/css');
Now every time you compile files you'll have public/css/public.css and public/css/dashboard.css outputted. Include each in its respective layout. The source dashboard.scss and public.scss files could be completely different from one another with different imports from different folders.
Views
I don't have any specific techniques for the views. What I would do is simply create public and dashboard sub-folders inside the resources/views folder and store all the corresponding views in those sub-folders. Additionally create separate layouts for each area inside the resources/views/layouts folder.
Tests
You might want to create sub-folders for the tests as well. For PHPUnit to take your folders into account, you should add them into the phpunit.xml file. By default there are 2 folders: Unit and Feature:
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
Instead of having a single Feature (or Unit) folder you could create separate folders for each area of your web app, for example:
<testsuite name="FeaturePublic">
<directory suffix="Test.php">./tests/FeaturePublic</directory>
</testsuite>
<testsuite name="FeatureDashboard">
<directory suffix="Test.php">./tests/FeatureDashboard</directory>
</testsuite>
<testsuite name="FeatureApi">
<directory suffix="Test.php">./tests/FeatureApi</directory>
</testsuite>
The only downside here is when you use artisan to generate your tests (e.g. php artisan make:test BrowseNewsTest) - those will still be created in the default Feature folder with the default Tests\Feature namespace. You'd have to move your test files to the desired folders manually, while the tests should still work even if you don't change the namespace.
Final Words
Now you have all the files properly separated - your project structure is cleaner, there's way more less ambiguity and stuff doesn't get unnecessarily mixed-up. The whole process is really quick and I suggest you do this for every project you work on.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment