Advertisement
  1. Code
  2. PHP
  3. Laravel

Building Web Applications From Scratch With Laravel

Scroll to top

In this series, we'll build a web application from scratch with Laravel—a simple and elegant PHP web framework.

First up, we'll learn more about Laravel and why it's such a great choice for your next PHP-based web application.

What Is Laravel?

Laravel Website

Laravel is a clean and classy framework for PHP web development. Freeing you from spaghetti code, it helps you create wonderful applications, using simple, expressive syntax. Development should be a creative experience that you enjoy, not something that is painful. Enjoy the fresh air!

Laravel is a web application framework that describes itself as “A Framework For Web Artisans”. According to its author, Taylor Otwell, Laravel strives to bring back the joy to programming by making Laravel simple, elegant, and, most importantly, well-documented.

From my experience with the framework, I would definitely agree that Laravel hits these three points dead-on:

  • Simple—Laravel's functionalities are easy to understand and implement.
  • Elegant—most of Laravel's functions work seamlessly with very little configuration, relying on industry-standard conventions to lessen code bloat.
  • Well-documented—Laravel's documentation is complete and always up-to-date. The framework's creator makes a point of updating the documentation before releasing a new version, ensuring that people who are learning the framework always have the latest documentation.

What Makes Laravel Different?

As with any PHP framework, Laravel boasts a multitude of functions that differentiate it from the rest of the pack. Here are some, which I feel are the most important (based on the Laravel documentation).

Packages

Packages are to Laravel as PEAR is to PHP; they are add-on code that you can download and plug into your Laravel installation. Laravel comes with a command-line tool called Artisan, which makes it incredibly easy to install bundles.

We all know the importance of web security in this modern digital era. One of my favorite Laravel Packages, called Spatie, adds a useful tool to Laravel that lets you define roles and permissions in your application. In essence, it lets you specify which users have access to what resources.

Other very useful Laravel packages include Laravel Mix (a webpack front-end build tool), Eloquent-Sluggable (for making slugs), and Laravel Debugbar (for debugging).

Eloquent ORM

The Eloquent ORM is the most advanced PHP ActiveRecord implementation available.

The Eloquent ORM is, by far, one of the best ORM implementations I've used. Similar to how Doctrine ORM functions, it makes any work on database records simple and easy. It abstracts most functions that you'll have on models (i.e. CRUD operations) and provides a flexible way to add more. Additionally, the Eloquent ORM gives you the ability to define model relationships to retrieve records, based on their relationship to another record. For example, you can retrieve all related file records associated with a user by doing:

1
foreach( $user->Files as $file ) {
2
    echo $file->name;
3
}

Migrations

Database migrations are a great utility in any project's arsenal—especially for projects where multiple developers are involved—by making it easy to keep your database schema up-to-date with other team members' changes. In Laravel, migrations are built into the framework; they can be executed via the Artisan command-line utility. Laravel's own Schema Builder functions are simple enough that anybody should be able to quickly write up a database schema change.

Here's an example that creates a users table in a database, taken from the Laravel documentation:

1
Schema::table('users', function($table)
2
{
3
    $table->create();
4
    $table->increments('id');
5
    $table->string('username');
6
    $table->string('email');
7
    $table->string('phone')->nullable();
8
    $table->text('about');
9
    $table->timestamps();
10
});

Migrations are defined inside a migration PHP file. These files go inside the app/database/migration folder in a Laravel project.

Typically, a migration file follows the naming convention: 2022_05_23_000000_create_users_table.php, where 2022_05_23 is the date and create_users_table is the type of migration.

Running the migration above will create a users table consisting of the specified columns inside your chosen database. Each column is assigned a type using the type methods (see a complete list in the official docs). To create a database migration, use the artisan utility as follows:

1
php artisan migrate

You can also perform more granular tasks on your table like adding or removing a column, reordering the columns, seeding data, and so on.

Here's an example that adds the location column to the existing users table:

1
Schema::table('users', function (Blueprint $table) {
2
    // Add location after the id column in the table

3
    $table->string('location')->after('id');
4
});

