Building a Laravel 8 Application: Routes and Views

In this post, we start building a Laravel 8 Application just created. You can follow the tutorial Create a Laravel 8 application from scratch to install and create your first project and the initial application structure. However, this can be applied to a project created using the Laravel Sail tools or Composer.

The main areas to work at the start of the project are:

  • Understanding the project directory structure
  • Navigation paths, adding/modifying routes
  • Application screens, creating/modifying views

Laravel 8 Project Directory Structure

The main folders used by a laravel 8 project are:

  • app: contains the application code. It includes Controllers, Middleware, Models, and Providers
  • bootstrap: includes the app.php file (main Application object). It contains the cache directory, used to cache generated files to improve performance.
  • config: contains all the configuration files, including app localization, timezone, application components, databases, filesystems, logs.
  • database: includes database scripts for schema definitions, migrations, and factories. You can use this directory to store an SQLite database.
  • public: all the public/static files to be served by the application, including the main page script (index.php), icons, and other compiled assets.
  • resources: contains the application views (PHP blade templates) as well as any un-compiled assets such as CSS or JavaScript. This directory also stores application language files for internationalization.
  • routes: this is the place to define routes for your application, console commands, REST APIs, and broadcasts.
  • tests: contains PHPUnit automated tests created to verify your application components, and generated reports of the test results.
  • vendor: this folder automatically stores all Laravel and third-party Composer components and the autoloader scripts.

A plain view of the main directory tree looks like this:

+-app
|---Console
|---Exceptions
|---Http
|---Models
|---Providers
+-bootstrap
|---cache
+-config
+-database
|---factories
|---migrations
|---seeders
+-public
+-resources
|---css
|---js
|---lang
|---views
+-routes
+-storage
|---app
|---framework
|---logs
+-tests
|---Feature
|---Unit
+-vendor

Laravel 8 application routes and views

