February 28, 2019

How I Start a New Laravel Project

I create a lot of new Laravel projects and each time I do I go through a series of steps to configure everything so that the project is just the way I like it. I created a Laravel Starter Template a while ago, but keeping it up to date with the latest Laravel releases is more work than I expected.

In order to ensure I never forget a step, and to help other developers who may want to incorporate some of these steps into their own project setup regime, these are the steps I go through.

Laravel Valet

Although not really applicable to this guide, I'm using Laravel Valet to serve my development projects. I'm not going to go through the setup of valet here as following the documentation should be easy enough.

Creating a Project

The first step in spinning up a new Laravel project is to create the project directory. To do this, I use the Laravel Installer. In the Terminal:

laravel new my-project

This creates the project structure, installs all of the required composer dependencies and sets the application key.

Move Models to Their Own Directory

By default, Laravel puts all models into the app/ directory in the project. Initially, there is only a User model available. Whilst this is probably okay for the vast majority of apps, I always find that during a long-running project I want to organise my models into subdirectories (to cater for things like Blog/Post.php and Blog/Category.php).

It's probably a contentious position, but I always move my Models to an app/Models directory at the start of a project.

To do this:

  1. Create the app/Models directory.
  2. Move the app/User.php model to the new app/Models directory and update the namespace to App\Models.
  3. Update the app/Http/Controller/Auth/RegisterController.php User import to App\Models\User.
  4. Update the config/auth.php user provider model to App\Models\User::class.
  5. Update the config/services.php Stripe model to App\Models\User::class.
  6. Update the database/factories/UserFactory.php factory to App\Models\User::class.

This will move the User model to the newly created app/Models directory and update all references to it throughout the project scaffolding.

Base Model

Eloquent tries to protect you against mass assignment by default. This is definitely a good thing if there is a possibility that you or someone on your team might pass unexpected request parameters to your model properties.

In my projects, I'm pretty confident that unguarding my models and allowing all attributes to be mass-assigned is okay, but I don't want to do this on a per-model basis. So, I create a base app/Models/Model.php file like this:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model as Eloquent;

class Model extends Eloquent
{
    protected $guarded = [];
}

When I create new models, I extend this model instead of the base Eloquent-provided model.

Set Up PHPUnit

When testing with PHPUnit, I want my tests to run as fast as possible. I allow my tests to interact with the database, but I don't want them to touch a real MySQL database. To enable this I set PHPUnit to use an in-memory SQLite database.

I add two environment variables to my phpunit.xml file:

<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>

This automatically switches Laravel to use the default sqlite database connection when running tests, and sets the database to an in-memory one.

There are times when this approach doesn't work due to differences between SQLite and MySQL (mostly due to migrations), but the frequency of that happening is so small for me that I always start out my testing setup this way and switch to another method only if there is no other choice.

Install Tailwind CSS

Ever since Adam Wathan released Tailwind CSS, I've used it on every project. It's made my designs better, and, coupled with Refactoring UI I'm now a lot more confident of producing a design that looks acceptable.

To install Tailwind CSS, we first need to pull it in using npm:

npm install tailwindcss --save-dev

We then create a Tailwind CSS configuration file:

npx tailwind init

Then, we need to add Tailwind's preflight and utilities into our app.scss file, replacing what is currently there:

/**
 * This injects Tailwind's base styles, which is a combination of
 * Normalize.css and some additional base styles.
 *
 * You can see the styles here:
 * https://github.com/tailwindcss/tailwindcss/blob/master/css/preflight.css
 *
 * If using `postcss-import`, use this import instead:
 *
 * @import "tailwindcss/preflight";
 */
@tailwind preflight;

/**
 * This injects any component classes registered by plugins.
 *
 * If using `postcss-import`, use this import instead:
 *
 * @import "tailwindcss/components";
 */
@tailwind components;

/**
 * Here you would add any of your custom component classes; stuff that you'd
 * want loaded *before* the utilities so that the utilities could still
 * override them.
 *
 * Example:
 *
 * .btn { ... }
 * .form-input { ... }
 *
 * Or if using a preprocessor or `postcss-import`:
 *
 * @import "components/buttons";
 * @import "components/forms";
 */

/**
 * This injects all of Tailwind's utility classes, generated based on your
 * config file.
 *
 * If using `postcss-import`, use this import instead:
 *
 * @import "tailwindcss/utilities";
 */
@tailwind utilities;

/**
 * Here you would add any custom utilities you need that don't come out of the
 * box with Tailwind.
 *
 * Example :
 *
 * .bg-pattern-graph-paper { ... }
 * .skew-45 { ... }
 *
 * Or if using a preprocessor or `postcss-import`:
 *
 * @import "utilities/background-patterns";
 * @import "utilities/skew-transforms";
 */

