Laravel 5.8 Tutorial from Scratch for Beginners

  • 2019-04-06 01:25 AM
  • 98

Laravel 5.8 Tutorial from Scratch for Beginners

In this series, We will make a basic (but rich in functionality) todo application with Laravel 5.8. The main purpose of this series is to get started with Laravel. You will be learning all the basic and important parts of this framework. Let’s get started.

Laravel 5.8 From Scratch: Intro, Setup , MVC Basics, and Views.

What You Will Learn In This Series

  • Installing and setting up Laravel locally.
  • Models, Views, and Controllers (MVC).
  • Database, Migrations, and Eloquent ORM.
  • Authentication.
  • Email verification and Password reset.

Introduction

Laravel is the most popular PHP framework built by Taylor Otwell and community. It uses MVC architecture pattern. It provides a lot of features like a complete Authentication System, Database Migrations , A Powerful ORM, Pagination, and so on. Before creating the application, you will need to have PHP 7.2 and MySQL (we are not using apache in this series) and composer installed. I use xampp which is a package that comes with PHP, MySQL, and Apache. Composer is a dependency manager for PHP. It’s similar to npm which is a dependency manager for Javascript. Also, read the server requirements for laravel if you run into installation errors.

Installation

Laravel provides an installer which can be used for creating Laravel projects. It can be install with composer:

composer global require "laravel/installer"

Now we can create our laravel project with:

laravel new myapp

Above command will create our project. We can also use composer to create a laravel project (which I do):

composer create-project laravel/laravel myapp

MVC

A Model is used to interact with the database and of course it does not have to be a database. It could be a JSON File or some other resource. A Controller contains the logic e.g how to validate form data and save a resource to the database by interacting through Model. View is the User Interface (UI) of the application that contains HTML or the presentation markup. It can also have logic e.g loops and conditionals. Template engines are used to embed logic in Views. Laravel has Blade template engine that is used for adding logic inside the views. you can read more about MVC at wikipedia.

Directory Structure

Let’s look at the basic folder structure of laravel application e.g where are Views, Models, and Controllers etc.

Routes

In laravel, there are two routes file web.php and api.php. web.php file is used for registering all the web routes like mywebsite.com/about or mywebsite.com/contact. api.php is used for registering all the routes related to an api. We are only using web routes so don’t worry about any api routes.

Controllers and Models

Models are stored in app directory and there is a default user model that comes with laravel for authentication purposes. app\Http\Controllers has all the controllers and there are some default controllers for authentication.

Views

Views can be found in resources/views and welcome.blade.php is a default view provided by laravel. Laravel views have a .blade.php extension so whenever you create a view don’t forget to add the blade extension. resources/sass has all sass files and js has vuejs components and bootstrap files (we won’t talk about that).

Other Directories

You can put your css, js, and all other static assets in public directory. Config directory has all the configuration files related to our application like database, session, and other configurations (we will talk about config in the next tutorial). There are other directories but you don’t need to worry about that.

Starting the Development Server

Since the root directory of the project is public, we will have to create a virtual host but instead of creating a virtual host, I want to make it simple by using Laravel development server with command (you should be inside the root of your project directory):

php artisan serve

This will start the development server on port 8000. By any chance, if you can’t use that port then you can use **— **port flag to specify a different port:

php artisan serve --port=8080

Routes and Controllers

Routing is a mechanism by which requests (as specified by a URL and HTTP method) are routed to the code that handles them and that code in MVC is a controller method. e.g / will be your home and /about will be your about page. If you open up the web.php file, you will see just one route:

<?php

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

We have a ‘/’ route that uses a callback/closure making a GET request that returns a view named welcome. In a route, the first parameter is the url and the second parameter can be a callback or controller action. Let’s create a controller named PagesController and update our route. In laravel, we have the ability to name our routes which comes in handy when you change the url later on. We will define two routes for our index page and about page:

<?php

