# PHP CRUD Rest API, using Laravel, Composer, Postgres, Docker and Docker Compose

Let's create a CRUD Rest API in PHP, using:

* Laravel (PHP framework)
    
* Composer (PHP package manager)
    
* Postgres (database)
    
* Docker
    
* Docker Compose
    

Mind the similar names!

⚠️ "Composer" is a package manager for PHP. It is used to install and manage dependencies in PHP projects. It is similar to NPM in Node.js projects.

⚠️ "Compose" is a tool for defining and running multi-container Docker applications. It is similar to Docker Compose in Node.js projects.

Video version:

<iframe width="905" height="510" src="https://www.youtube.com/embed/cdlHJeHVFW4"></iframe>

All the code is available in the [GitHub repository](https://youtube.com/live/cdlHJeHVFW4) (link in the video description)

---

## 🏁 Intro

Here is a schema of the architecture of the application we are going to create:

[![PHP CRUD Rest API with Laravel, Postgres, Docker and Docker Compose. Postman and Tableplus to test it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxiumyehdgaid6qu4r30.png align="left")](https://youtube.com/live/cdlHJeHVFW4)

We will create 5 endpoints for basic CRUD operations:

* Create
    
* Read all
    
* Read one
    
* Update
    
* Delete
    

### 👣 Steps

We will go with a step-by-step guide, so you can follow along.

Here are the steps:

1. Check the prerequisites
    
2. Create a new Laravel project
    
3. Code the application
    
4. Run the Postgres database with Docker
    
5. Build and run the application with Docker Compose
    
6. Test the application with Postman and Tableplus
    

---

## 💡 Prerequisites

* php installed (version 8+ )
    
* composer installed (version 2.5+ )
    
* docker installed (version 20.10+ )
    
* \[optional\] VS Code installed (or any IDE you prefer)
    
* \[optional\] Laravel cli
    
* \[optional\] Postman or any API test tool
    
* \[optional\] Tableplus or any database client
    

---

## 🚀 Create a new Laravel project

To create a new Laravel project, we will use the Laravel CLI.

```plaintext
laravel new laravel-crud-api
```

This will take a while, but the final output should be something like this:

[![Laravel project created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v334r9l6exq970pd8npn.png align="left")](https://youtube.com/live/cdlHJeHVFW4)

Now step into the project folder:

```plaintext
cd laravel-crud-api
```

and open the project with your favorite IDE. If you use VS Code, you can use the following command:

```plaintext
code .
```

This will open the project, open a terminal and run the following command:

```plaintext
php artisan serve
```

and you should have something like this:

[![Laravel app running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m4q1dgvvy7hbb7u3vu1.png align="left")](https://youtube.com/live/cdlHJeHVFW4)

You can stop the server with `Ctrl + C`.

Now we are ready to start coding.

---

## 👩‍💻 Code the application

There are two steps to code the application:

* Configure the database connection
    
* Create the Player, PlayerController, and Player routes
    

### 🔗 Configure the database connection

We will use Postgres as our database. To configure the database connection, we will use the `.env` file.

Open the .env file and replace lines 11-16 (DB\_CONNECTION, DB\_HOST, DB\_PORT, DB\_DATABASE, DB\_USERNAME, DB\_PASSWORD) with the following (it should be `mysql` by default).

```plaintext
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=postgres
DB_USERNAME=postgres
DB_PASSWORD=postgres
```

Your final .env file should look like this:

```plaintext
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:EB/Rhl0udascxY9GVkIeHpoZT5LtivQlZpVvQp850QQ=
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=postgres
DB_USERNAME=postgres
DB_PASSWORD=postgres

BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=127.0.0.1

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1

VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
```

⚠️ Note: note that instead of an ip address, we use the name of the service (db) as the host. This is because we will use Docker Compose to run the application and the database. This is how Docker knows how to connect the two services (of course they should be in the same network).

[![.env file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m4q1dgvvy7hbb7u3vu1.png align="left")](https://youtube.com/live/cdlHJeHVFW4)

### 📁 Create the resource structure

We will create a `Player` resource. This resource will have the following fields:

* id (autoincremented)
    
* name (string)
    
* email (string)
    

```plaintext
php artisan make:model Player -m
```

This created a Player.php file in App/Models and a create\_players\_table.php file in database/migrations.

Open the `Player.php` file and replace it with the following:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Player extends Model
{
    use HasFactory;

    //add name and email to fillable
    protected $fillable = ['name', 'email'];
}
```

Open the `create_players_table.php` file in the database/migrations folder and replace it with the following:

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('players', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('players');
    }
};
```

Now create a file called PlayerController.php in the App/Http/Controllers folder and add the following:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Player;
use Illuminate\Http\Request;

class PlayerController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        //get all players
        $players = Player::all();
        //return JSON response with the players
        return response()->json($players);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'name' => 'required|string',
            'email' => 'required|string',
        ]);

        $player = Player::create($validatedData);

        return response()->json($player, 201);
    }

    /**
     * Display the specified resource.
     */
    public function show(Player $player)
    {
        // return JSON response with the player
        return response()->json($player);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, Player $player)
    {
        $validatedData = $request->validate([
            'name' => 'required|string',
            'email' => 'required|string',
        ]);

        $player->update($validatedData);

        return response()->json($player, 200);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Player $player)
    {
        $player->delete();

        return response()->json(null, 204);
    }
}
```

Last, open the routes/api.php file and add the following at the top of the file:

```php
use App\Http\Controllers\PlayerController;
...
```

And this at the bottom of the file:

```php
...
Route::get('/players', [PlayerController::class, 'index']);
Route::post('/players', [PlayerController::class, 'store']);
Route::get('/players/{player}', [PlayerController::class, 'show']);
Route::put('/players/{player}', [PlayerController::class, 'update']);
Route::delete('/players/{player}', [PlayerController::class, 'destroy']);
```

The api.php file should look like this:

```php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PlayerController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::get('/players', [PlayerController::class, 'index']);
Route::post('/players', [PlayerController::class, 'store']);
Route::get('/players/{player}', [PlayerController::class, 'show']);
Route::put('/players/{player}', [PlayerController::class, 'update']);
Route::delete('/players/{player}', [PlayerController::class, 'destroy']);
```

---

## 🐳 Dockerization

Now let's dockerize the application. We will use docker-compose to run the application.

We will create a Dockerfile and a docker-compose.yml file.

### 🐋 Dockerfile

Create a new file called `Dockerfile` in the root of the project.

Add the following content:

```plaintext
FROM php:8.1

RUN apt-get update && apt-get install -y \
    libpq-dev \
    && docker-php-ext-install pdo pdo_pgsql

WORKDIR /var/www/html

COPY . .

RUN chown -R www-data:www-data \
    /var/www/html/storage \
    /var/www/html/bootstrap/cache

CMD php artisan serve --host=0.0.0.0 --port=8000
```

Explanation of the Dockerfile:

* `FROM php:8.1`: this is the base image that we will use. We will use the official php image with version 8.1.
    
* `RUN apt-get update && apt-get install -y \`: this is the command that will be executed when the image is built. We will update the apt package manager and install the libpq-dev package.
    
* `RUN docker-php-ext-install pdo pdo_pgsql`: this is the command that will be executed when the image is built. We will install the pdo and pdo\_pgsql extensions.
    
* `WORKDIR /var/www/html`: this is the working directory of the container. All the commands will be executed from this directory.
    
* `COPY . .`: this is the command that will be executed when the image is built. We will copy all the files from the current directory to the container's working directory.
    
* `RUN chown -R www-data:www-data \`: this is the command that will be executed when the image is built. We will change the owner of the storage and bootstrap/cache directories to www-data.
    
* `CMD php artisan serve --host=8000`: this is the command that will be executed when the container is started. We will start the php artisan serve command.
    

### 🐙 docker-compose.yml

Let's create the `docker-compose.yml` file at the root of the project.

Add the following content:

```yaml
version: '3'

services:
  laravelapp:
    container_name: laravelapp
    image: francescoxx/laravelapp:1.0.0
    build: . 
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - db

  db:
    container_name: db
    image: postgres:12
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=postgres
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data: {}
```

Explanation of the docker-compose.yml file:

* `version: '3'`: this is the version of the docker-compose file.
    
* `services:`: this is the section where we will define the services that we want to run.
    
* `laravelapp:`: this is the name of the service.
    
* `container_name: laravelapp`: this is the name of the container.
    
* `image: francescoxx/laravelapp:1.0.0`: this is the name of the image that we will use. We will use the image that we created in the previous step. Replace `francescoxx` with your DockerHub username
    
* `build: .`: this is the path of the Dockerfile. We will use the Dockerfile that we created in the last step.
    
* `ports:`: this is the section where we will define the ports that we want to expose.
    
* `- "8000:8000"`: this is the port that we want to expose. We will expose the port 8000 of the container to the port 8000 of the host.
    
* `env_file:`: this is the section where we will define the environment variables that we want to use.
    
* `- .env`: this is the path of the .env file. We will use the .env file that we created in the previous step.
    
* `depends_on:`: this is the section where we will define the services that we want to run before this one.
    
* `- db`: this is the name of the service that we want to run before this one.
    
* `db:`: this is the name of the service.
    
* `container_name: db`: this is the name of the container.
    
* `image: postgres:12`: this is the name of the image that we will use. We will use the official postgres image with version 12.
    
* `ports:`: this is the section where we will define the ports that we want to expose.
    
* `- "5432:5432"`: this is the port that we want to expose. We will expose the port 5432 of the container to the port 5432 of the host.
    
* `environment:`: this is the section where we will define the environment variables that we want to use.
    

Now it's time to build the image and run the services (containers)

---

## Build and run the project

Now we can build and run the project.

## 💽 Run the Postgres database

First, we need to run the Postgres database.

```bash
docker compose up -d db
```

To check if it's running, you can use the following command:

```plaintext
docker compose logs
```

and the

```plaintext
docker ps -a
```

If the output is like the following one, you are good to go:

[![docker ps -a](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3fp551dd0hgs7oq28qwo.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

You should see something like that, you are good to go.

As an additional test, you can connect to the database using TablePlus (or any other database client).

You can create a new connection using the following parameters:

* Host: localhost
    
* Port: 5432
    
* Database: postgres
    
* User: postgres
    
* Password: postgres
    

Then click on the `Test Connection` button. The database is connected but emptt for now.

[![tableplus](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnwcxfnmup7ugod1ilvd.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

### 🏗️ Build the project

To build the project, type:

```bash
docker compose build
```

And the output should be something like that:

[![docker compose build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo0ujxgcymyu4ufvzbj0.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

### 🏃‍♂️ Run the project

Now we can run the project.

```bash
docker compose up laravelapp
```

And this should be the output:

[![docker compose up laravelapp](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34xa6f5xt4lom27fow8p.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

### 💽 Apply the migrations

Now we need to apply the migrations.

```bash
docker compose exec laravelapp php artisan migrate
```

Now it's time to test the project.

---

## 🧪 Test the project

Now we can test the project. We will use Postman, but you can use any other tool.

### 📝 Create a player

To create a new player, make a `POST request to localhost:8000/api/player`.

The body of the request should be like that:

```json
{
    "name": "aaa",
    "email": "aaa@mail"
}
```

The output should be something like that:

[![create player](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lkxdoxojbr2wlpknfenl.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

Let's create two more players, make a `POST request to localhost:8000/api/player`.

```json
{
    "name": "bbb",
    "email": "bbb@mail"
}
```

```json
{
    "name": "ccc",
    "email": "ccc@mail"
}
```

### 📝 Get all players

To get all players, make a `GET request to localhost:8000/api/player`.

The output should be something like that:

[![get all players](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kt25ly5qfrt1nglogpo.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

### 📝 Get a player

To get a player, make a `GET request to localhost:8000/api/players/{id}`.

For example `GET request to localhost:8000/api/players/1`.

The output should be something like that:

[![get a player](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ome6cfyh13mfdz9bqlwl.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

### 📝 Update a player

To update a player, make a `PUT request to localhost:8000/api/players/{id}`.

For example `PUT request to localhost:8000/api/players/2`.

The body of the request should be like that:

```json
{
    "name": "NEW",
    "email": "MODIFIED@mail"
}
```

The output should be something like that:

[![update a player](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r8g7ca0q51f1yn9lhgxa.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

### 📝 Delete a player

To delete a user, make a `DELETE request to localhost:8000/api/players/{id}`.

For example `DELETE request to localhost:8000/api/players/1`.

On Postman you should see something like that:

[![delete a players](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bmuil7ghuz2z3fb5omsm.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

### Final test

As a final test, we can check the database using TablePlus.

Create a new connection using the following parameters: Host: localhost Port: 5432 Database: postgres User: postgres Password: postgres

[![TablePlus connection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pggs0w9wvs45cwq4ukf3.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

Then click on the `Connect` button at the bottom-right.

As you can see, we have a `players` table with 2 records:

[![TablePlus players table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m9jpj972vrl2lerjzsr.png align="left")](https://www.youtube.com/live/cdlHJeHVFW4)

---

## 🏁Conclusion

We made it! We have built a CRUD Rest API in PHP, using:

* Laravel (PHP framework)
    
* Composer (PHP package manager)
    
* Postgres (database)
    
* Docker
    
* Docker Compose
    

Video Version:

<iframe width="905" height="510" src="https://www.youtube.com/embed/cdlHJeHVFW4"></iframe>

All the code is available in the [GitHub repository](https://youtube.com/live/cdlHJeHVFW4) (link in the video description)

That's all.

If you have any questions, drop a comment below.

[Francesco](https://francescociulla.com)
