Skip to main content
Laravel, shipping fast.

Automate testing and deployment:

# .github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_DATABASE: api_test
          MYSQL_PASSWORD: password
          MYSQL_ROOT_PASSWORD: root
        options: >-
          --health-cmd="mysqladmin ping"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=3
        ports:
          - 3306:3306

    steps:
      - uses: actions/checkout@v2

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: "8.4"
          extensions: mysql, sqlite, pdo_sqlite

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Run tests
        run: php artisan test --coverage

      - name: Run PHPStan
        run: ./vendor/bin/phpstan analyse

      - name: Format check
        run: ./vendor/bin/pint --test

Docker Deployment

# Dockerfile
FROM php:8.4-fpm-alpine

RUN apk add --no-cache \
    mysql-client \
    postgresql-client \
    redis

WORKDIR /app

COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader

COPY . .

RUN php artisan optimize

EXPOSE 9000

CMD ["php-fpm"]
# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8000:9000"
    environment:
      - DB_HOST=mysql
      - CACHE_DRIVER=redis
      - QUEUE_CONNECTION=redis
    depends_on:
      - mysql
      - redis
      - queue

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: api_app
      MYSQL_ROOT_PASSWORD: secret
    ports:
      - "3306:3306"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  queue:
    build: .
    command: php artisan queue:work
    depends_on:
      - mysql
      - redis

Database Migrations

Version control your database:

php artisan make:migration create_bearers_table

class CreateBearersTable extends Migration
{
    public function up(): void
    {
        Schema::create('bearers', function (Blueprint $table) {
            $table->id();
            $table->string('token', 64)->unique();
            $table->string('name');
            $table->text('description')->nullable();
            $table->timestamp('expires_at');
            $table->json('scopes')->nullable();
            $table->boolean('revoked')->default(false);
            $table->timestamps();

            $table->index('token');
            $table->index('expires_at');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('bearers');
    }
}

// Deploy safely
php artisan migrate --force

Monitoring & Alerting

# Monitor logs in real-time
tail -f storage/logs/laravel.log | grep -i error

# Check queue length
php artisan queue:failed

# View active processes
php artisan tinker
queue()->failed()->count()

Scaling Strategies

  1. Horizontal Scaling: Run multiple API instances behind load balancer
  2. Caching: Cache expensive computations (Redis)
  3. Database Optimization: Index columns, use replicas
  4. Queue Workers: Scale background job processing independently
  5. CDN: Cache static responses

Chapter 12 Summary

Advanced features come later. Don't add them until you need them.

Examples:

  • Webhooks: Notify external systems when things happen
  • Batch operations: Let clients do multiple things in one request
  • Search: Full-text search across resources
  • Subscriptions: Real-time updates via WebSockets
  • Rate limiting by plan: Different limits for different users

These are the features that make your API feel enterprise-grade. But don't let them distract from the fundamentals. Get the basics right first.