Weekend Project: Develop a Laravel Web Application with Database and API

Bringing it all together with George Polya's problem-solving methodology

Introduction

Welcome to our weekend project session! Today, we'll synthesize everything we've learned this week about Laravel to build a complete web application with database integration and a RESTful API. But we won't just dive in coding—we'll use a systematic approach.

George Polya, a renowned mathematician, developed a 4-step problem-solving procedure that's incredibly valuable for developers:

flowchart TD A[1. Understand the Problem] --> B[2. Devise a Plan] B --> C[3. Execute the Plan] C --> D[4. Review/Reflect] D -.-> A style A fill:#f9f,stroke:#333,stroke-width:2px

This approach helps ensure we don't miss important steps and creates a thoughtful process rather than immediately jumping into code. Let's apply this to our weekend project.

Project Overview: TaskMaster API

We'll build TaskMaster, a task management system with both web and API interfaces. Users can create projects, add tasks to projects, assign tasks to team members, and track completion. Our application will feature:

Step 1: Understand the Problem

Following Polya's first step, let's fully understand what we need to build:

Defining Requirements

Entity Relationships

erDiagram USERS ||--o{ PROJECTS : creates USERS ||--o{ TASKS : assigned PROJECTS ||--o{ TASKS : contains TASKS }|--|| STATUSES : has

Key Questions

Analogy: Think of this like planning a construction project. Before breaking ground, architects and builders need detailed plans showing everything from the foundation to the roof, electrical systems, and plumbing. Our database schema and API specs are our blueprints.

Step 2: Devise a Plan

Project Structure

  1. Initialize Laravel project and set up development environment
  2. Design and create database migrations, models, and relationships
  3. Implement authentication for both web and API
  4. Create web controllers and views for core functionality
  5. Develop API controllers and resources
  6. Implement validation and error handling
  7. Add security and permissions
  8. Test the application

Database Schema Design

// Users Table
Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->enum('role', ['admin', 'user'])->default('user');
    $table->rememberToken();
    $table->timestamps();
});

// Projects Table
Schema::create('projects', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->text('description')->nullable();
    $table->foreignId('user_id')->constrained(); // Creator of project
    $table->timestamps();
});

// Tasks Table
Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description')->nullable();
    $table->foreignId('project_id')->constrained();
    $table->foreignId('assigned_to')->nullable()->constrained('users');
    $table->enum('status', ['todo', 'in_progress', 'completed'])->default('todo');
    $table->date('due_date')->nullable();
    $table->timestamps();
});

API Endpoints Plan

// Authentication
POST /api/login         - Login and get token
POST /api/register      - Register a new user
POST /api/logout        - Logout (revoke token)

// Projects
GET    /api/projects            - List all projects
POST   /api/projects            - Create a project
GET    /api/projects/{id}       - Get single project
PUT    /api/projects/{id}       - Update a project
DELETE /api/projects/{id}       - Delete a project

// Tasks
GET    /api/projects/{id}/tasks - List all tasks in project
POST   /api/projects/{id}/tasks - Create a task in project
GET    /api/tasks/{id}          - Get single task
PUT    /api/tasks/{id}          - Update a task
DELETE /api/tasks/{id}          - Delete a task
PATCH  /api/tasks/{id}/status   - Update task status

Analogy: Our plan is like a recipe with all ingredients listed and steps in the right order. A good chef doesn't just throw things in a pot—they prep ingredients, understand cooking times, and know which techniques to use when. Similarly, our development plan sets us up for successful implementation.

Step 3: Execute the Plan

Phase 1: Project Setup and Authentication

Initialize Project

composer create-project laravel/laravel taskmaster
cd taskmaster
composer require laravel/passport
php artisan migrate
php artisan passport:install

Configure Passport in User Model

// app/Models/User.php
namespace App\Models;

use Laravel\Passport\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
    
    // Rest of your User model...
}

Update Auth Configuration

// config/auth.php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],

Phase 2: Models and Migrations

Create Models with Relationships

// app/Models/Project.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Project extends Model
{
    protected $fillable = ['name', 'description', 'user_id'];
    
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    
    public function tasks()
    {
        return $this->hasMany(Task::class);
    }
}

// app/Models/Task.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    protected $fillable = [
        'title', 'description', 'project_id', 
        'assigned_to', 'status', 'due_date'
    ];
    
    public function project()
    {
        return $this->belongsTo(Project::class);
    }
    
    public function assignedUser()
    {
        return $this->belongsTo(User::class, 'assigned_to');
    }
}

Phase 3: API Controllers and Resources

Create API Resources

// app/Http/Resources/ProjectResource.php
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class ProjectResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'description' => $this->description,
            'created_by' => [
                'id' => $this->user->id,
                'name' => $this->user->name,
            ],
            'tasks_count' => $this->tasks->count(),
            'created_at' => $this->created_at->format('Y-m-d H:i:s'),
            'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
        ];
    }
}

// app/Http/Resources/TaskResource.php
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class TaskResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'description' => $this->description,
            'project' => [
                'id' => $this->project->id,
                'name' => $this->project->name,
            ],
            'status' => $this->status,
            'due_date' => $this->due_date,
            'assigned_to' => $this->when($this->assignedUser, [
                'id' => $this->assignedUser->id,
                'name' => $this->assignedUser->name,
            ]),
            'created_at' => $this->created_at->format('Y-m-d H:i:s'),
            'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
        ];
    }
}

Create API Controllers

// app/Http/Controllers/API/ProjectController.php
namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Project;
use App\Http\Resources\ProjectResource;
use Illuminate\Http\Request;

