7 Ways to Make Your Laravel App Seamlessly Faster
Laravel is among the most popular PHP frameworks today and my favorite framework in the PHP realm. As your application grows, it may start to lag, leading to reduced user satisfaction. Fortunately, several techniques can help you optimize your Laravel application for speed. Here are ten ways to make your Laravel app seamlessly faster:
1. Database Optimization
Eager Loading
Avoid the N+1 query problem by using Laravel’s with()
method when retrieving records.
Scenario
Let's assume you have a Post
model, and each Post
belongs to a User
. If you want to list 100 posts along with the name of the user who wrote each post, you might do something like:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
}
This would result in 101 database queries: 1 query to get all posts and 100 additional queries to get the user for each post.
Solution With Eager Loading
With eager loading, you can load all the required data in just 2 queries:
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name;
}
Here, only 2 queries are executed:
- One to retrieve all posts.
- One to retrieve all users associated with the retrieved posts.
Nested Eager Loading
If a User
has many comments
, and you want to get all posts, with their authors, and all comments associated with these authors, you can do:
$posts = Post::with('user.comments')->get();
Conditional Eager Loading
You can also add conditions to your eager loading. For instance, if you only want to load comments that are approved:
$posts = Post::with(['user.comments' => function ($query) {
$query->where('approved', true);
}])->get();
By using eager loading appropriately, you can drastically reduce the number of database queries, thereby improving the performance of your Laravel application.
Database Indexing
Always index your database tables, especially columns used in WHERE clauses, joins, or ORDER BY.
Scenario
Imagine you have a users
table with thousands of records. This table has several columns like id
, name
, email
, and country
.
Now, let’s say you frequently query the users
table to fetch users based on their country
.
Without Indexing: If the country
column isn't indexed and you run a query like:
SELECT * FROM users WHERE country = 'PH';
Instead of fetching all records at once, use Laravel’s paginate()
method.
The database will perform a full table scan, going row by row checking if the country
matches PH. This can be very slow, especially if the table has a large number of rows.
If you create an index on the country
column, the database can quickly locate the rows for 'USA' without scanning the entire table. Creating an index might look something like this in a migration:
Schema::table('users', function (Blueprint $table) {
$table->index('country');
});
Now, when you run the same query:
SELECT * FROM users WHERE country = 'USA';
The database can swiftly find the relevant rows using the index, leading to much faster query times.
Things to Consider:
- While indexes speed up read operations, they can slightly slow down write operations (like
INSERT
,UPDATE
,DELETE
) because the database needs to update the index tree structure. - Indexes consume additional disk space.
- It’s essential to strike a balance. Over-indexing can be detrimental, so only index columns that are frequently involved in WHERE clauses, JOIN operations, or are used for sorting.
Conclusion
Database indexing is a powerful tool in optimizing database operations. By understanding and analyzing the queries your application runs frequently, you can decide on the appropriate columns to index, which in turn can significantly boost your Laravel application’s performance.
2. Using Session and/or Caching
Cache Data
Use Laravel’s cache drivers like Redis or Memcached to store frequently accessed data.
Scenario
Suppose you have a blog and want to display the latest 10 posts on the homepage. Fetching these posts from the database every time a user visits the homepage can be inefficient, especially if the homepage receives a lot of traffic. Instead, you can cache the results and serve them much faster to subsequent visitors.
Without Caching:
Fetching the latest 10 posts:
$latestPosts = Post::latest()->take(10)->get();
With Caching:
- Storing Data in Cache:
use Illuminate\Support\Facades\Cache;
$minutes = 60; // Cache duration
$latestPosts = Cache::remember('latest_posts', $minutes, function () {
return Post::latest()->take(10)->get();
});
Here’s what’s happening:
- The
remember
method checks if 'latest_posts' exists in the cache. - If it does, it retrieves the cached value.
- If not, it executes the closure provided (fetching posts from the database) and then stores the result in the cache.
Clearing/Flushing Cache
When a new post is added or an existing post is updated/deleted, you’ll want to clear this cache to ensure that the homepage displays the updated data.
Cache::forget('latest_posts');
or if you want to clear all cache:
Cache::flush();
Using Different Cache Drivers
Laravel supports various cache drivers such as file
, database
, redis
, memcached
, etc. You can specify your desired driver in the .env
file:
CACHE_DRIVER=redis
Make sure you have the necessary setup ready for the specific driver, e.g., a running Redis server in this case.
Conclusion
By caching data, you can reduce the number of database queries, which in turn can drastically improve the performance of your Laravel application, especially for data that doesn’t change often. Always remember to manage your cache by invalidating (clearing) cached data when the original data changes.
Session Driver
Store sessions in a fast session driver. If your application has high traffic, consider using Redis or database sessions instead of the file driver.
The session driver in Laravel determines how session data is stored. Laravel supports several session drivers:
file
- sessions are stored in file storagecookie
- sessions are stored in secure, encrypted cookiesdatabase
- sessions are stored in a relational databasememcached
/redis
- sessions are stored in one of these fast, cache-based storesarray
- sessions are stored in a PHP array and will not be persisted
Let’s dive into an example of using the database
session driver:
Switching to the Database Session Driver
First, in your .env
file, update the SESSION_DRIVER
setting:
SESSION_DRIVER=database
Before using the database
session driver, you'll need a table to hold the session items. Luckily, Laravel provides an Artisan command to generate this table:
php artisan session:table
This will create a migration for the sessions table. Run the migration to create the table:
php artisan migrate
Once you’ve switched to the database
session driver, using sessions remains the same. For instance:
// Storing a piece of data in the session
session(['user_id' => 1]);
// Retrieving data from the session
$userId = session('user_id');
// Removing data from the session
session()->forget('user_id');
// Clearing all data from the session
session()->flush();
The difference now is that the session data is being stored in and retrieved from the database, rather than files or whatever the previous driver was.
Benefits:
- Centralized storage: If you’re running multiple instances of your application (like in a load-balanced environment), using the
database
orredis
session drivers can ensure that session data is available to all instances. - Persistence: Sessions stored in the database persist even if your application server is restarted.
Drawbacks:
- Performance: Writing to and reading from a database can be slower than using in-memory stores like
redis
ormemcached
.
Conclusion
Choosing the right session driver depends on the needs and infrastructure of your application. If you need centralized and persistent storage, the database
driver might be suitable. However, if you need high-speed access and have the necessary setup, redis
or memcached
might be better choices.
3. Compile Assets
Use Laravel Mix to compile and minify assets such as CSS, JavaScript, and images. This reduces the file sizes and reduces HTTP requests.
Here’s how you can compile assets using Laravel Mix:
Setup
- By default, Laravel’s
package.json
file includes everything you need to get started. You might have to runnpm install
to download all the necessary dependencies if you haven't done so already. - In the root of your Laravel project, there’s a
webpack.mix.js
file where you can define your mix tasks. - Suppose you have a main
resources/js/app.js
file and aresources/sass/app.scss
SASS file that you want to compile down to CSS:
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
Running Mix
Once you’ve defined your tasks, you can run the following commands to compile your assets:
npm run dev
: This command runs your tasks without minifying the output.npm run watch
: This command will keep a watcher and automatically recompile your assets whenever you save changes to your source files.npm run production
: This will run the tasks and also minify the output, making it optimized for production.
Versioning / Cache Busting
In a production environment, browsers might cache your assets. To ensure users always get the latest assets, you can use the version
method to generate a unique hash:
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.version();
Then, in your blade view, you can use the mix
function to generate the appropriate URL with the hash:
<script src="{{ mix('js/app.js') }}"></script>
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
Source Maps
It can be helpful to generate source maps during development to track down errors in your original source files rather than the compiled output. Simply chain the sourceMaps
method onto your mix chain:
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.sourceMaps();
Conclusion
By using Laravel Mix, you can seamlessly compile and optimize your application’s assets, making it ready for production use. It abstracts the complexity of Webpack, allowing developers to focus on the development process.
4. Optimize Images
Optimizing images can significantly improve the performance of your website or application. Images that aren’t optimized can lead to longer load times, increased bandwidth usage, and a diminished user experience. Here’s how to optimize images for your Laravel application:
Using Laravel Image Intervention Package:
The intervention/image
package provides an easy way to handle and manipulate images. It also includes some optimization methods.
Installation
composer require intervention/image
Service Provider & Facade (For Laravel versions below 5.5, because Laravel 5.5+ has package auto-discovery):
Add the service provider in config/app.php
:
'providers' => [
// ...
Intervention\Image\ImageServiceProvider::class,
],
'aliases' => [
// ...
'Image' => Intervention\Image\Facades\Image::class,
],
Usage
You can resize images, which is one way to optimize them:
use Image;
$image = Image::make('path/to/image.jpg')->resize(300, 200)->save('path/to/new/image.jpg');
Using Spatie Image Optimizer Package
The spatie/laravel-image-optimizer
package optimizes images without any noticeable quality loss.
Installation
composer require spatie/laravel-image-optimizer
This package requires the installation of a few tools on your server, such as jpegoptim
, pngquant
, svgo
, etc. You can find the installation details in the package's documentation.
Usage
use Spatie\LaravelImageOptimizer\Facades\ImageOptimizer;
ImageOptimizer::optimize($pathToImage);
You can also specify which optimization tools to use and customize their settings.
Serve WebP Images
WebP is a modern image format that provides better compression than PNG or JPEG formats. Serving WebP images can lead to significant bandwidth savings.
You can use the intervention/image
package to easily convert images to WebP:
use Image;
$image = Image::make('path/to/image.jpg')
->encode('webp', 75)
->save('path/to/image.webp');
Lazy Loading
Consider lazy loading your images so they only load when they enter the viewport. This decreases the initial load time of the page. There are numerous JavaScript libraries available to assist with this, such as lozad.js
or the loading
attribute with the value lazy
for native lazy-loading in modern browsers.
Content Delivery Network (CDN)
Consider serving your images from a CDN. CDNs can cache your images in multiple locations around the world, ensuring that users download them from the nearest server. This results in faster load times.
Conclusion
Optimizing images is essential for any web application. By ensuring that your images are appropriately sized, compressed, and served efficiently, you can improve the performance of your Laravel application and provide a better user experience.
5. Limit Use of Plugins/Providers
Each additional provider or package can add extra load time. Only use necessary packages and always check for their performance overhead.
Overusing plugins or service providers in a Laravel application can lead to slower performance, increased boot-up time, and potentially, conflicts between different packages. It’s vital to periodically review and limit the use of unnecessary plugins/providers.
Example
Let’s take an example of a Laravel project that initially started as a blog but has evolved over time.
Identifying Unnecessary Providers
You realize that over time, as your application evolved, some of the previously installed packages are no longer necessary:
- A comments plugin to allow users to comment on blog posts.
- A syntax highlighter for code snippets in the blog.
- An SEO package to optimize your blog posts for search engines.
- A newsletter package to allow users to subscribe and receive regular blog post updates.
Reviewing Current Usage
Upon reviewing the current features of the blog:
- Comments have been disabled for a long time due to spam issues.
- You switched to a third-party service for newsletters.
Removing Unused Plugins
Given the review
- The comments plugin can be safely removed.
- The newsletter package is unnecessary since you’re using a third-party service.
Steps to Remove Unused Plugins/Providers
Remove Service Providers
Open config/app.php
and locate the service providers and aliases arrays. Remove any references to the unnecessary packages:
'providers' => [
//...
// Remove this line if you're not using it anymore
// CommentsServiceProvider::class,
SyntaxHighlighterServiceProvider::class,
SEOPackageServiceProvider::class,
// NewsletterServiceProvider::class, // remove this
//...
],
'aliases' => [
//...
// 'Comments' => CommentsFacade::class,
'Highlight' => SyntaxHighlighterFacade::class,
'SEO' => SEOFacade::class,
// 'Newsletter' => NewsletterFacade::class, // remove this
//...
],
Update Composer
Remove the unnecessary packages from the composer.json
file and then run:
composer update
This command will update the dependency tree and ensure that the packages are no longer part of your project.
Remove Database Tables/Migrations (if applicable)
If the plugins/providers added database tables or migrations, you might want to create new migrations to drop those tables or simply remove the migration files if they are no longer necessary.
Review Assets
Some packages might have published assets (JS, CSS, images, etc.) to your public directory. Ensure that you remove any assets related to the plugins/providers you’re no longer using.
Conclusion
Regularly reviewing and removing unnecessary plugins/providers can keep your Laravel application lean, maintainable, and performant. Always ensure to test your application after removing packages to ensure that no other parts of your application were dependent on them.
6. Optimize Middleware
Reduce the number of middlewares running on each request. Not every middleware needs to run on every request.
Middleware in Laravel provides a way to filter HTTP requests entering your application. However, having too many middlewares or poorly optimized middleware can slow down your application, especially if they perform computationally intensive tasks or make external API calls.
Here’s an example of optimizing middleware in Laravel:
Review Existing Middlewares
First, you need to understand what middlewares are currently active. You can do this by checking the $middleware
and $middlewareGroups
properties in your app/Http/Kernel.php
file.
Optimizing a Hypothetical Logging Middleware
Imagine you have a middleware that logs the user agent of every request to a database.
Before Optimization
public function handle($request, Closure $next)
{
DB::table('user_agents')->insert(['user_agent' => $request->header('User-Agent')]);
return $next($request);
}
The above middleware will make a database insert operation on every request, which can be inefficient and slow.
After Optimization
a. Use Queues: Instead of logging the user agent directly to the database, push it to a queue to process it in the background.
public function handle($request, Closure $next)
{
dispatch(new LogUserAgentJob($request->header('User-Agent')));
return $next($request);
}
b. Bulk Inserts: Instead of inserting records one by one, you can group multiple records and insert them all at once at regular intervals.
c. Caching: If you notice that the same user agents hit your application frequently, consider caching the user agent so that you don’t log duplicate entries continuously.
public function handle($request, Closure $next)
{
$userAgent = $request->header('User-Agent');
if (!Cache::has($userAgent)) {
dispatch(new LogUserAgentJob($userAgent));
Cache::put($userAgent, true, now()->addHours(1));
}
return $next($request);
}
Limit Global Middleware
Global middlewares run on every request, so they can have a substantial impact on performance. Only essential middleware, like request sanitization or CORS handling, should be global.
Defer Loading
If a middleware does some operations that aren’t needed immediately (like logging or updating some statistics), consider deferring these operations until after the response is sent to the user. Laravel provides the terminate
method in middlewares for such operations:
public function terminate($request, $response)
{
// Deferred operations here
}
Conclusion
Optimizing middleware is about ensuring that the operations inside them are efficient and only run when necessary. By reviewing and streamlining middleware logic, avoiding global middlewares when possible, and using features like queues and caching, you can improve the performance of your Laravel application. Always remember to profile and test your application to ensure that changes lead to actual performance improvements.
7. Profile your Application
Profiling your Laravel application means analyzing it to understand where performance bottlenecks are, how memory is being used, which queries take a long time, etc. This analysis helps in optimizing and improving the application’s performance.
Here’s how you can profile a Laravel application:
Laravel Debugbar
One of the most popular tools to profile a Laravel application is the Laravel Debugbar.
Installation
composer require barryvdh/laravel-debugbar --dev
Once installed, Debugbar will show a bar at the bottom of your web pages, which includes a lot of useful information:
- Number and time of executed queries.
- Route and controller details.
- Session data, request input, and more.
- Memory usage and execution time.
By using Laravel Debugbar, you can easily pinpoint which parts of your application might be causing slowdowns or other issues.
Laravel Telescope
Laravel Telescope is another powerful tool for debugging and profiling your Laravel applications. It provides an elegant dashboard for you to review logs, queued jobs, database queries, cache, etc.
Installation
composer require laravel/telescope
After installation, you can access the Telescope dashboard at /telescope
and gain insights into:
- Requests & Responses.
- Commands & Schedule.
- Database queries & Redis commands.
- Logs, Dumps, and more.
Blackfire.io:
Blackfire is a SaaS-based solution that offers deep insights into how your application behaves and consumes resources.
Setup:
- First, sign up on the Blackfire website.
- Install the Blackfire PHP Probe and the companion browser extension.
Once set up, Blackfire allows you to profile requests to your application and provides detailed performance metrics and recommendations.
Example
Suppose your application has a dashboard where users can view their latest activities and notifications. After deploying a new feature, users report that the dashboard has become noticeably slower.
By using the Laravel Debugbar, you notice:
- The page makes 50 separate database queries.
- One of the queries takes over 300ms to execute.
Using the details provided by Debugbar, you realize that:
- Multiple queries are running because of an N+1 problem related to loading user notifications.
- The slow 300ms query is because of an unindexed column in the database.
Based on these findings:
- You implement eager loading for user notifications to address the N+1 issue.
- You add a proper index to the database column that was causing the slow query.
After these changes, profiling the application again with Debugbar shows a significant reduction in queries and overall page load time.
Conclusion
Profiling is a continuous process, especially as the application grows and evolves. Regularly profiling and acting on the findings ensures that the application remains fast and efficient for end-users. Always ensure to profile your application in a realistic environment that mirrors production as closely as possible.
Key Takeaways
Optimizing a Laravel application requires a mix of understanding the application structure, using built-in Laravel features, and leveraging external tools. By following these ten techniques, you’ll significantly improve the speed and responsiveness of your Laravel application, providing a better experience for your users. Remember always to monitor your app’s performance and make necessary adjustments as it grows.