Many To Many Relationships With Laravel

Part 21 of 48 in API Driven Development With Laravel and VueJS
Dan Pastori avatar
Dan Pastori November 16th, 2017
⚡️ Updated content is available We learned a lot since we originally wrote this article. We now have this updated for Laravel 10, Vue 3/NuxtJS 3, and Capacitor 3.

So in this tutorial, we will go through one of the more useful, but advanced relationships you can do with Laraval, the many-to-many relationship between models with eloquent. Eloquent, Laravel’s ORM makes database connections and querying a breeze. We will be using the many-to-many relationship to add brew methods to cafes.

The key with many-to-many relationships is the join (or pivot) table. This allows the relationship id from one model to be related to many other models and vice-versa. This makes querying simple for questions like “Which cafes have this brew method?” and “Which brew method does this cafe have?”

Step 1: Create Brew Methods Table

We will need a simple table created for our brew methods. To do this we need to run a migration.

First we will need to open the terminal on the server and run:
php artisan make:migration added_brew_methods

This will create our migration. find the migration in the /app/database/migrations directory and open it up.

The table will be really simple, we will need an ID, timestamps, and a brew method name. In your migration up method, add the following code:

Schema::create('brew_methods', function( $table ){
  $table->increments('id');
  $table->string('method');
  $table->timestamps();
});

We now have an auto incremented id for the brew method and a method which will store names like ‘Aeropress’, ‘V-60 Pour Over’, etc.

Just to clean up the migration, make sure you have:

Schema::dropTable('brew_methods');

In the down method.

Now run php artisan migrate and you will have a brew methods table.

I seeded the table with the following Insertquery:

INSERT INTO `brew_methods` (`id`, `method`, `created_at`, `updated_at`)
VALUES
    (1, 'Hario V60 Dripper', '2017-10-29 01:41:52', '2017-10-29 01:41:52'),
    (2, 'Chemex', '2017-10-29 01:41:59', '2017-10-29 01:41:59'),
    (3, 'Siphon', '2017-10-29 01:42:25', '2017-10-29 01:42:25'),
    (4, 'Kyoto Cold Brew', '2017-10-29 01:42:45', '2017-10-29 01:42:45'),
    (5, 'Clover', '2017-10-29 01:42:52', '2017-10-29 01:42:52'),
    (6, 'Espresso', '2017-10-29 01:43:18', '2017-10-29 01:43:18'),
    (7, 'Aeropress', '2017-10-29 01:43:41', '2017-10-29 01:43:41'),
    (8, 'French Press', '2017-10-29 01:44:02', '2017-10-29 01:44:02'),
    (9, 'Kalita Wave Dripper', '2017-10-29 01:44:58', '2017-10-29 01:44:58'),
    (10, 'Nitrous', '2017-10-29 01:45:35', '2017-10-29 01:45:35');

These should be managed by some sort of admin backend (yes this is foreshadowing for future tutorials) but for now this will work!

Step 2: Create Join Table

The join table is where the magic happens for many-to-many relationships. This table simply links the IDs of the two joining models, in this case ‘Brew Methods’ and ‘Cafes’. The table is simple and contains the columns for both cafe_id and brew_method_id which are set up as foreign keys as best practice for database structure.

First we will make another migration so run:
php artisan make:migration added_cafes_brew_methods

Open the new file in your app/database/migrations . You will need to add this code to the create() method:

Schema::create('cafes_brew_methods', function( $table ){
  $table->integer('cafe_id')->unsigned();
  $table->foreign('cafe_id')->references('id')->on('cafes');
  $table->integer('brew_method_id')->unsigned();
  $table->foreign('brew_method_id')->references('id')->on('brew_methods');
});

What this does is first create a cafes_brew_methods table which will be our join table. Next it creates an unsigned integer called cafe_id It needs to be unsigned since it’s referencing an auto incrementing field. Next we set up the relationship for that key.

A foreign key is simply a reference to a primary key on a different table. This helps keep data integrity in tact so you don’t reference cafes or brew methods that don’t exist.

To set up the relationship, we use the schema builder documented here: Database: Migrations – Laravel – The PHP Framework For Web Artisans to set up a foreign key. Looking at this line of code:

$table->foreign('cafe_id')->references('id')->on('cafes');

We first say that the newly created cafe_id will be our foreign key and that it will reference the id key on the cafes table. The schema builder makes this SQL query much easier to grasp.

For the brew methods relationship, we repeat the same type of logic except we reference the brew_methods table.

Now we have our many-to-many table structured! We just need to instruct our models on how to use it.

Step 3: Create Brew Methods Model

In your /app/Http/Models/ directory add a file BrewMethods.php. This will be the model we use for our Brew Methods table.

First we need to declare our model and add the table it uses:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BrewMethod extends Model
{
    protected $table = 'brew_methods';
}

Now we need to declare the relationship to cafes. To do this, we will be creating a method that contains our relationship so add:

public function cafes(){

}

to the model. Now we need to use the belongsToMany relationship provided by Eloquent. In our cafes method, we should add this code:

return $this->belongsToMany( 'App\Models\Cafe', 'cafes_brew_methods', 'brew_method_id', 'cafe_id' );