class ProjectController extends Controller
{
    public function index()
    {
        return ProjectResource::collection(
            Project::where('user_id', auth()->id())->get()
        );
    }
    
    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
        ]);
        
        $project = Project::create([
            'user_id' => auth()->id(),
            'name' => $validated['name'],
            'description' => $validated['description'] ?? null,
        ]);
        
        return new ProjectResource($project);
    }
    
    public function show(Project $project)
    {
        $this->authorize('view', $project);
        return new ProjectResource($project);
    }
    
    public function update(Request $request, Project $project)
    {
        $this->authorize('update', $project);
        
        $validated = $request->validate([
            'name' => 'sometimes|required|string|max:255',
            'description' => 'nullable|string',
        ]);
        
        $project->update($validated);
        
        return new ProjectResource($project);
    }
    
    public function destroy(Project $project)
    {
        $this->authorize('delete', $project);
        
        $project->delete();
        
        return response()->json(['message' => 'Project deleted successfully']);
    }
}

Analogy: Think of this execution phase like constructing a building. We laid the foundation (database and models), built the framework (controllers), and now we're adding the functional systems (API endpoints). Each component must be built correctly and in the right order for the whole structure to stand strong.

Implementing the TaskController

// app/Http/Controllers/API/TaskController.php
namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Project;
use App\Models\Task;
use App\Http\Resources\TaskResource;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index(Project $project)
    {
        $this->authorize('view', $project);
        
        return TaskResource::collection(
            $project->tasks()->with('assignedUser')->get()
        );
    }
    
    public function store(Request $request, Project $project)
    {
        $this->authorize('update', $project);
        
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'description' => 'nullable|string',
            'assigned_to' => 'nullable|exists:users,id',
            'due_date' => 'nullable|date',
            'status' => 'nullable|in:todo,in_progress,completed',
        ]);
        
        $task = $project->tasks()->create($validated);
        
        return new TaskResource($task->load('assignedUser'));
    }
    
    public function show(Task $task)
    {
        $this->authorize('view', $task->project);
        
        return new TaskResource($task->load('assignedUser'));
    }
    
    public function update(Request $request, Task $task)
    {
        $this->authorize('update', $task->project);
        
        $validated = $request->validate([
            'title' => 'sometimes|required|string|max:255',
            'description' => 'nullable|string',
            'assigned_to' => 'nullable|exists:users,id',
            'due_date' => 'nullable|date',
            'status' => 'nullable|in:todo,in_progress,completed',
        ]);
        
        $task->update($validated);
        
        return new TaskResource($task->load('assignedUser'));
    }
    
    public function destroy(Task $task)
    {
        $this->authorize('update', $task->project);
        
        $task->delete();
        
        return response()->json(['message' => 'Task deleted successfully']);
    }
    
    public function updateStatus(Request $request, Task $task)
    {
        $this->authorize('update', $task->project);
        
        $validated = $request->validate([
            'status' => 'required|in:todo,in_progress,completed',
        ]);
        
        $task->update(['status' => $validated['status']]);
        
        return new TaskResource($task->load('assignedUser'));
    }
}

Phase 4: Web Interface

For a complete application, we'll also need a web interface. This would include:

The web interface would follow MVC architecture, using Blade templates and Laravel's authentication scaffolding.

Phase 5: Policies and Authorization

// app/Policies/ProjectPolicy.php
namespace App\Policies;

use App\Models\Project;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class ProjectPolicy
{
    use HandlesAuthorization;
    
    public function view(User $user, Project $project)
    {
        return $user->id === $project->user_id || $user->role === 'admin';
    }
    
    public function update(User $user, Project $project)
    {
        return $user->id === $project->user_id || $user->role === 'admin';
    }
    
    public function delete(User $user, Project $project)
    {
        return $user->id === $project->user_id || $user->role === 'admin';
    }
}

Phase 6: Routes Configuration

// routes/api.php
use App\Http\Controllers\API\AuthController;
use App\Http\Controllers\API\ProjectController;
use App\Http\Controllers\API\TaskController;

// Authentication routes
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

// Protected routes
Route::middleware('auth:api')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    
    // Projects
    Route::apiResource('projects', ProjectController::class);
    
    // Tasks within projects
    Route::get('projects/{project}/tasks', [TaskController::class, 'index']);
    Route::post('projects/{project}/tasks', [TaskController::class, 'store']);
    
    // Individual tasks
    Route::apiResource('tasks', TaskController::class)->except(['index', 'store']);
    Route::patch('tasks/{task}/status', [TaskController::class, 'updateStatus']);
});

Step 4: Review and Reflect

Following Polya's final step, let's review our implementation and reflect on what we've built:

Checking Our Solution

Learning Points

Room for Improvement

Analogy: Just as a construction project concludes with inspections and a punch list of final items, we've assessed our application for completeness, security, and maintainability. This final review ensures we've delivered a solution that truly solves the original problem.

Weekend Project Assignment

Now it's your turn to apply Polya's problem-solving procedure to build your own Laravel application. Your weekend project is to extend the TaskMaster application we've discussed:

Basic Requirements

  1. Implement the full TaskMaster API as described in this lecture
  2. Add a web interface using Blade templates
  3. Add at least one feature not discussed (e.g., comments on tasks, file attachments, or notifications)

Follow Polya's Steps

  1. Understand: Begin by writing out your requirements and entities
  2. Plan: Design your database schema and application structure
  3. Execute: Implement your solution step-by-step
  4. Review: Test your application and identify improvements

Submission Guidelines

Conclusion: The Power of Systematic Problem Solving

George Polya's 4-step process isn't just for mathematics—it's a powerful framework for tackling software development projects:

By applying this structured approach to Laravel development, you'll build more robust, well-designed applications and avoid common pitfalls of diving straight into coding without proper planning.

Remember that software development is ultimately about problem-solving, and mastering a systematic approach like Polya's will serve you well throughout your career.

Good luck with your weekend project!