The first step is to learn about the Laravel routing configuration. Routing is the mechanism to map a URL path to a specific resource of the application. The main configuration files for routes are stored in the /routes/ folder:

  • web.php : routes for the web application.
  • api.php : routes for a REST API interface
  • console.php : commands to run using artisan console commands
  • channels.php : used to “broadcast” your server-side Laravel events over a WebSocket connection (see https://laravel.com/docs/8.x/broadcasting)

Web routing

The main application route configuration is stored at routes/web.php. This file already contains the main route (the root path / ) pointing to a welcome page view.

Route::get('/', function () {
    return view('welcome');
});

Views are stored in the /resources/views/ folder. Normally, you can use blade templates to work with PHP views adding additional templating features like value formatting, loops, conditions, sub-views. In this example calling to view(‘welcome’) will route the root path to the file /resources/views/welcome.blade.php (see more details in the views section)

Each route has a method, a URI expression and the request callback to manage requests to this route.

Route methods

A route can be defined using any common HTTP method, matching the Route class method name:

Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

Also, you can use Route::match() to match more than one method, and Route::any() to match any method.

Route::match(['get', 'post'], '/', function () {
    // controller code for both GET and POST requests
});

Route::any('/', function () {
    // controller code for any request method
});

Route URIs

The $uri parameter is a literal path or a path expression containing parameters surrounded by brackets (eg: {parameter}).

// literal route, exact match
Route::post('/register', function (Request $request) {
    return view('register');
});
// parametrized route , example: /notification/1234
Route::get('/notification/{id}', function ($id) {
    return view('notification',['notification_id' => $id]);
});

Route callbacks

Callbacks could be inline functions, like in the previous examples, or calls to Controller methods:

use App\Http\Controllers\LoginController;

Route::post('/login', [LoginController::class,'authenticate']);

In some cases, the controller class could be defined dynamically, using the full namespace:

Route::post('/login', ['uses'=>'App\\Http\\Controllers\\LoginController@authenticate']);

Route Controllers

Controllers can be defined in the app/Http/Controllers directory, using the name of the controller class as the filename (LoginController.php):

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    /**
     * Handle an authentication attempt.
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function authenticate(Request $request)
    {
        // user authentication
    }
}

Requests and responses

Finally, you can use the Request object (passed to your controller function before any route parameter) to get information and available methods to manage the $request and the final response(). For example:

Route::patch('/user/{id}', function(Request $request, $id){
    $user = User::findOrFail($id);
    $user->fill($request->only(['firstName','lastName']));
    $user->save();
    if ($request->expectsJson()){
        return response()->json([
           'status' => 'success'
        ]); 
    }else{
        redirect('/user/'.$id);
    }
}

This code will retrieve the user $id from the route path ({id}) and specific data from the user using $request->only([keys]), in order to replace this information in the user model data (calling $user->fill() and $user->save()). Additionally, it will return a JSON response if the request expects JSON (from an AJAX-type request, for example) or simply redirect to a specific URL path.

By default, Laravel will return the content using the text/html mime-type. If you want to respond with a different response code or adding headers, you can use the response(content, status) function:

return response('Text content', 200)
       ->header('Content-Type', 'text/plain');

You can redirect your response using redirect('/path'). Also, you can redirect the response to a specific Controller/method, also passing parameters:

return redirect()->action(
    [PaymentController::class, 'thankyou'], ['transaction_id' => $id]
);

One common scenario is to process data and the return a view (using a Laravel blade template file or a plain PHP view):

Route::get('/terms-and-conditions', function () {
    return view('terms');
});

In the next section, we explain how views works.

Laravel 8 application views

By default, Laravel views are built using the Blade template language, and stored in the /resources/views/ folder. The Blade templating language is a mix of literal HTML code, directives starting with @ (example: @include, @if and @foreach) and PHP expressions surrounded by double brackets, for example:

@if( count($users)>0 )
    <span >{{ count($users) }} users</span>
@endisset

To differentiate plain PHP views from blade templates, use the .blade.php suffix to process a file as a blade template.

Displaying data

Blade uses the double curly brackets ( {{ expression }} ) to echo information to the PHP output. By default, all the content generated by blade expressions is automatically encoded/escaped to prevent XSS attacks (using raw HTML to break your code). You can use any valid PHP variable, expression, or function call as an expression:

<div>Number of items: {{ count($items) }}.</div>

Displaying raw HTML

Sometimes you may want to display HTML generated code in your template (warning: do this only from a trusted generated code, not directly from the user input), in this case, use the double exclamation mark between curly brackets ( {!! expression !!}) to display raw HTML

{!! $component->render() !!}

Displaying JSON data

You may need to embed JSON code inside your template. When you need some javascript code to process JSON data use @json($data). Optionally, you can add the JSON_PRETTY_PRINT option to format the JSON output:

<script>
    var appdata = @json($appdata, JSON_PRETTY_PRINT);
    var app = new Vue({
        el: '#app',
        data: appdata
    })
</script>

The expression will be converted to a JSON-compatible expression like:

var appdata = { "id" : 1241, "name" : "Product 1"};

Blade Directives

Laravel Blade directives help to build complex views using programmatic logic found in most programming languages. The most common directives are:

Conditional @if directive

Is the common way to conditionally display a block based on conditions:

@if (count($results) === 1)
    <span>1 result</span>
@elseif (count($results) > 1)
    <span>{{ count($results) }} results</span>
@else
    <span>No results</span>
@endif

@isset and @empty directives

@isset checks if a variable is defined and not null. On the other hand, @empty checks if the variable has an ’empty’ value: null, false, '', [] empty array and others (see empty())

@isset($errorMessage)
    <span class='error-message'>{{ $errorMessage }}</span>
@endisset

@empty($records)
    <span>No matches found</span>
@endempty

Loop directives

You can loop over data using @for @foreach @forelse and @while

// regular for
@for ($i = 0; $i < count($results); $i++)
    <div>Name: {{ $results[$i]["firstName"] }}</div>
@endfor

// for each element of a list
@foreach ($results as $result)
    <div>Name: {{ $result["firstName"] }}</div>
@endforeach

// for each element, fallback if empty 
@forelse ($results as $result)
    <div>{{ $result["firstName"] }}</div>
@empty
    <div>No results</div>
@endforelse

// loop while a condition is true 
@while ($dataSource->hasData())
    <div>{{ $dataSource->data['id'] }}</div>
@endwhile

As in regular loops, you can use the @break to stop the loop, and @continue directive to skip/pass the current loop. Also, you can check the value of $loop->index (0-based) attribute to get the current loop index or $loop->iteration (1-based) to get the current loop count (see more options in the Laravel Blade documentation).

@foreach ($users as $user)
    @if ($user->isDeleted())
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($loop->iteration == 10)
        @break
    @endif
@endforeach

Adding sub-views using @include

You can include other sub-views inside a blade template using @include( view, parameters). For example, you can create your views using a mix of local content and reuse of common components (the same header/footer for each page, for example):

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    @include('head',['title'=>'My Page'))
<body>
    @include('components/toolbar')
    <div id="content">
      My content ...
    </div>
    @include('components/footer')
</body>
</html>

This structure will use a sub-view stored in /resources/views/head.blade.php to fill the <head></head> section and 2 components to create the header/toolbar and the footer of the application (both created at /resources/views/components/). However, all relative paths when calling @include will be relative to /resources/views/.

Going further with views

Laravel also provides a way to reuse a common page structure using Layouts. This will be covered in a different post. Stay tuned!