The file for this migration would follow almost the same naming convention discussed earlier. The only difference would be in the migration name, which would go like: add_location_column_to_users_table.

Seeding

When developing an application, it's important that you test the functionality to see if the app is working as intended. Seeding allows you to populate your tables with fake data en masse, for testing purposes. 

All seeders inside a project go in the app/database/seeders directory. To generate a seeder, use the artisan utility as follows:

1
php artisan make:seeder UserSeeder

Here's a basic example that populates the users table with some autogenerated data, from the docs:

1
DB::table('users')->insert([
2
    'name' => Str::random(10),
3
    'email' => Str::random(10).'@gmail.com',
4
    'password' => Hash::make('password'),
5
]);

You can use model factories to generate large amounts of database records at a time. This basic example creates 50 posts for a user inside this database:

1
Post::factory()
2
            ->count(50)
3
            ->belongsToUser(1)
4
            ->create();

Unit-Testing

As a believer in Test-Driven Development (read for more info: The Newbie's Guide to Test-Driven Development), I love it when a framework has some sort of unit-testing utility baked in. Laravel's own beautifully integrates with PHPUnit, relying on its status as one of the industry's best PHP unit testing frameworks. To build a test, simply extend the TestCase class, like so:

1
class MyUnitTest extends TestCase
2
{
3
    public function somethingShouldBeTrue()
4
    {
5
        $this->assertTrue(true);
6
    }
7
}

To run your Laravel application's tests, let's, again, use the artisan command-line utility:

1
php artisan test

That command will run all the tests that are found within the app/tests/unit directory of your Laravel application.


Let's Build a To-Do Application With Laravel

Now that we know more about Laravel, it's time to get hands-on by building a web application with it from scratch. We'll build a to-do application that allows us to post items to the database. I'll also include the functionality to mark them once completed. When done, it will look like this:

To-do ApplicationTo-do ApplicationTo-do Application
To-do Application

In the process, we'll learn about the different concepts and features of Laravel like routing and controllers, as well as MySQL and, of course, the Blade templating language.

With that in mind, let's dive in!


Installing Laravel 

There are different ways to install Laravel on your machine, and your choice depends on your operating system. Laravel provides detailed documentation on that.
Here we'll be focusing on installing it via Composer. If your local machine doesn't have PHP and Composer, here's a well-put-together guide to help you with downloading and setting up both requirements.
Once you have installed PHP and Composer, creating a Laravel application becomes very straightforward. Open your terminal and run the following command to create a brand new app inside a folder named my-app (use any name you wish):
1
composer create-project laravel/laravel my-app

After the application has been created, cd into my-app and start your development server:

1
cd my-app
2
 
3
php artisan serve

Now we're done setting up the environment. After some time, you should be able to access your Laravel development server at http://localhost:8000.

Setting Up the Application

At this point, Laravel has scaffolded a working framework for you. You'll find a host of folders and files inside your root project folder. We won't go over all of these folders—instead, we'll focus on the most important ones.

First, you have the app folder. This folder contains the Models and Controllers, amongst others. Next you have the database folder, which contains the migrations, factories, and seeders. resources is where all the front-end code will go, and specifically in here we find the view folder, where we'll create the view of our application using Blade templates. Finally, routes will contain all of our application routes. Inside routes/web.php, we'll define the web routes of our application.

Now that we have a basic understanding of the framework, let's create and serve our first page. By default, Laravel sets up a starter template at resources/views/welcome.blade.php, which is what we see at http://localhost:8000. This is a Blade template, not HTML. Whenever we modify that file, the changes are reflected in the browser. You can modify yours and see the changes in effect.

First, we'll bring Bootstrap CSS into our project by embedding its CDN link in the <head> tag of our welcome.blade.php file:

1
<head>
2
    <meta charset="utf-8">
3
    <meta name="viewport" content="width=device-width, initial-scale=1">
4
5
    <title>Laravel</title>    
6
7
    <!-- Bootstrap CDN -->
8
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">            
9
</head>

Then we'll create a basic Bootstrap CSS form for our to-do layout:

1
<form method="post" action="">
2
    <div class="row g-3 align-items-center">
3
        <div class="col-auto">
4
            <label for="inputPassword6" class="col-form-label">Add New Item:</label>
5
        </div>
6
        <div class="col-auto">
7
            <input type="text" name="name" id="name" class="form-control" aria-describedby="passwordHelpInline">
8
        </div>
9
        <div class="col-auto">
10
            <button type="submit" class="btn btn-secondary">Save item</button>
11
        </div>
12
    </div>
13
</form>

Hooking Up the Database (MySQL)

MySQL is a popular relational database, similar to Microsoft SQL Server and Oracle. It's used by many web applications to create relational datastores where the records relate to one another.

You can get MySQL via popular distributions like WAMPServer and XAMPP. Create a new database in your local MySQL installation and name it todo. In the following section, we'll create and migrate a table comprising some columns to this database.

Once you have your MySQL server set up, simply open the .env file in your project root folder and pass your database name to DB_DATABASE, like so:

1
DB_DATABASE=todo

Creating a Table and Adding Columns (Models and Migrations)

Earlier we learnt that migrations are simply a way to execute changes to our database tables. They allow us to create tables and add or remove columns right from our application. But in relation to databases, there is another important concept we must learn about, and that is Models.

Models allow you to retrieve, insert, and update information in your data table. Typically, each of the tables in a database should have a corresponding “Model” that allows us to interact with that table.

Now we'll create our first model, which is ListItem. To do so, we run the following artisan command:

1
php artisan make:model ListItem -m

The -m flag creates a migration file along with the model, and this migration file will go inside the app/database/migrations folder. 

Next, we'll modify the migration file and add the columns we want in our list_items table:

1
<?php
2
3
use Illuminate\Database\Migrations\Migration;
4
use Illuminate\Database\Schema\Blueprint;
5
use Illuminate\Support\Facades\Schema;
6
7
class CreateListItemsTable extends Migration
8
{
9
    /**
10
     * Run the migrations.
11
     *
12
     * @return void
13
     */
14
    public function up()
15
    {
16
        Schema::create('list_items', function (Blueprint $table) {
17
            $table->id();
18
            $table->string('name');
19
            $table->integer('is_complete');
20
            $table->timestamps();
21
        });
22
    }
23
24
    /**
25
     * Reverse the migrations.
26
     *
27
     * @return void
28
     */
29
    public function down()
30
    {
31
        Schema::dropIfExists('list_items');
32
    }
33
}

Here we are adding the fields id, name, and is_complete. table->timestamps() will automatically generate two fields in our database: created_at and updated_at.

Run the following to migrate the table and its columns to your database:

1
php artisan migrate

Setting Up Routes

In Laravel, all requests to the application are mapped to specific functions or controllers by Routes. They are responsible for instructing the application where URLs go. For example, if we wanted http://localhost:8000/home to render the home view file, we could create the following route within web.php, found inside the routes folder:

1
Route::get('/home', function()
2
{
3
    return view('home');
4
})

Alternatively, if we instead needed to route http://localhost:8000/home to a Controller, say, the HomeController.php controller, we might do something like this:

1
Route::controller('HomeController');

This would route to the HomeController.php controller file. Any action method there will be made available as well. To take things further, you can specify a particular method to handle that route:

1
Route.get([HomeController::class, 'index'])

Your HomeController.php file will basically look like this:

1
<?php
2
3
namespace App\Http\Controllers;
4
5
class HomeController extends Controller
6
{   
7
    public function index()
8
    {
9
        
10
    }    
11
}

Don't worry, we'll learn more about controllers soon. Basically, what we did is specify the index method inside the HomeController class to be called when a user navigates to localhost:8000/home.

In addition, a route name, home.index, is assigned to the route. This is especially useful if you're using a templating language like Blade (as we'll see soon). Sometimes, two routes may share the same path (e.g. /thread), but one is GET and the other is POST. In such cases, you can distinguish both by using different route names, preventing mix-ups.

