Starting a Fresh Laravel Application
January 5th, 2025
Starting a new project is always an envigorating prospect; so much potential and possiblity, the very world is at your fingertips. It always feels good to start something new. When working on a web projects I prefer to use Laravel, and this article describes how I prepare a brand new project repository.
I have created a dedicated git repo where all of the examples in this article can be referenced. Feel free to clone that repo to start your own project if you don't want to go through all these steps on your own.
- Create the new project
- Set up the development environment with Docker
- Using Vite for asset management
- Local Database
- Tests and Code Coverage
- Static Analysis
- Code Formatting
- Ops Helper Script
#Create the new project
Step 1: Use composer to pull down a fresh copy of the Laravel framework. I prefer to use a utility container for this as the docker images for the project don't yet exist.
php8.4 composer create-project laravel/laravel starter cd starter
In this case 'starter' is the name of the folder where the code will be placed.
The stack we will be using includes:
#Set up the development environment with Docker
Laravel Sail is an excellent tool for managing development environments with Docker, and it is one of the reccomended ways of getting up and running with a new Laravel application. However, I prefer to manage my Docker environment directly using Docker Compose.
We will start with three different services; one each for PHP, PostgreSQL and Node.
#PHP
The full Dockerfile can be seen here. The base image is php8.4-alpine
; most of the work done in the Dockerfile relates to user permissions and APK dependencies. There are a view items to note however:
The default PHP memory limit is increased to 1096M:
# Increase the default memory limit
RUN echo 'memory_limit = 1096M' >> /usr/local/etc/php/conf.d/docker-php-memlimit.ini;
Composer is installed for PHP package management:
# Install Composer RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer # Add Composer to the PATH ENV PATH="$PATH:/usr/local/bin:/home/${NAME}/.composer/vendor/bin" # Ensure ~/.composer belongs to user RUN mkdir /home/${NAME}/.composer && chown -R ${NAME}:${NAME} /home/${NAME}
Xdebug is installed for step-debugging and code coverage reporting. There is a separate xdebug.ini file in the repo containing the xdebug configuration.
# Install XDebug extension for code coverage support RUN pecl install xdebug && docker-php-ext-enable xdebug COPY xdebug.ini /usr/local/etc/php/conf.d
Finally, the container is configured to run php artisan serve
automatically when it spins up:
# Serve the web application through the PHP dev server
CMD ["php", "artisan", "serve", "--host", "0.0.0.0"]
#PostgreSQL
The full PostgreSQL docker image can be seen here. Here are a few things to note:
A psqlrc file has been included to configure the PostgreSQL command line tool:
# Copy over the .psqlrc configuration file
COPY --chown=postgres:postgres .psqlrc /var/lib/postgresql/
An init.sql file has been included to scaffold the new databases:
# Copy our init.sql into the container
# This will only be run if the persistence volume is empty
COPY --chown=postgres:postgres init.sql /docker-entrypoint-initdb.d/
When the PostgreSQL container spins up it checks to see if there are any known databases present. If not, all of the scripts in the docker-entrypoint-initdb.d
folder will be run. In our case, the init.sql
file sets up two new databases:
CREATE USER developer; ALTER USER developer WITH PASSWORD 'secret'; CREATE DATABASE app_dev OWNER developer; GRANT ALL PRIVILEGES ON DATABASE app_dev TO developer; CREATE DATABASE app_test OWNER developer; GRANT ALL PRIVILEGES ON DATABASE app_test TO developer;
#Node
The node image is very simple. The only modificatioin we make is configuring the image to run yarn dev
automatically on startup:
CMD [ "yarn", "dev" ]
#Building the Environment
The docker-compose.yml
file is relatively straight forward:
services: php: build: context: docker/php volumes: - ./:/var/www/:cached ports: - 80:8000 postgres: build: context: docker/postgres ports: - "54320:5432" environment: - EDITOR=vim - POSTGRES_PASSWORD=secret volumes: - pg-data:/var/lib/postgresql/data - ./docker/postgres/share:/var/lib/postgresql/share node: build: context: docker/node volumes: - ./:/var/www:cached ports: - 5173:5173 mailpit: image: "axllent/mailpit:latest" ports: - "${FORWARD_MAILPIT_PORT:-1025}:1025" - "${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025" volumes: pg-data:
I have included a MailPit service as well for testing email locally. With this docker-compose file in place, you can now build the images:
docker compose build
To be able to access the site from your browser you may want to add an entry to your local hosts file:
127.0.0.1 starter.test
You can spin the containers up now using docker compose up -d
, but we don't quite have all of our dependencies in place yet.
#Using Vite for asset management
We can now install our node dependencies using the node image. I prefer to use Yarn for package management, but NPM works just as well here.
docker-compose run --rm node yarn
There is one small change we need to make to the Vite config to get it working in our new docker environment. We will add a "server" block telling vite to use "0.0.0.0" as its default host:
server: { host: "0.0.0.0", hmr: { host: "localhost", }, },
With this change in place we can now restart our containers and then view the new site in a web browser: http://starter.test. Laravel ships with Tailwind CSS by default, which also happens to be my preferred CSS tool. The default tailwind.config.js file should be a sufficient starting place.
#Local Database
We need to tell our new application to use Postgres instead of Sqlite by default. This can be done in our environment file:
DB_CONNECTION=pgsql DB_HOST=postgres DB_PORT=5432 DB_DATABASE=app_dev DB_USERNAME=developer DB_PASSWORD=secret
The 'host' value here is the name of the postgres docker service; this is shorthand used by the docker network for service resolution.
Our application database was created automatically when the container spun up for the first time. We can now run our migrations:
docker compose exec php php artisan migrate:fresh
A separate database was also created for the testing environment. We will need to update our phpunit.xml file with those details:
<php> <!-- other stuff --> <env name="DB_CONNECTION" value="pgsql"/> <env name="DB_HOST" value="postgres"/> <env name="DB_DATABASE" value="app_dev"/> <env name="DB_USERNAME" value="developer"/> <env name="DB_SECRET" value="secret"/> <!-- other stuff --> </php>
#Tests and Code Coverage
With the xdebug extension installed we can configure PHPUnit to generate code coverage reports for us. We do this by adding a <coverage>
block to the phpunit.xml file:
<coverage> <report> <html outputDirectory=".coverage" lowUpperBound="50" highLowerBound="90"/> </report> </coverage>
The output directory can be whatever you would like; just make sure to add it to your git ignore file.
Composer offers the ability to run scripts for us, similar to NPM and Yarn. I prefer to use this method for running the test suite. To do this we will add a new entry in the scripts
section of the composer.json file:
"scripts": { // ... "test": "vendor/bin/phpunit" },
#Static Analysis
PHPStan is my preferred static analysis tool; and the Larastan project provides an excellent way to integrate static analysis into a Laravel application.
We will first add Larastan as a composer dependency:
docker compose exec php composer require --dev "larastan/larastan:^3.0"
Adding a phpstan.neon file to the root of the project allows us to configure PHPStan for this application. I personally strive for level 8 in all of my projects.
We can again use a composer script for running static analysis:
"scripts": { // ... "phpstan": "phpstan analyze" },
#Code Formatting
Laravel ships with a tool called Pint that performs PHP code formatting. Pint is a wrapper for the excellent friendsofphp/php-cs-fixer
tool. We will set it up for this project using the "per" standard, which is the latest successor to the "psr-12" standard. We will also use composer to run our formatting for us:
"scripts": { // ... "format": "pint --preset per", },
I like to use Prettier for formatting JavaScript and Blade files. It can also automatically sort tailwind classes for us which is a nice bonus. Matt Stauffer has an excellent post on his blog that details setting up Prettier for Laravel. The VSCode Prettier plugin will automatically run this formatting for us. We don't necessarily need to add a script to run it manually but we could by updating the "scripts" block in the package.json file:
"scripts": { // ... "format": "prettier . --write" },
#Ops Helper Script
To streamline local development tasks I like to set up an ops helper script. This is an idea that I borrowed from Chris Fidao and his Shipping Docker course. It becomes super helpful when combined with a bash alias:
starter() { cd /full/path/to/project ./ops.sh ${*:-ps} cd $OLDPWD }
You can call the function whatever you would like. With this alias in place you can now easily run development tasks without having to spell out the full docker command each time:
starter artisan make:model Team -mfs # Make a new model with a migration, factory and seeder starter composer require [package] # Add a PHP package with Composer starter composer format # Run Laravel Pint to format PHP code starter composer phpstan # Run PHPStan for static analysis starter composer test # Run the test suite starter psql app_dev # Drop into the postgres cli for the app_dev database starter yarn add [package] # Add a node dependency with Yarn starter yarn format # Run a yarn script
This script is easily extendible if you add more services to the docker environment.