// syntax: '[email protected]'

Route::get('/','[email protected]')->name('pages.index');
Route::get('/about','[email protected]')->name('pages.about');

GET method is used for retrieving data from the server. In our case we will always use GET routes for displaying a plain view or a view with data. With GET method we can pass data to the query string e.g search term or date range for a report but you should not insert sensitive data like email or password since it’s visible in the URL. For passing sensitive data to the server (when we submit a form), we use POST method because the data is sent in the request body. There are other request methods but the most commonly used are GET and POST. PUT method is used for updating data and DELETE for deleting data.

We need to create that PagesController and we can do that by typing this command:

php artisan make:controller PagesController

Laravel uses autoloading and all the classes are namespaced so if you want to create a controller inside a sub-directory then you should use artisan(It will create the directory if it does not exist):

php artisan make:controller Admin/LoginController

Above command will create a LoginController inside a sub-directory called Admin. Most of the stuff can be done through artisan which is a command line tool that comes with laravel. You can create models, controllers, migrations and other files with artisan.

Let’s open the PagesController located in app\Http\Controllers:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PagesController extends Controller
{
    //
}

It has a class PagesController that’s extending Controller class which will provide us all the controller functionality. It also has a Request class imported with “use” statement which is used to access form data and validation. In this controller, we need two methods namely index and about, both will return views. let’s add them:

public function index(){
    return view('pages.index');
}

public function about(){
    return view('pages.about');
}

Index method is returning index view stored inside the pages folder and about method is returning a view named about. In laravel, you don’t have to specify view extension and if you have a view inside a folder e.g index view inside posts folder then use dots like view(‘posts.index’).

Also If you want to see the list of all the registered routes in your application then type the below artisan command:

php artisan route:list

Views

First of all, we will create a layout file which will have all the markup that gets repeated by views e.g doctype, head, and body tags. Usually, layout files are stored in layouts folder in views directory, named app (you can name it whatever you want). let’s create an app.blade.php file after creating layouts folder and add this markup:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <link rel="stylesheet" href="{{asset('css/app.css')}}"> {{-- <- bootstrap css --}}

    <title>@yield('title','Laravel 5.8 Basics')</title>
