Skip to content

Set up a new Laravel project

This guide shows you how to initialise a new Laravel project from scratch, adhering to our company standards for Git workflows, linting, formatting, and CI setup.


Follow the official Laravel instructions for setting up a Laravel project with a starter kit. Ensure you’re on the latest version of the starter kit (remove and re-install if necessary).

Company preferences:

  • React frontend
  • Built-in auth
  • No teams support (unless wanted)
  • Pest for testing
  • Laravel Boost (why not)
  1. Go to GitHub and create a new repository under our organisation.
  2. Ensure the repo is Private and empty. No need to add a license.
  3. Push the starter kit code to the empty repo.
Terminal window
cd new-project
git add -A
git commit -m 'initial commit'
git remote add origin git@github.com:novatura/new-project.git
git push -u origin main

Add the repository as a target for our GitHub org’s branch rulesets in order to apply standard branch protection rules and other security features such as requiring signed commits. Find them here.

For a waterfall project (pre-production), create the following branches:

  • main
  • staging
  • dev
  • bug-fixes
  • d1

The worflow consists of, each deliverable gets a dX branch. Each new deliverable branch is created from it’s predecessor, so d2 would branch from d1.

For other projects (agile, takeover), only the following:

  • main
  • staging
  • dev
  • bug-fixes

Once a project is in production, this is the workflow:

  1. Use feature branches (using a Linear issue as the branch name, i.e. DEL-123-initialise-project) to build features.
  2. Open feature branches PRs into dev. Keep PRs short and targeted. This way, AI and human reviewers can actually review.
  3. At the end of sprint cadence / deliverable, open a PR from dev into staging. Use AI code review to catch any final bugs. Diffs may be too large to review manually at this point, so it’s important that feature branches were used properly.
  4. Once testing has finished on staging, and all CI checks have passed, open a PR from staging into main, which will require approval.
  5. For bug fixes, use the bug-fixes branch and open PRs into main for AI and human code review. Keep this branch up to date with main.

Configure Biome for the project.

Terminal window
bun add -D -E @biomejs/biome
bunx --bun @biomejs/biome init

Pint and PHPStan should be installed from the Laravel starter kit.

composer types:check && types:check

Add these workflows to .github/workflows that runs on dev, staging and main.

.github/workflow/biome.yml
name: Frontend Code Quality
on:
push:
branches:
- main
- staging
- dev
jobs:
quality:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome
run: biome ci .
.github/workflow/pint.yml
name: Backend Code Quality
on:
push:
branches:
- main
- staging
- dev
jobs:
quality:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.5']
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v2
coverage: xdebug
version: latest
- name: Install Composer Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Run Pint
run: pint

TODO: Verify these work and maybe configure automated pushing of safe fixes?

Ensure you have Pest set up and a version controlled .env.ci file exists. Do not keep real secrets or credentials in this file.

Add a testing workflow to .github/workflows that runs on staging and main. The below is just a guideline, as testing setup may vary project to project.

tests.yml
name: Run Tests
on:
push:
branches:
- main
pull_request:
branches:
- staging
- main
workflow_dispatch:
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.5']
services:
mysql:
image: mysql:8.0
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: password
# Match phpunit.xml DB_DATABASE so migrations run against this schema
MYSQL_DATABASE: testing
# Without this, only root exists; local/Sail uses sail and tests inherit that username
MYSQL_USER: sail
MYSQL_PASSWORD: password
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis:alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v2
coverage: xdebug
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install Bun Dependencies
run: bun install --frozen-lockfile
- name: Install Composer Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Copy Environment File
run: cp .env.example .env
- name: Generate Application Key
run: php artisan key:generate
- name: Build Assets
run: bun run build
- name: Setup network hosts
run: |
echo "127.0.0.1 localhost" | sudo tee -a /etc/hosts
echo "127.0.0.1 mysql" | sudo tee -a /etc/hosts
echo "127.0.0.1 redis" | sudo tee -a /etc/hosts
- name: Tests
env:
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: testing
DB_USERNAME: sail
DB_PASSWORD: password
REDIS_HOST: redis
run: ./vendor/bin/pest

Add the following instructions for Copilot:

.github/copilot-instructions.md
# React/Laravel Code Review Guidelines
Use the following guidelines for code review. The main goal is clean and maintainable code. For every PR you review, generate a short changelog using the following example template.
<format>
# Title
Brief summary of main features.
## Changelog:
1. Feature: Added new integration for ___
2. Refactor: Updated ___ page
3. Fix: Broken layout on ___ page
4. Fix: Bug where ___
</format>
## Code Style
Ignore formatting issues and comments. These are handled by CI formatters.
## Query Optimisation
- Ensure that batch queries are employed where sensible, rather than performing queries inside of loops.
- If the number of database queries can be reduced, suggest an appropriate refactor.
## Architecture
- Ensure that single responsibility principle is respected.
- Ensure separation of concerns is considered.
## Testing
- Advocate for integration/feature tests where appropriate, with a small and well curated set of end to end tests for core features is a good balance.
- Where services classes and repositories are used, advise on testability by looking at whether their dependencies can be mocked through appropriate techniques such as dependency injection.
- Ensure no tests are going to call third party APIs that might incur fees or take up lots of CI time.
".github/instructions/php.instructions.md
---
applyTo: "**/*.php"
---
# PHP / Laravel Code Review Guideline
This file defines our PHP and Laravel conventions for code review.
## Architecture
- Avoid tight coupling between systems. Use controllers to handle requests, repositories to handle data fetching.
## Tests
- Ensure tests are deterministic.
.github/instructions/react.instructions.md
---
applyTo: "**/*.tsx"
---
# React Code Review Guideline
This file defines our React conventions for code review.
## Components
- Ensure useEffects are only used where necessary or sensible, to avoid unnecessary re-renders.
- Make sure components are Instance-Proof
- Make sure components are Concurrent-Proof
- Make sure components are Portal-Proof
- Avoid tight coupling between components, use React Context where appropriate to abstract logic away from UI to keep code clean.
## Robustness
- Make sure all relevant feedback is accounted for, such as toast popups for when operations fail, loading UI or error messages in forms

Add this cron job that cleans up our artifact storage.

.github/workflows/clean-artifacts.yml
name: Cleanup Artifacts
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # Daily
permissions:
actions: write
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Delete old artifacts
uses: actions/github-script@v7
with:
script: |
const artifacts = await github.rest.actions.listArtifactsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
for (const artifact of artifacts.data.artifacts) {
console.log(`Deleting ${artifact.name} (${artifact.size_in_bytes} bytes)`);
await github.rest.actions.deleteArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: artifact.id
});
}