Because we're no longer going to be using it, we can safely delete the _variables.scss file in the resources/sass/ folder.

We now need to process our styles with Tailwind. To do this, we need to edit our webpack.mix.js file to look as follows:

const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .options({
      processCssUrls: false,
      postCss: [ tailwindcss('./tailwind.js') ],
   });

Now, processing our CSS is as easy as running the default npm scripts for development or production:

# For development
npm run dev

# For production
npm run prod

Tailwind CSS is up and running and we now have utility-first CSS goodness at our fingertips.

Set up PurgeCSS

By default, the CSS file that Tailwind produces includes every utility provided, even if it's not being used in the project. In order to tame the CSS file, the Laravel community have largely settled on using PurgeCSS to remove unused CSS.

In order to make this super easy, I always reach for the Spatie PurgeCSS Package which enables this in two lines of code.

To install the package:

npm install laravel-mix-purgecss --save-dev

I only install this in the dev environment as I commit my compiled CSS ready for production deployment to my projects. I also only purge my CSS when I compile for production so that whilst in development I have all of Tailwind's utilities available to me.

To enable this, I update my webpack.mix.js file as follows:

const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss');

require('laravel-mix-purgecss');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .options({
      processCssUrls: false,
      postCss: [ tailwindcss('./tailwind.js') ],
   });

if (mix.inProduction()) {
   mix.purgeCss();
}

By doing this, when I run npm run dev or npm run watch I can guarantee that all of Tailwind's utilities are available to me. Once I'm happy with the result I can run npm run prod and get an optimised CSS file that only includes the utilities that I'm actually using.

Asset Versioning and Vendor Extraction

When making changes to my CSS and JS files I want to ensure that browsers will use the new files immediately, without having to wait for a browser cache to expire or making my users do a forced refresh.

I also like to extract my vendor-specific libraries so that my users only need to download the changes to my CSS and JS, rather than downloading vendor libraries such as Vue.js again.

Laravel Mix provides out-of-the-box functionality to do this. To set it up, I update my webpack.mix.js file again:

const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss');

require('laravel-mix-purgecss');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .options({
      processCssUrls: false,
      postCss: [ tailwindcss('./tailwind.js') ],
   });

if (mix.inProduction()) {
   mix.purgeCss()
      .extract()
      .version();
}

This versions my assets and extracts vendor libraries when they are compiled for production. By doing this, I end up with to following files after compiling for production:

Further explanation about Asset Versioning can be found in the documentation.

In addition to this, I also remove some vendor libraries that I never use: Bootstrap, jQuery and Popper.js.

To do this, I update my package.json file:

{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "npm run development -- --watch",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "devDependencies": {
        "axios": "^0.18",
        "cross-env": "^5.1",
        "laravel-mix": "^4.0.7",
        "laravel-mix-purgecss": "^4.1.0",
        "lodash": "^4.17.5",
        "resolve-url-loader": "^2.3.1",
        "sass": "^1.15.2",
        "sass-loader": "^7.1.0",
        "tailwindcss": "^0.7.4",
        "vue": "^2.5.17",
        "vue-template-compiler": "^2.6.7"
    }
}

And also update my resources/js/bootstrap.js file:


window._ = require('lodash');

/**
 * We'll load the axios HTTP library which allows us to easily issue requests
 * to our Laravel back-end. This library automatically handles sending the
 * CSRF token as a header based on the value of the "XSRF" token cookie.
 */

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

/**
 * Next we will register the CSRF Token as a common header with Axios so that
 * all outgoing HTTP requests automatically have it attached. This is just
 * a simple convenience so we don't have to attach every token manually.
 */

let token = document.head.querySelector('meta[name="csrf-token"]');

if (token) {
    window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
    console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}

/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that are broadcast by Laravel. Echo and event broadcasting
 * allows your team to easily build robust real-time web applications.
 */

// import Echo from 'laravel-echo'

// window.Pusher = require('pusher-js');

// window.Echo = new Echo({
//     broadcaster: 'pusher',
//     key: process.env.MIX_PUSHER_APP_KEY,
//     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
//     encrypted: true
// });

Usually, by this point, I've already used npm install and so, I nuke my node_modules directory and use npm install again to remove the cruft:

rm -fr node_modules/
npm install

By compiling my assets for production again, the vendor.js file shrinks from 341kb to 177kb.

Wrapping It Up

All told, these changes take me around 5 minutes to perform at the start of a project. Although some of them are personal preference, I feel that they make my projects slightly more organised and give me the tools that I need to get moving quickly and give me a flexible enough foundation to build any project on.

I also have a basic Blade template structure that I use in each project, but that's a topic for another blog post!