An important thing to note here is that by default, Laravel does not route to the controllers as other PHP frameworks do. This is by design. By doing so, we can actually create simple pages without the need to create a controller for it. For example, if we wanted to create a static Contact Us page that just lists contact information, we can simply do something like this:

1
Route::any('contact-us', function()
2
{
3
    return view('contact-us');
4
})

This will route http://localhost:8000/contact-us and render the resources/views/contact-us.blade.php file. Since we don't really have any dynamic processing on this page, we can just automatically render the view file, saving us the time to create and configure a controller to do so.

There is so much more that we can do with Routes in Laravel. Stuff like:

  • HTTP Verbs—Laravel gives us the ability to create routes based on the HTTP verb that was used in the request. For example, we can have a GET request to the /home route go somewhere different from where the POST request would go.
  • Wildcards—this lets us route a URL with a wildcard value attached to it (e.g. /user/(:num) where (:num) is the user's ID)
  • Middleware—this lets us run some functionality before or after a route is executed, depending on the route that was called. For example, we can create auth middleware that will be called before all routes, except the home and about routes. 

For the purposes of this web application, though, we only need two routes: the first route just shows the welcome page along with the to-do list, and the second route will be called when we submit a new to-do item using the form.

Open up the web.php file and add the following routes:

1
Route::get('/', [TodoListController::class, 'index']);
2
Route::post('/saveItem', [TodoListController::class, 'saveItem'])->name('saveItem');

For the second, we set the method to post to indicate that we are handling a POST request. When this route is triggered, we want to call the saveItem method inside the TodoListController (we'll create it soon). A route name is also set. We'll use this name when making a POST request from the form.

Next, we import the controller on top of the file:

1
use App\Http\Controllers\TodoListController;

Time to learn about controllers and create our very own TodoListController. 

Creating Controllers

Controllers
Controllers Anyone?
 

Typically, you define all of the logic for handling a route inside a controller. Controllers in Laravel are found inside the app/Http/Controllers folder. 

To create a controller, we use the artisan utility. Remember that TodoListController whose method I said was going to handle to POST request to our saveItem route? It's time to create it.

Run the following command on your terminal:

1
php artisan make:controller TodoListController

This will create a TodoListController.php file inside the app/Http/Controllers folder. By convention, we'll want to name the file something descriptive that will also be the name of the controller class, which is why I went with that name. 

In this controller class, we'll define two methods: index and saveItem.

1
<?php
2
3
namespace App\Http\Controllers;
4
5
use Illuminate\Http\Request;
6
use App\Models\ListItem
7
8
class TodoListController extends Controller
9
{
10
    public function index() 
11
    {
12
        return view('welcome', ['listItems' => ListItem::all()]);   
13
    }
14
    
15
    public function saveItem(Request $request) 
16
    {
17
        $item = new ListItem;
18
        $item->name = request->name;
19
        $item->is_complete = 0;
20
        $item->save();
21
22
        return view('welcome');
23
    }
24
}

First, we import two Models: Request and ListItem. Request gives us all the information about the HTTP request. ListItem is the model we created earlier for saving a to-do list item in our database.

The index method returns the welcome view and passes all the list items from the database. That way, we can display them on the page if there is any.

The saveItem method is for saving a to-do item to our database. Here we create a new ListItem instance. We then set its name to the name from the request payload, and set is_complete to 0 (represents false or "no"). Finally, we return the welcome page.

For this to work, all we have to do now is modify our welcome.blade.php file, which is where we'll be making a request from and showing the to-do list. 

For now, let's learn more about controllers.  

More Controller Fun

Middleware

There's a lot more that we can do with controllers, rather than them just being gateways to the view files. For example, remember the Middleware feature that I mentioned earlier in the routes section? Aside from being attached to specific Routes, we can also attach them to specific controllers! Simply create a __constructor method for the controller, and set up the middleware there. For example, if we need to ensure that a user is authenticated for all the methods in a controller, we can make use of our example auth middleware:

1
public function __construct() {
2
    $this->middleware('auth');
3
}

This will call the auth middleware on all actions in this controller. If we want to target some specific actions, we can refer to the only method, like so:

1
public function __construct() {
2
    $this->middleware('auth')->only('index');
3
    
4
    // Or for only index and store actions

5
    $this->middleware('auth')->only(array('index, 'store'));
6
}

We can alternatively use the except method to implement the middleware on all actions, except a few:

1
public function __construct() {
2
    $this->middleware('auth')->except('store');
3
}

Notice how expressive this code is?

We can even target a specific HTTP verb:

1
public function __construct() {    
2
    $this->middleware('auth')->except('store')->on('post');
3
}

The Base Controller

Most, if not all, controllers extend the Controller. This gives us a way to define methods that will be the same for all our controllers. For example, if we need to create a logging method to log any controller request:

1
class Controller extends BaseController {
2
    
3
    public function __call($method, $parameters)
4
    {
5
        return Response::error('404');
6
    }
7
    
8
    public function logRequest()
9
    {
10
        $route = Request::route();
11
        Log::log('request', "Controller: {$route->controller} / Action: {$route->controller_action} called at ". date('Y-m-d H:i:s'));
12
    }
13
}

Any time we want to log a request, we can just call $this->logRequest(); in any controller. If you want to learn more, this section of Laravel's documentation provides detailed information about controllers.

Now let's hook up our view to our already-implemented route and controller.


Creating the Laravel View With the Blade Templating Engine

Laravel supports two ways to generate views for an application:

  • PHP-based views—these are views that make use of PHP as a templating language.
  • Blade-based views—these are views that make use of Laravel's built-in templating engine, called Blade.

The Blade Templating Engine is a templating framework that, similar to how the Smarty Templating Engine works, makes use of customized tags and functions to allow for better separation of presentation logic and application code.

For the sake of simplicity, we'll be utilizing the Blade Templating Engine, as opposed to the PHP-based views.

All view files that will make use of the Blade Templating Engine need to have a .blade.php extension. This tells Laravel to use the engine on the view file.

Now, back in our welcome.blade.php file, we do two things:

  1. We use forelse to loop through our to-do list and show them. If there is no item (i.e. we haven't saved any item yet), we instead show No Items Saved Yet.
  2. We set the route to POST to. We'll make our form work by adding a CSRF token to it. This is a security measure you take whenever you're posting data with forms.

Replace the contents of div.container with the following markup:

1
@forelse ($listItems as $listItem)
2
<div class="alert alert-primary" role="alert">
3
    <span>Item: {{ $listItem->name }}</span> 
4
5
    <form method="post" action="{{ route('markAsComplete', $listItem->id) }}">
6
        {{ csrf_field() }}
7
        <button 
8
            type="submit" 
9
            class="btn {{ $listItem->is_complete ? 'btn-success' : 'btn-danger' }}"
10
        >
11
            {{ $listItem->is_complete ? 'Completed' : 'Mark as Complete' }}
12
        </button>
13
    </form>                    
14
</div>                
15
@empty
16
<div class="alert alert-danger" role="alert">
17
    No Items Saved Yet
18
</div>                  
19
@endforelse
20
            
21
<form method="post" action="{{ route('saveItem') }}">
22
    {{ csrf_field() }}
23
    <div class="row g-3 align-items-center">
24
        <div class="col-auto">
25
            <label for="inputPassword6" class="col-form-label">Add New Item:</label>
26
        </div>
27
        <div class="col-auto">
28
            <input type="text" name="name" id="name" class="form-control" aria-describedby="passwordHelpInline">
29
        </div>
30
        <div class="col-auto">
31
            <button type="submit" class="btn btn-secondary">Save item</button>
32
        </div>
33
    </div>
34
</form>

Notice how we used the is_complete value of each list item to decide the color of the button and text content. We'll come to how we change it very soon.

Also, we used the route() helper function provided by Blade to define a form submit action; we simply route to saveItem (that we created earlier) when this form is submitted.

To test this out, navigate your browser to localhost:3000 to see the welcome page. Now type a to-do item in the text input and submit. The new item will be shown on the page. 

List ItemsList ItemsList Items
List Items

Also, go to your MySQL database and check the list_items table to confirm. While you're there, you'll notice that all the items have their is_complete column set to 0. One last thing we'll do is add the functionality for marking a to-do list as Completed.

If you recall, we already added the button for marking a to-do item as completed in the view. Now we need to define the route. Go to your web.php and add the following:

1
Route::post('/markAsComplete/{id}', [TodoListController::class, 'markItem'])->name('markAsComplete');

Because we need to know which list item is being marked, we pass its id to the markItem method in the controller since all items had an id column and we passed the id to route() inside the view.

Next, we define the method in the controller:

1
public function markItem($id) 
2
{
3
    $item = ListItem::find($id);
4
    $item->is_complete = 1;
5
    $item->save();
6
7
    return redirect('/');
8
}

Now refresh your page at http://localhost:8000 and mark any item. The button colour and text content will change.

List ItemsList ItemsList Items
List Items

Now that we have our main layout, let's see how to include other sections inside it.

@section and @yield

Sections let us inject content into the main layout from within a view. To define which part of our main layout is a section, we surround it with @section and @yield_section Blade tags.

Supposing we want to create a navigation section and inject it into our main layout, welcome.blade.php. We create a new blade file and name it nav.blade.php in. In this file, we create a section called Navigation and define the markup for this section:

1
@section('navigation')
2
<li><a href="about">About</a></li>
3
<li><a href="policy">Policy</a></li>
4
<li><a href="app">Mobile App</a></li>
5
@endsection

At this point, the navigation simply exists. To actually inject it inside the main layout, we use @yield.

@yield

The @yield function is used to bring a section into a layout file. To yield a section, we need to pass the correct path to that file inside the @yield function. To define the path, the starting point is the views folder.

For example, assuming that we created nav.blade.php inside the views folder, this is how we bring it into welcome.blade.php:

1
<div class="container">      
2
    <div class="navigation">
3
        @yield('nav')
4
    </div>
5
    
6
    <!-- Other Markups -->
7
</div>

Importing Assets With @section and @yield

With @section and @yield, you can also load CSS and JS files to a page. This makes it possible to organize your application's layout in the best possible manner. 

To demonstrate, I'll create a third blade file and name it styles.blade.php. Inside this file, I'll bring in the stylesheet used in my app:

1
@section('navigation')
2
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
3
4
<style>
5
    form {
6
        display: flex;
7
        align-items: center;
8
        justify-content: center;
9
    }
10
11
    .container {
12
        margin-top: 8rem;
13
    }
14
15
    .alert {
16
        text-align: center;
17
        display: flex;
18
        align-items: center;
19
        justify-content: space-between;
20
    }
21
</style> 
22
@endsection

Instead of writing the styles directly in welcome.blade.php, I can simply do this instead:

1
<head>            
2
    <!-- Styles -->        
3
    @yield('styles')   
4
</head>

There is so much more to learn about Blade. If you're interested in exploring further, be sure to check out the documentation.


Conclusion

After reading this tutorial, you've learned:

  • What Laravel is and how it's different from other PHP frameworks.
  • Where to download Laravel and how to set it up.
  • How Laravel's Routing system works.
  • How to create your first Laravel Controller.
  • How to create your first Laravel View.
  • How to use Laravel's Blade Templating Engine.

Laravel is truly an amazing framework. It's fast, simple, elegant, and so easy to use. It absolutely merits being considered as the framework to use for your next project.

The Laravel category on Envato Market is growing fast, too. There's a wide selection of useful scripts to help you with your projects, whether you want to build an image-sharing community, add avatar functionality, or much more.

Laravel scripts on Envato Market
Laravel scripts on Envato Market
Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.