Build a Portfolio with Laravel
Learn Laravel by Building a Professional Portfolio Website
This comprehensive Laravel tutorial guides you through building a complete portfolio website with an admin panel. You'll learn Laravel fundamentals including Eloquent ORM, Blade templating, authentication, file uploads, and database relationships. Perfect for intermediate PHP developers ready to master Laravel's elegant MVC architecture.
What You'll Build
- Public-facing portfolio showcase with project galleries
- Admin dashboard for managing projects, skills, and content
- Contact form with email notifications
- Image upload system with optimization
- Responsive design with Tailwind CSS
Skills You'll Master
- Laravel Routing & Controllers: Build RESTful routes and organize application logic
- Eloquent ORM: Create models, relationships, and database queries
- Blade Templating: Master Laravel's powerful templating engine
- Authentication: Implement secure login with Laravel Breeze
- File Uploads: Handle image uploads with validation and optimization
- Database Migrations: Version control your database schema
- Testing with PHPUnit: Write feature tests for your portfolio
Prerequisites
- Basic PHP knowledge (classes, functions, arrays)
- Understanding of HTML, CSS, and basic JavaScript
- Composer installed on your system
- MySQL or PostgreSQL database
- Code editor (VS Code or PHPStorm recommended)
Time Commitment: 6-8 hours total. Follow at your own pace with AI assistance. Each step includes copyable prompts for Claude or ChatGPT to help you build faster.
Why Laravel for Portfolio Development?
Laravel is the most popular PHP framework, trusted by companies worldwide for its elegant syntax and developer-friendly features. Building a portfolio with Laravel demonstrates professional-level PHP skills that employers value. Laravel's batteries-included approach means you get authentication, database tools, and testing frameworks out of the boxβletting you focus on building features instead of reinventing wheels.
This tutorial teaches real-world Laravel development patterns you'll use in production applications. You'll learn to structure code properly, implement security best practices, and deploy to professional hosting platforms like Laravel Forge.
Build a Portfolio with Laravel
Laravel isn't just a PHP framework - it's the sophisticated choice for building professional portfolios that scale. With its elegant syntax, powerful ORM, and built-in authentication, you will create a portfolio that can grow from a simple showcase to a full content management system. This guide shows you how to leverage Laravel's batteries-included approach to build a portfolio that impresses both visitors and potential employers. From beautiful Blade templates to robust backend architecture, you're building something that demonstrates real engineering skill.
Laravel Portfolio Setup: Installation and Project Configuration
Initialize Laravel project
Initialize a new Laravel portfolio project using composer create-project laravel/laravel portfolio Set up the proper folder structure for portfolio application Configure the .env file with APP_NAME set to your portfolio name, APP_ENV=local for development, and APP_DEBUG=true Generate application key with php artisan key:generate Initialize git repository and create .gitignore for Laravel projects Set up proper directory permissions for storage and bootstrap/cache directories (chmod -R 775 storage bootstrap/cache) Plan your portfolio architecture: admin panel for content management and public-facing portfolio pages.
Configure PHPStorm/VS Code
Configure IDE for Laravel portfolio development For VS Code: install essential extensions: Laravel Extension Pack, PHP Intelephense, Laravel Blade Snippets, Laravel Extra Intellisense, Tailwind CSS IntelliSense Create .vscode/settings.json with PHP formatting rules, Blade formatter settings, and file associations for .blade.php files Set up PHP CS Fixer or Laravel Pint for code style consistency (PSR-12 standard) Configure Xdebug for debugging For PHPStorm: enable Laravel plugin, configure PHP interpreter, set up Blade formatting, configure database tools Create .editorconfig for consistent formatting (4-space indent, UTF-8 charset) Set up code snippets for common Laravel patterns.
Laravel Breeze, Livewire
Install essential packages for Laravel portfolio functionality Install Laravel Breeze for authentication scaffolding: composer require laravel/breeze --dev, then php artisan breeze:install (choose Blade with Alpine or Livewire stack) Install Livewire for dynamic components: composer require livewire/livewire Add spatie/laravel-sluggable for automatic URL slug generation: composer require spatie/laravel-sluggable Install intervention/image for image manipulation and optimization: composer require intervention/image For admin panel, consider laravel/nova (paid) or install filament/filament (free): composer require filament/filament Run npm install to set up frontend dependencies (Tailwind CSS, Alpine.js) Configure each package: publish config files with php artisan vendor:publish commands as needed Run migrations with php artisan migrate.
Database and Environment Configuration for Laravel
Configure MySQL/PostgreSQL
Configure database for portfolio content storage In .env file, set up database connection: choose DB_CONNECTION (mysql or pgsql), set DB_HOST (localhost or 127.0.0.1), DB_PORT (3306 for MySQL, 5432 for PostgreSQL), DB_DATABASE (portfolio), DB_USERNAME, and DB_PASSWORD Create the database using command line (mysql -u root -p, CREATE DATABASE portfolio;) or GUI tool like TablePlus, phpMyAdmin, or MySQL Workbench Test connection with php artisan migrate:status Configure database timezone and charset in config/database.php (utf8mb4 for full Unicode support including emojis) Set up separate testing database in .env.testing for running tests without affecting development data.
Create projects, skills tables
Create database migrations for portfolio data Run php artisan make:migration create_projects_table to create projects table with columns: id, title, slug (unique), description (text), long_description (text, nullable), tech_stack (json or separate table), project_url (nullable), github_url (nullable), image_path, featured (boolean, default false), display_order (integer), published_at (nullable timestamp), timestamps, soft_deletes Create skills table with: id, name, category (frontend/backend/tools/soft), proficiency_level (beginner/intermediate/advanced/expert), icon_path (nullable), display_order Create categories table if organizing projects by category: id, name, slug, description Create project_skill pivot table for many-to-many relationship: project_id, skill_id Add indexes on slug columns and foreign key constraints Run php artisan migrate to create tables Create seeders for initial data: php artisan make:seeder ProjectSeeder.
Set up Blade with Tailwind
Set up Blade template structure with Tailwind CSS styling
Create layouts/app.blade.php as main layout with <!DOCTYPE html>, head with meta tags (charset, viewport, CSRF token), @vite directive for assets, navigation header, main content area with @yield("content") or {{ $slot }}, and footer
If using Breeze, this is already created - customize it
Build layouts/guest.blade.php for public portfolio pages
Create components/nav.blade.php with responsive navigation menu: logo/name, links to Home, Projects, About, Contact sections (smooth scroll or separate pages), mobile hamburger menu using Alpine.js
Create components/footer.blade.php with social media links, copyright, optional sitemap
Set up Tailwind CSS: customize tailwind.config.js with your color scheme, fonts (add Google Fonts), and breakpoints
Create resources/css/app.css with custom styles and Tailwind directives
Build reusable Blade components for: project cards, skill badges, buttons, form inputs
Ensure responsive design with mobile-first approach
Run npm run dev to compile assets.Building Portfolio Features: Core Functionality and Admin Panel
Create Project, Skill models
Create Eloquent models for portfolio data management Generate models with php artisan make:model Project and php artisan make:model Skill In Project model: define $fillable array (title, slug, description, long_description, tech_stack, project_url, github_url, image_path, featured, display_order, published_at), add $casts for tech_stack (array) and published_at (datetime), implement HasSlug trait from spatie/sluggable for automatic slug generation, add relationships (belongsToMany Skills, hasMany images if multiple images), create published() scope for filtering published projects, add accessor for formatted_tech_stack, implement soft deletes with SoftDeletes trait In Skill model: define $fillable (name, category, proficiency_level, icon_path, display_order), add relationships (belongsToMany Projects), create scopes for filtering by category (scopeByCategory) Create model factories for testing: php artisan make:factory ProjectFactory and SkillFactory Implement model events (creating, updating) if needed for slug generation or other automation.
Build admin dashboard for content
Build an admin panel for managing portfolio content If using Filament: install and configure with php artisan filament:install, create admin user with php artisan make:filament-user, create resources for Projects (php artisan make:filament-resource Project --generate) and Skills, customize forms with proper field types (TextInput, Textarea, Select, FileUpload, Toggle for featured), add table columns and filters If building custom admin: create AdminController, design admin layout in resources/views/admin with sidebar navigation, create CRUD views for projects (index, create, edit), implement image upload handling with validation (max size, image types), use Storage facade for file operations, add rich text editor for descriptions (Trix, TinyMCE, or CKEditor), implement drag-and-drop reordering for display_order using Livewire or JavaScript library. Secure admin routes with auth middleware Add authorization using Laravel policies to ensure only authenticated admins can access Create dashboard view showing statistics: total projects, published projects, skills count Implement bulk actions for publishing/unpublishing projects.
Create home, projects, about, contact
Build public-facing portfolio pages with Laravel Blade
Create PortfolioController to handle routes
Build Home page (resources/views/welcome.blade.php or home.blade.php) with hero section (name, title, tagline, CTA buttons), featured projects carousel or grid (query featured projects), skills overview, call-to-action to view all projects
Create Projects page (projects/index.blade.php) displaying all published projects in grid layout with filtering/sorting options (by technology, category), use Eloquent query with eager loading: Project::with("skills")->published()->orderBy("display_order")->get()
Build individual project detail page (projects/show.blade.php) with full description, tech stack, screenshots/images, live demo and GitHub links, related projects
Create About page with bio, professional photo, comprehensive skills section organized by category (use Skill model grouped by category), timeline of experience/education, downloadable resume link
Build Contact page with contact form (name, email, message), social media links, and location
Implement form submission: create ContactController with store() method, validate input, send email using Laravel Mail with contact.blade.php email template, store messages in database (optional), show success message with flash session
Add smooth scrolling for same-page navigation links using Alpine.js or JavaScript.Implement admin login
Implement authentication for admin panel access
If using Laravel Breeze, authentication is already scaffolded - customize it for your needs
Create admin user seeder (database/seeders/AdminSeeder.php) to create initial admin account with hashed password
Protect admin routes in routes/web.php with auth middleware: Route::middleware(["auth"])->prefix("admin")->group()
Consider adding role-based authorization: create is_admin boolean column in users migration, add middleware to check admin status, or use spatie/laravel-permission package for more robust role management. Customize login view to match portfolio design
Add logout functionality in admin header
Implement password reset functionality
Add remember me option
Consider two-factor authentication for enhanced security using laravel/fortify
Create admin profile page where admin can update their information
Add activity logging to track admin actions on portfolio content
Ensure auth redirects work correctly: after login redirect to admin dashboard, after logout redirect to homepage.Set up image uploads
Implement robust media management for portfolio images
Configure file storage in config/filesystems.php: set default disk to "public", ensure public disk is configured correctly
Create storage symlink with php artisan storage:link to make storage/app/public accessible via public/storage
In admin project forms, add image upload field: use HTML file input or Livewire FileUpload component
Implement upload handling in controller: validate image (required|image|mimes:jpeg,png,jpg,webp|max:5048), store with $request->file("image")->store("projects", "public"), save path to database
Use Intervention Image package to resize and optimize images on upload: create multiple sizes (thumbnail, medium, full), maintain aspect ratio, convert to WebP for better compression
Create ImageService class to handle image operations (upload, resize, delete)
Implement image deletion when project is deleted or image is replaced using Storage::delete()
On frontend, display images with proper alt tags for accessibility and SEO
Consider implementing image gallery for multiple images per project: create project_images table with project_id, image_path, order
Add drag-and-drop multi-image upload in admin. Show image previews in admin panel
Implement lazy loading for images in frontend for better performance.Advanced Image Management & Galleries
Add comprehensive image management for portfolio project galleries. Ensure intervention/image package is installed: composer require intervention/image. Configure image settings in config/filesystems.php: create projects disk in storage/app/public/projects, configure image quality and format settings. Create project_images migration and model: php artisan make:migration create_project_images_table with fields: project_id (foreign key), image_path, thumbnail_path, caption, display_order, is_featured. Build ProjectImage model with relationships: belongsTo Project, implement ordering scope. Update Project model: add hasMany ProjectImages relationship, implement featured image accessor. Create ImageService class for image operations: upload and resize images (create multiple sizes: thumbnail 400px, medium 800px, large 1200px), maintain aspect ratio, convert to WebP with JPG fallback, optimize with quality settings, generate unique filenames using Str::uuid(), store in organized folder structure. Implement admin image management: create image upload interface in admin/projects views using Dropzone.js or Livewire File Upload, allow multiple image uploads simultaneously, show image previews immediately after upload, implement drag-and-drop reordering using SortableJS, AJAX endpoints for ordering changes. Build gallery display for project detail pages: create Blade component for image gallery, implement carousel/slider with Swiper or Splide, add lightbox functionality for full-size viewing using PhotoSwipe or GLightbox, show captions and image metadata. For hero section and profile images: create separate upload endpoint for hero background images, implement image cropping interface using Cropper.js, allow position/zoom controls, generate optimized versions for different screen sizes. Add advanced features: implement lazy loading with native loading="lazy" and IntersectionObserver fallback, serve responsive images using <picture> element with WebP and fallback formats, add image metadata: alt text (required), caption, photographer credits, implement schema.org ImageObject for SEO. Create image cleanup service: delete old images when replaced, remove orphaned images with scheduled task (php artisan make:command CleanOrphanedImages), run weekly via Laravel Scheduler. For large portfolios: integrate with cloud storage (S3, Cloudinary) using Laravel Storage facades, configure CDN for faster global delivery, implement image transformation on-the-fly with Cloudinary or Imgix. Add image validation in FormRequests: validate file types (jpeg, png, gif, webp), enforce size limits (max:5120 for 5MB), check image dimensions if needed.
Testing Your Laravel Portfolio Application
Configure PHPUnit
Set up testing infrastructure for Laravel portfolio. PHPUnit is included by default - configure it for your needs. Review phpunit.xml configuration: ensure test database is configured (DB_CONNECTION=sqlite, DB_DATABASE=:memory: for in-memory testing), set BCRYPT_ROUNDS=4 for faster password hashing in tests, configure other test environment variables Create TestCase extensions if needed Set up database factories for models: php artisan make:factory ProjectFactory with realistic fake data (title using faker, description, tech stack, URLs), php artisan make:factory SkillFactory, php artisan make:factory UserFactory (may already exist) Create seeders for test data: TestDatabaseSeeder Add helper methods in tests/TestCase.php for common operations like creating admin user, authenticating Install additional testing packages if desired: pestphp/pest for more expressive syntax (Laravel 10+ includes Pest by default) Create separate test directories: tests/Feature for feature tests, tests/Unit for unit tests Add test command shortcuts in composer.json scripts Configure code coverage reporting if desired Plan your testing strategy: test public portfolio pages (guests can view), test admin CRUD operations (require authentication), test authorization (only admins can access admin), test image uploads, test form validation.
Test homepage renders
Write your first feature test for the portfolio homepage
Create tests/Feature/HomePageTest.php using php artisan make:test HomePageTest
Test that homepage returns successful response: $this->get("/")->assertStatus(200)
Test that page displays expected elements: assertSee your name/title, assertSee navigation links, assertSee hero section content
Test that featured projects are displayed: use Project factory to create featured projects, assert their titles appear on page, assert non-featured projects do not appear
Test that published projects appear but unpublished do not: create mix of published and unpublished projects, query homepage, assert only published show
Use RefreshDatabase trait to reset database for each test
Test responsive navigation: assert mobile menu exists, desktop menu exists
Test links work: assertSee with expected href attributes
Run test with php artisan test or ./vendor/bin/phpunit and verify it passes. This establishes your testing foundation and ensures homepage works as expected.Test portfolio features
Write comprehensive feature tests for all portfolio functionality Test public pages: projects index shows all published projects with pagination, project detail page displays correct project information, about page renders successfully, contact form submission works (test validation, email sending, success message) Test admin functionality: create tests/Feature/Admin/ProjectManagementTest.php, test admin can view projects list, admin can create new project with valid data, validation prevents invalid project creation (test required fields, image validation), admin can update existing project, admin can delete project (soft delete), only authenticated users can access admin routes (guests redirected to login), test image upload creates file in storage Test project features: test filtering projects by technology/category, test featured projects query, test project ordering by display_order Use TestCase methods: actingAs() for authenticated requests, assertDatabaseHas/Missing for database assertions, assertRedirect for redirects, assertSessionHas for flash messages Create helper method createAdminUser() in TestCase. Mock external services like email sending if needed Test edge cases: empty states, invalid IDs (404 errors), permission denial (403 errors). Aim for high test coverage of critical functionality to catch regressions.
Deploying Your Laravel Portfolio to Production
Deploy to Laravel Forge
Deploy Laravel portfolio to production environment Using Laravel Forge: create account on forge.laravel.com, connect your server provider (DigitalOcean, AWS, Linode, Vultr), provision new server (select PHP 8.2+, database, Redis), create new site on server with your domain, connect Git repository (GitHub, GitLab, Bitbucket), configure deployment script in Forge dashboard: git pull, composer install --optimize-autoloader --no-dev, npm install && npm run build, php artisan config:cache, php artisan route:cache, php artisan view:cache, php artisan migrate --force, php artisan storage:link (only needed once) Configure environment variables in Forge: set production values for APP_ENV=production, APP_DEBUG=false, APP_URL with your domain, database credentials, mail settings Set up SSL certificate (Forge can provision free Let's Encrypt certificate). Configure server settings: PHP version, memory limits, upload size. Set up scheduled jobs if needed (php artisan schedule:run). Configure queue workers if using queues. Set up automatic backups for database and storage. Test deployment: trigger deploy via Forge or git push to connected branch, monitor deployment log for errors. After successful deployment: test all pages load, test admin panel access, test image uploads work, verify email sending works, test contact form. Set up monitoring: enable Laravel Telescope in local/staging for debugging (not production), use error tracking service like Sentry or Flare, monitor server resources in Forge dashboard. Configure DNS to point domain to server IP. Test site on multiple devices and browsers.