</head>
<body>

    {{-- That's how you write a comment in blade --}}
    
    @include('inc.navbar')
    
    <main class="container mt-4">
        @yield('content')
    </main>

    @include('inc.footer')

    <script src="{{asset('js/app.js')}}"></script> {{-- <- bootstrap and jquery --}}
    
</body>
</html>

Blade directives start with “@” symbol. @include() is used for including a separate view file. As you can see inc is the name of the folder and navbar is the name of the view. @yield() will output the content of the view section that’s extending this layout. Double curly braces “{{}}” are used for echoing a variable or calling a helper function. Asset() helper is used for generating URLs to public assets stored in public directory like CSS, Images, and Javascript. Note that laravel comes with bootstrap 4 and that’s what we are including.

Let’s create navbar.blade.php in inc folder and add the markup:

<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
    <div class="container">
        <a class="navbar-brand" href="{{ url('/') }}">
            {{ config('app.name', 'Laravel') }}
        </a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <!-- Left Side Of Navbar -->
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a href="{{route('pages.index')}}" class="nav-link">Home</a>
                </li>
                <li class="nav-item">
                    <a href="{{route('pages.about')}}" class="nav-link">About</a>
                </li>
            </ul>

            <!-- Right Side Of Navbar -->
            <ul class="navbar-nav ml-auto">
            </ul>
        </div>
    </div>
</nav>

Inside the navbar we are using url() helper which is used for generating application urls. route() function is used to generate urls of named routes. config() is used for retrieving values from files inside the config folder, app.name will return “name” value from the app.php config file, second parameter is a fallback value or else it will return null. You can use dot syntax for accessing values inside of an array e.g config(‘file.array.string’,’fallback value’).

Now whenever you create a view just add the below lines of code. All the content of your page will be inside the @section directive.

@extends('layouts.app')
@section('content')
  My Page Content
@endsection

Index.blade.php view inside the pages folder will have the following markup:

@extends('layouts.app')
@section('content')
    <h2 class="mt-5 mb-3 text-center">Laravel 5.8 For Beginners!</h2>
    <p class="text-center">Lorem ipsum dolor sit amet consectetur adipisicing elit Aut.</p>
@endsection

And for the about.blade.php view:

@extends('layouts.app')

@section('title')
Laravel 5.8 Basics | About Page
@endsection

@section('content')
    <h3>About Page</h3>
    <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
@endsection

As you have noticed, we have a @yield directive inside the title html tag and we are defining the section inside our about view so when you will access the about page from the browser you will see that the title on the browser tab is changed.

In the next tutorial, we will talk about accessing and updating config values from .env file then we will create, read, update, and delete todos with MySQL database.

Source code till this tutorial HERE | complete project HERE

Laravel 5.8 From Scratch: Config, ENV, Migrations, and Todos CRUD

In this tutorial, we will talk about Models, Database Migrations and make CRUD (CRUD means Create, Read, Update, and Delete from a database) operations on todos table with MySQL.

.ENV File

Before creating migrations, We will need to setup our database, assuming you know how to create a database using phpmyadmin. After creating the database, we will add the database credentials in our application. Laravel has a **.**env environment file which will have all the sensitive data like database details, mail driver details, etc because it’s not recommended to store such information directly inside the code (environment files are not limited to PHP. They are used in all other major frameworks). Values inside the **.**env files are loaded inside the files from the config directory. .env file is located at the root of our application. Let’s take a look at the file (Below is not the complete env file. I have just paste the variables that are important for beginners to know):

#Use double quotes if you have spaces between values e.g APP_NAME="My First Application"
APP_NAME=Laravel #Application name
APP_ENV=local #Application environment, can change to "production"
APP_KEY=your_application_key #Application encryption key
APP_DEBUG=true #Display errors
APP_URL=http://localhost #Application URL
#Supports: MySQL, SQL Server, SQLite, and PostgreSQL.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database_name
DB_USERNAME=your_db_username
DB_PASSWORD=your_db_password

All APP values are loaded in config/app.php config file and DB values in config/database.php.

Note: Whenever you making changes to .env file then don’t forget to restart the server ( if you are using laravel dev server) and if you are using a virtual host and changes don’t seem to take effect then just run php artisan config:clear (This command will clear the configuration cache) in your terminal.

Migrations

Since we have setup our database, now let’s take a look at database migrations. Migration are used to store the details about a database table and it’s properties so you don’t have to manually create all the tables by going to the database interface or something like phpmyadmin. We can create migrations using artisan with “make:migration” command:

php artisan make:migration create_todos_table

In laravel, the name of the model has to be singular and the name of the migration should be plural so it can automatically find the table name. You can find these migration files in database/migrations directory. Laravel by default comes with two migrations namely users and password_resets (all migration files are prepended with a timestamp), which are used for authentication. If you want to create a migration but have a different table name in mind then you can explicitly define the table name with “ — create” flag:

php artisan make:migration create_todos_table --create=todos

When you open the create_todos_table.php. You will see two methods, up() and down(). up() is used for creating/updating tables, columns, and indexes. The down() method is used for reversing the operation done by up() method.

<?php
    
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
    
class CreateTodosTable extends Migration
{
    public function up()
    {
       Schema::create('todos', function (Blueprint $table) {
           $table->bigIncrements('id');
           $table->timestamps();
       });
    }
    
    public function down()
    {
        Schema::dropIfExists('todos');
    }
}

Inside up() method, We have Schema:create(‘table_name’,callback) method which will be used for creating a new table. Inside the callback, we have $table->bigIncrements(‘id’) which will create an auto_increment integer column with a primary key and argument ‘id’ is the name of the column. Second one is $table->timestamps() which will create two timestamp columns created_at and updated_at. created_at will be filled when a row is created and updated_at when a row is updated. We will add two columns title and body (for the sake of simplicity) but if you want to know the available columns types then see the list.

Schema::create('todos', function (Blueprint $table) {
   $table->bigIncrements('id');
   $table->string('title')->unique(); //unique varchar equivalent column
   $table->text('body'); //text equivalent column
   $table->timestamps();
});

You can also specify length to a string column as the second argument:

$table->string('title',255);

Before running any migration commands we need to add a line of code inside the AppServiceProvider.php which can be found in app/Providers directory. Open the file and inside the boot() method, add this line:

\Schema::defaultStringLength(191);

Above code will set the default length of a string column to 191. Since laravel comes with utf8mb4 charset as default which supports emojis and it’s max length for a unique key is 191. If it exceeds the limit then laravel will generate an error and won’t run migrations. If you want to specify a different charset then you can set it in database.php config file.

To run migrations, We can use “migrate” command:

php artisan migrate

Above command will run the migrations and create all the tables. It will execute the up() method. If you want to reverse the migrations, you can use migrate:rollback command which will execute down() method:

php artisan migrate:rollback

Models

After creating the tables, We will create model that will interact with the database resource (table) e.g Post model for posts table. To create a model we simply need to run make:model command:

php artisan make:model Todo

We can also add -m flag which will create a Model as well as a migration for it.

php artisan make:model Todo -m

Above command will also create a migrating named create_todos_table. You can find all the Models inside the app directory. Let’s open the Todo model.

<?php
    
namespace App;
    
use Illuminate\Database\Eloquent\Model;
    
class Todo extends Model
{
    //
}

We can specify properties to modify the behavior of a model. Let’s start with $table property which is used to specify the name of the table that this model will interact with. By default, It will define the table name as the plural of the model name e.g todos table for Todo model and users table for User model. When you don’t want to use timestamps on your table then you will also have to specify $timestamps property and set it to false in your Model because Laravel expects your table to have created_at and updated_at timestamp columns.

class Todo extends Model
{
    protected $table = 'todos';
        
    public $timestamps = false;
}

Above is just an example, so you don’t need to put it in your code to follow this series.

Todos CRUD

Now all the basic stuff is done, let’s start creating views for our todos and add functionality to our TodosController. We will have 4 views Index, Create, Edit, and Show. So let’s create a folder named todos inside the views directory and create all those four views. After that, we will first create the controller:

php artisan make:controller TodosController --resource

Note that we have also added a — resource flag which will define six methods inside the TodosController namely:

  1. Index (used for displaying a list of Todos)
  2. Create (will show a view with form for creating a Todo)
  3. Store (used for creating a Todo inside the database. Note: create method submits to store method)
  4. Show (will display a specified Todo)
  5. Edit (will show a form for editing a Todo. Form will be filled with the existing Todo data)
  6. Update (Used for updating a Todo inside the database. Note: edit submits to update method)
  7. Destroy (used for deleting a specified Todo)

These methods are not specific to TodosController. It can be used for PostsController, CommentsController or any other resource. (A resource is an object e.g user, todo or anything else)

Let’s add the routes to our web.php files:

Route::get('/todos','[email protected]')->name('todos.index');
Route::get('/todos/create','[email protected]')->name('todos.create');
Route::post('/todos','[email protected]')->name('todos.store'); // making a post request
Route::get('/todos/{id}','[email protected]')->name('todos.show');
Route::get('/todos/{id}/edit','[email protected]')->name('todos.edit');
Route::put('/todos/{id}','[email protected]')->name('todos.update'); // making a put request
Route::delete('/todos/{id}','[email protected]')->name('todos.destroy'); // making a delete request

We can pass dynamic parameters with {} brackets and you might have noticed that show, update, and destroy has the same url but different methods so it’s legit. Just like — resource flag, laravel has a method called resource() that will generate all the above routes. You can also use that method instead of specifying them individually like above:

Route::resource('/todos','TodosController');

After defining routes, we need to add the links to our navbar so open the navbar.blade.php view inside the inc folder and update the

    that has the links for the right side of our navbar:

    <!-- Right Side Of Navbar -->
    <ul class="navbar-nav ml-auto">
      <li class="nav-item">
          <a href="{{route('todos.index')}}" class="nav-link">Todos</a>
      </li>
      <li class="nav-item">
          <a href="{{route('todos.create')}}" class="nav-link">New Todo</a>
      </li>
    </ul>
    

    Displaying Todos

    Now open the TodosController so we can modify our Index method:

    public function index()
    {
        $todos = Todo::orderBy('created_at','desc')->paginate(8);
        return view('todos.index',[
            'todos' => $todos,
        ]);
    }
    

    We are retrieving all the todos with Todo model and ordering by created_at column in descending order then we are chaining it with paginate() which will create pagination links and by default the items displayed per page is 15. After retrieving todos, We are returning a view with $todos array. We can pass data to a view by passing an array of data as a second parameter to the view() method.

    Some helpful methods for retreiving models:

    Todo::where('completed',true)->take(8)->get(); // only take 8 todos from the database. <- just an example, we don't have completed column
    Todo::where('title','LIKE',"{%}$search_keyword{%}")->get(); // with LIKE operator.
    Todo::where('title','LIKE',"{%}$search_keyword{%}")->take(8)->get(); //retrieve only 8 results.
    Todo::select(['title','body'])->findOrFail(id); // for specific columns, you can also chain where() with select().
    Todo::where('title','value')->whereOr('body','value')->firstOrFail(); // with SQL OR operator
    Todo::where('title','value')->whereAnd('body','value')->firstOrFail(); // with SQL AND operator.
    

    Now, we can add the markup to our Index view inside the todos folder:

    @extends('layouts.app')
    @section('content')
        <h2 class="text-center">All Todos</h2>
        <ul class="list-group py-3 mb-3">
            @forelse($todos as $todo)
                <li class="list-group-item my-2">
                    <h5>{{$todo->title}}</h5>
                    <p>{{str_limit($todo->body,20)}}</p>
                    <small class="float-right">{{$todo->created_at->diffForHumans()}}</small>
                    <a href="{{route('todos.show',$todo->id)}}">Read More</a>
                </li>
            @empty
                <h4 class="text-center">No Todos Found!</h4>
            @endforelse
        </ul>
        <div class="d-flex justify-content-center">
            {{$todos->links()}}
        </div>
    @endsection
    

    Inside the content section, we are using @forelse() loop to iterate todos and content inside the @empty works as an else statement if an array or collection is empty. Blade has @for() , @foreach(), and @while() loops too. Of course, you need to end this @forelse with @endforelse(). Inside @forelse(), We have some HTML markup and you can see that we are echoing $todo object properties. str_limit() is a helper function used to limit the number of string characters and diffForHumans() is used for displaying date in a more readable format e.g 1 day ago or 1 months ago. We are displaying the pagination links with $todos->links(). By default, Laravel supports bootstrap pagination but if you are using a different framework like materialize-css then you can specify custom pagination view inside the links() method. Laravel already comes with some default pagination views. You can publish these pagination views with below command:

    php artisan vendor:publish --tag=laravel-pagination
    

    Above command will copy the pagination views to our views/vendor/pagination directory. Then you can specify the view like this:

    {{$todos->links('vendor.pagination.bootstrap-4')}} // bootstrap-4 is the name of the pagination view
    

    There will be more than one pagination views. You can customize the view however you like. Now, when you navigate to todos page, you will see “Nothing found!” since there are not todos in the database.(we will create them now)

    Creating A Todo

    Let’s modify the create method of TodosController which will simply return a view with a form for creating a todo:

    public function create()
    {
        return view('todos.create');
    }
    

    Adding the markup to create view inside the todos folder:

    @extends('layouts.app')
    @section('content')
        <h3 class="text-center">Create Todo</h3>
        <form action="{{route('todos.store')}}" method="post">
            @csrf
            <div class="form-group">
                <label for="title">Todo Title</label>
                <input type="text" name="title" id="title" class="form-control {{$errors->has('title') ? 'is-invalid' : '' }}" value="{{old('title')}}" placeholder="Enter Title">
                @if($errors->has('title'))
                    <span class="invalid-feedback">
                        {{$errors->first('title')}}
                    </span>
                @endif
            </div>
            <div class="form-group">
                <label for="body">Todo Description</label>
                <textarea name="body" id="body" rows="4" class="form-control {{$errors->has('body') ? 'is-invalid' : ''}}" placeholder="Enter Todo Description">{{old('body')}}</textarea>
                @if($errors->has('body')) {{-- <-check if we have a validation error --}}
                    <span class="invalid-feedback">
                        {{$errors->first('body')}} {{-- <- Display the First validation error --}}
                    </span>
                @endif
            </div>
            <button type="submit" class="btn btn-primary">Create</button>
        </form>
    @endsection
    

    We have a form with POST method submitting to a name route “todos.store”. Inside the form, we have @csrf will generate a hidden input named csrf_token which is important for security and without that you will not be able to submit a form (You will see a page with “Page expired” text after form submission or a 419 error page). old(‘field_name’) is a helper function used for echoing the old values from a submitted form e.g if you failed the validation and got redirected back to the form then you will need those values that were previously filled. Validation errors can be accessed by $errors object. To check for validation failure, We can use $errors->has(‘field_name’) and $errors->first(‘field_name’) will echo the first validation error.

    “field_name” is the value of the “name” attribute defined in our input fields. In our form, we have “title” and “body” fields.

    Let’s modify the Store method of our TodosController which will store the todo to the database:

    public function store(Request $request)
    {
        //validation rules
        $rules = [
            'title' => 'required|string|unique:todos,title|min:2|max:191',
            'body'  => 'required|string|min:5|max:1000',
        ];
        //custom validation error messages
        $messages = [
            'title.unique' => 'Todo title should be unique', //syntax: field_name.rule
        ];
        //First Validate the form data
        $request->validate($rules,$messages);
        //Create a Todo
        $todo        = new Todo;
        $todo->title = $request->title;
        $todo->body  = $request->body;
        $todo->user_id = Auth::id();
        $todo->save(); // save it to the database.
        //Redirect to a specified route with flash message.
        return redirect()
            ->route('todos.index')
            ->with('status','Created a new Todo!');
    }
    

    Store method has $request object as a parameter which will be used to access form data. First thing you want to do is validate the form data. We can use the $request->validate() method for validation, which will receive an array of validation rules. Validation rules is an associative array. Key will be the field_name and value with be the validation rules. Second parameter is an optional array for custom validation messages. Rules are separated with pipe sign “|”. We are using the most basic validation rules. First is “required” which means the field_name should not be empty (“nullable” rule is vice versa), “string” means it should be a string value, “min” is the limit of minimum characters for a string in an input field and “max” is the maximum characters. “unique:table,column” with see if the same value does not exists in the database (comes handy for storing emails or any other unique data). If validation fails then it will redirect us back. After validation, we are create a new object of the Todo model. Since Todo and Request are both objects, We can access their properties with arrow “->” operator. After specifying the values to the model properties, We can call the save() method which will save the Todo to the database. When a Todo is successfully created then we are redirecting the user to a specific route and chaining it with with() method which will make the data available for the next request so we can use it as a flash message. Since this will be stored in session, We can access the value with session(‘status’) as well as check if the a value exists.

    Some Methods for accessing form values that are useful:

    $request->input('field_name'); // access an input field
    $request->has('field_name'); // check if field exists
    $request->title; // dynamically access input fields
    request('key') // you can use this global helper if needed inside a view
    

    Helpful redirecting methods:

    return redirect('/todos'); // to a specific url
    return redirect(url('/todos')); // to a specific url with url helper
    return redirect(url()->previous()); // to a previous url
    return redirect()->back(); // redirect back (same as above)
    

    We also need to display the message as bootstrap alert so open the app.blade.php layout file (so we can make this alert available in every view) and add the below code right after the app.js script:

    @if(session('status')) {{-- <- If session key exists --}}
        <div class="alert alert-success alert-dismissible fade show" role="alert">
            {{session('status')}} {{-- <- Display the session value --}}
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
    @endif
    <script>
        //close the alert after 3 seconds.
        $(document).ready(function(){
           setTimeout(function() {
              $(".alert").alert('close');
           }, 3000);
        });
    </script>
    

    Let’s add a little css in the tag for our alert:

    <style>
    .alert{
        z-index: 99;
        top: 60px;
        right:18px;
        min-width:30%;
        position: fixed;
        animation: slide 0.5s forwards;
    }
    @keyframes slide {
        100% { top: 30px; }
    }
    @media screen and (max-width: 668px) {
        .alert{ /* center the alert on small screens */
            left: 10px;
            right: 10px; 
        }
    }
    </style>
    

    Displaying a Single Todo

    Let’s open the show.blade.php and add the following markup:

    @extends('layouts.app')
    @section('content')
        <h3 class="text-center">{{$todo->title}}</h3>
        <p>{{$todo->body}}</p>
        <br>
        <a href="{{route('todos.edit',$todo->id)}}" class="btn btn-primary float-left">Update</a>
        <a href="#" class="btn btn-danger float-right" data-toggle="modal" data-target="#delete-modal">Delete</a>
        <div class="clearfix"></div>
        <div class="modal fade" id="delete-modal">
            <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Delete Todo</h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <p>Are you sure!</p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-danger" onclick="document.querySelector('#delete-form').submit()">Proceed</button>
                    <button type="button" class="btn btn-primary" data-dismiss="modal">Cancel</button>
                </div>
            </div>
            </div>
        </div>
        <form method="POST" id="delete-form" action="{{route('todos.destroy',$todo->id)}}">
            @csrf
            @method('DELETE')
        </form>
    @endsection
    

    All that’s different in our view is that this time we have a single Todo and since it’s a single object we don’t need a loop. There’s an Update button that with open the edit todo view and there’s also a Delete button that will open the modal for confirmation and when you will click the Proceed button it will submit a hidden form having an id of “delete-form” (we will work on destroy method after show method). We are making a delete request when deleting a specific resource so simple anchor tag will not work and a form is needed. Since HTML forms only support GET and POST methods, We will need to add @method(‘DELETE’) which will generate an input field with a value of DELETE ( PUT for updating a resource). Let’s modify the show method in TodosController.

    public function show($id)
    {
        $todo = Todo::findOrFail($id);
        return view('todos.show',[
            'todo' => $todo,
        ]);
    }
    

    To get a specific resource with an id column, We can use findOrFail() method this method will throw a 404 error if it fails to find the resource. If you want to find a resource by a different column then you can use where(‘column’,’value’) and chain it to firstOrFail() method. For example:

    $todo = Todo::where('title','this is title')->firstOrFail();
    

    Delete a Todo

    We have already created a button in the show view that submits to this method. Now we can modify the destroy method that is responsible for deleting the Todo and all we need to do in this method is fetch the specified Todo and call delete() method on it.

    public function destroy($id)
    {
        //Delete the Todo
        $todo = Todo::findOrFail($id);
        $todo->delete();
        //Redirect to a specified route with flash message.
        return redirect()
            ->route('todos.index')
            ->with('status','Deleted the selected Todo!');
    }
    

    Updating the Todo

    Edit method is same as show method just views are different because we will display the values of our todo object in an html form:

    public function edit($id)
    {
        //Find a Todo by it's ID
        $todo = Todo::findOrFail($id);
        return view('todos.edit',[
            'todo' => $todo,
        ]);
    }
    

    Let’s add the markup for our last view edit.blade.php:

    @extends('layouts.app')
    @section('content')
        <h3 class="text-center">Edit Todo</h3>
        <form action="{{route('todos.update',$todo->id)}}" method="post">
            @csrf
            @method('PUT')
            <div class="form-group">
                <label for="title">Todo Title</label>
                <input type="text" name="title" id="title" class="form-control {{ $errors->has('title') ? 'is-invalid' : '' }}" value="{{ old('title') ? : $todo->title }}" placeholder="Enter Title">
                @if($errors->has('title')) {{-- <-check if we have a validation error --}}
                    <span class="invalid-feedback">
                        {{$errors->first('title')}} {{-- <- Display the First validation error --}}
                    </span>
                @endif
            </div>
            <div class="form-group">
                <label for="body">Todo Description</label>
                <textarea name="body" id="body" rows="4" class="form-control {{ $errors->has('body') ? 'is-invalid' : '' }}" placeholder="Enter Todo Description">{{ old('body') ? : $todo->body }}</textarea>
                @if($errors->has('body')) {{-- <-check if we have a validation error --}}
                    <span class="invalid-feedback">
                        {{$errors->first('body')}} {{-- <- Display the First validation error --}}
                    </span>
                @endif
            </div>
            <button type="submit" class="btn btn-primary">Update</button>
        </form>
    @endsection
    

    This view is the same as the create.blade.php, the difference is that it’s submitting to update method of TodosController having a @method(‘PUT’) inside the form. We are also echoing the values from the $post in the input field with a ternary operator and if validation fails then we will have old() value instead of $post values. Lastly the update method of our TodosController which is same as store but we are updating an existing resource:

    public function update(Request $request, $id)
    {
        //validation rules
        $rules = [
            'title' => "required|string|unique:todos,title,{$id}|min:2|max:191", //Using double quotes
            'body'  => 'required|string|min:5|max:1000',
        ];
        //custom validation error messages
        $messages = [
            'title.unique' => 'Todo title should be unique',
        ];
        //First Validate the form data
        $request->validate($rules,$messages);
        //Update the Todo
        $todo        = Todo::findOrFail($id);
        $todo->title = $request->title;
        $todo->body  = $request->body;
        $todo->save(); //Can be used for both creating and updating
        //Redirect to a specified route with flash message.
        return redirect()
            ->route('todos.show',$id)
            ->with('status','Updated the selected Todo!');
    }
    

    There is a scenario when you want to update the todo body but not title then you will get a validation error saying the title should be unique. In these cases we can specify the id of that model to ignore.

    Now we can create, display all todos, display a single todo, update them, and lastly delete them, Next tutorial will be on authentication and middleware.

    Next tutorial will soon be published within this week.

    Source code till this tutorial HERE | complete project HERE

    Learn More

    PHP with Laravel for beginners - Become a Master in Laravel
    Projects in Laravel: Learn Laravel Building 10 Projects
    Laravel for RESTful: Build Your RESTful API with Laravel
    Fullstack Web Development With Laravel and Vue.js
    Laravel 5.8 Ajax CRUD tutorial using Datatable JS
    Laravel 5.8 Tutorial for Beginners
    Build a CMS with Laravel and Vue
    Build a Basic CRUD App with Laravel and Vue
    Build a Basic CRUD App with Laravel and Angular
    Build a Basic CRUD App with Laravel and React

    Originally published by Sagar Maheshwary at https://medium.com

Suggest