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:
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:
- User authentication (web and API)
- Project and task management
- Team member assignments
- RESTful API with proper resources
- Database relationships
Step 1: Understand the Problem
Following Polya's first step, let's fully understand what we need to build:
Defining Requirements
- Users need to register, log in, and manage their profile
- Projects group related tasks together
- Tasks are the core items that need to be completed
- Assignments connect tasks to specific users
- Both web interface and API access are needed
Entity Relationships
Key Questions
- What user roles do we need? (Admin, Regular User, Guest)
- What task statuses should we support? (To Do, In Progress, Completed)
- What API endpoints will be required?
- How should we handle validation and error responses?
- What security measures are necessary?
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
- Initialize Laravel project and set up development environment
- Design and create database migrations, models, and relationships
- Implement authentication for both web and API
- Create web controllers and views for core functionality
- Develop API controllers and resources
- Implement validation and error handling
- Add security and permissions
- 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:
- Web controllers extending the API logic
- Blade templates for views
- Authentication views and controllers
- Dashboard for user projects and tasks
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
- Does it satisfy the requirements? Our application provides user management, project and task tracking, and a full API.
- Is the code clean and maintainable? We've followed Laravel best practices with proper models, controllers, and resources.
- Is it secure? We've implemented authentication, authorization policies, and validation.
- Is it tested? We would need to add comprehensive tests (beyond our scope today).
Learning Points
- API Resources provide a clean separation between internal data models and external representations
- Policy classes centralize authorization logic and keep controllers clean
- Proper validation ensures data integrity throughout the application
- Laravel Passport simplifies OAuth2 implementation for API authentication
Room for Improvement
- Add comprehensive testing (unit, feature, integration)
- Implement more granular permissions system
- Add team functionality for collaborative projects
- Implement real-time notifications for task updates
- Add reporting and analytics features
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
- Implement the full TaskMaster API as described in this lecture
- Add a web interface using Blade templates
- Add at least one feature not discussed (e.g., comments on tasks, file attachments, or notifications)
Follow Polya's Steps
- Understand: Begin by writing out your requirements and entities
- Plan: Design your database schema and application structure
- Execute: Implement your solution step-by-step
- Review: Test your application and identify improvements
Submission Guidelines
- Push your code to a GitHub repository
- Include a README.md that explains your application and how to set it up
- Document your API endpoints
- Include screenshots of your web interface
- Be prepared to present your solution on Monday
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:
- Understanding the problem ensures we build the right solution
- Devising a plan helps us structure our approach and identify components
- Executing the plan translates our design into working code
- Reviewing the solution helps us validate our work and find improvements
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!