August 16, 2018
Over the past five years, I’ve been involved in lots of Laravel projects of varying size and complexity. Some of those projects have come to me as greenfield work. Others have been existing releases that I’ve been brought in to update, optimize, or manage in the wake of a staff member that’s no longer involved.
This experience has led me to gather my best practices when it comes to developing in Laravel. I’m happy to share them with you here. A lot of these are best practices for any developer to internalize no matter in what system you choose to develop.
Design your database schemas around how they will be used in practice. This means attributes that will be used in join queries most often must have DB indexes. Related tables should have foreign key constraints. This is something that is often overlooked in the development phase because you don’t see performance degradation until your tables have thousands of records. One of my clients has a few tables with >10 million records. Adding indexes based only on how the data are queried saved us as much as 70% execution time on some requests.
Any time-intensive actions or actions that will be repeated by the same users that frequently query the database should be cached. This means that the action is performed once, written to a static data store (usually the file system or Redis server) and then fetched quickly for subsequent uses. For example, if your project has a rather extensive navigation (several levels with many different nav items) that are recursively pulled from a database, you should perform that DB lookup once and cache the HTML result for all users. Then you just expire the cache when you add a new page or change the menu structure to force the cache to be built again for the next user who’s accessing the site.
Cache data tied to a specific user. If the user’s account includes extensive purchase history or invoice history, you can perform that action once and then pull the data from the cache for subsequent visits to the account page, for example.
Trigger early cache expiration. Laravel caches always have a unique key (e.g., "main_nav" or "invoices_user_78773") and an expiration time in minutes from creation (e.g., 60 minutes, 1440 minutes, ...). You can trigger early cache expiration based on relevant events like a user’s creation of a new invoice or a new page being added to the nav. That way users never see old cache content.
Caches can sometimes be stored in multiple places or replicated across servers. It can be hard to make sure the right cache is invalidated at the right time for the right user. Laravel makes that easy.
“ There are only two hard things in Computer Science: cache invalidation and naming things. ” — Phil Karlton
TDD describes the software development process where you divide your project into granular, logical pieces. You then write unit or functional tests that verify that the code will produce the expected output (unit tests) or outcome (functional tests) for a given set of known inputs. When you write the actual code, it will need to pass these tests. In production, if you’ve followed the TDD process, you will have a suite of tests that can be executed before each deployment. This ensures everything is still working and you have not inadvertently introduced regression bugs.
For medium and large projects, it’s not practical to test every single feature and function manually. The test suite can be run and accomplished in seconds vs. the hours or days it would take you to manually verify.
Employing TDD does have an upfront cost, but it always pays off in the long run in time saved during testing, developer confidence, and user satisfaction.
Perhaps this goes without saying, but we’ve seen some ugly code bases. Sloppy code makes it near impossible to understand and debug.
Your user’s internet connections are now faster than ever, but that doesn’t negate the fact that your source needs to be reduced to the smallest possible bundle size. Users notice even a 100ms lag.