What this does is states that each brew method belongs to many cafe models on the cafes_brew_methods table. The next two parameters are hidden in the docs for Eloquent on Laravel, but what they state is that we are using the brew_method_id column for a Brew Method as the key we are mapping to the foreign cafe id which is represented by the cafe_id table. I like to explicitly declare these two parameters in case there is a different key used for primary keys.

Our Brew Methods are now set up correctly, so next we should configure our Cafes.

Step 4: Configure Cafes to have many Brew Methods

If we open our app/Http/Models/Cafe.php file, we need to add
the same type of relationship for cafes to brew methods as we did from brew methods to cafes. To do this, add this function to the Cafe.php model:

public function brewMethods(){
  return $this->belongsToMany( 'App\Models\BrewMethod', 'cafes_brew_methods', 'cafe_id', 'brew_method_id' );
}

This does exactly the same thing as we did with Brew Methods to cafes except from Cafes to brew methods, we are saying that each Cafe belongs to many brew methods on the cafes_brew_methods table and that we are using the cafe_id of the cafe table to bind thebrew_method_id in the cafes_brew_methods table to join the two models. The documentation states it here: Eloquent: Relationships – Laravel – The PHP Framework For Web Artisans

Now we have our many to many relationship set up and we are good to go for using it!

Step 5: Retrieving the Many To Many Data Record

When we load the cafes, we also want to load the brew methods associated with the Cafe. We want to do this by calling the relationship method on the model when we retrieve our data.

First, open the app/Http/Controllers/API/CafesController.php file and look at the getCafes() method. Right now we are calling:
$cafes = Cafe::all() which gets all of the cafes. We want to call our relationship to the Brew Methods in this as well, so we need to change the code to:

$cafes = Cafe::with('brewMethods')->get();

Now each cafe returned will have the associated brew methods that the cafe provides!

We should also update our return for the individual cafe as well, so if you look at the getCafe( $id ) method, swap:

$cafe = Cafe::where('id', '=', $id)->first();

with

$cafe = Cafe::where('id', '=', $id)
            ->with('brewMethods')
            ->first();

Now we return the brew methods with each individual cafe as well!

Step 6: Other Features

So, along with adding our Brew Methods model and tables, I also added a route to retrieve the brew methods:

/*
  |-------------------------------------------------------------------------------
  | Get All Brew methods
  |-------------------------------------------------------------------------------
  | URL:            /api/v1/brew-methods
  | Controller:     API\BrewMethodsController@getBrewMethods
  | Method:         GET
  | Description:    Gets all of the brew methods in the application
  */
  Route::get('/brew-methods', 'API\BrewMethodsController@getBrewMethods');

A Brew Methods Controller located at: app/Http/Controllers/API/BrewMethodsController.php which returns all of the brew methods. I followed the same ideas laid out in this tutorial: Add Laravel API End Points – Server Side Up.

I also added a Vuex module and an API file for Brew Methods. The API file is /resources/assets/js/api/brewMethod.js and contains the route to load brew methods. For the tutorial on Javascript API routes check out: https://serversideup.net/build-api-requests-javascript/.

The Vuex module is located at /resources/assets/js/modules/brewMethods.js and imported into the store at /resources/assets/js/store.js. To build this module, I followed the same instructions as I did for Building a Vuex Module found here: Build a Vuex Module – Server Side Up.

The last thing I did is added the dispatch call to load the brew methods in the /resources/assets/js/pages/Layout.vue file to load the brew methods on page load:

this.$store.dispatch( 'loadBrewMethods' );

The reason I load it on the initial page load is we can use the brew methods for filtering, but also for adding a new cafe. We will want to select the brew methods available at that cafe when adding the cafe.

Step 7: Saving Many To Many Data

So in the companion tutorial about Dynamic Forms with VueJS https://serversideup.net/dynamic-forms-vuejs/, I will be showing how to tie everything together in the NewCafe.vue page. We will be adding another feature to cafes that allow for multiple locations that will drastically adjust the form we created and all of that is detailed in the Dynamic Forms with VueJS tutorial https://serversideup.net/dynamic-forms-vuejs/. For now, know we will be passing an array of brew method ids to the API at the /api/v1/cafes POST route.

That route is handled by the app/Http/Controllers/API/CafesController.php file and the postNewCafe() method.

What we will do is after we save our new cafe, we will sync the brewMethods() relationship with the brew_method ids array.

Add the code just below the saving of the cafe. The cafe has to exist before we sync relationships:

/*
  Sync the brew methods on the cafe
*/
$brewMethods = $request->get('brew_methods');

$cafe->brewMethods()->sync( $brewMethods );

This is all we need to do to save the brew methods with the cafe! If you were to edit a brew method like remove one, sync will take care of all of the logic for it!

Conclusion

In this tutorial we added a many to many relationship between cafes and brew methods. A cafe can have many brew methods and brew methods can have many cafes. In the next tutorial, we are going to take things one step farther with a Parent/Child relationship between cafes. We will use cafe locations as an example. A cafe may have multiple locations with different brew methods. For example, Valentine coffee in the Milwaukee airport does not have pour over, but in their tasting room they do.

All code is pushed here: GitHub – serversideup/roastandbrew Feel free to reach out with any questions!

Keep Reading
View the course View the Course API Driven Development With Laravel and VueJS
Up Next → Dynamic Forms with VueJS

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.