Laravel One to Many relationships with CRUD example

by Eric McWinNEr

.

Updated Sun Jun 18 2023

Laravel One to Many relationships with CRUD example

Laravel One to Many relationships is used to define situations where one entity in our database is related to many entities of the same type.

In a voting system example, we can see that the political parties and candidates have one to many relationships. 

A political party can have many candidates — at least two candidates for each category of elections, for the running mates; and it could have several candidates if we extend to presidential, governorship and other levels of elections.

However, a candidate can only belong to one political party (at a time, at least).  

By the way, if you’re STARTING with Laravel, I have created a Complete Laravel Guide just for you.

Another good example is categories and products — a simple system could exist where a category can have many products, but each product should belong to only one category.

Before we dive into the article, subscribe to get access to our free Laravel tips that will improve your productivity.

If we were to define this relationship in plain English, we’d say:

“A political party has many candidates”.

Eloquent makes it easy to define such relationships:

Go ahead and guess how Eloquent defines it?

Well, it’s done by using the hasMany method (I hope you got it right).

We use it like this:

<?php
  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class PoliticalParty extends Model {
    
    /**
    * Get the candidates this party has
    */
    public function candidates() {
      return $this->hasMany(App\Model\Candidate::class); 
    }
    
  }

To retrieve the entities that this relationship defines, we can use the dynamic property like this:

<?php

  namespace App\Http\Controllers;

  use App\Models\PoliticalParty;

  class RandomController extends Controller {
    /**
    * Gets candidates
    */
    public function getCandidates() {
      $party = PoliticalParty::find(1);
      $candidates = $party->candidates; // Returns a Laravel Collection
      return response(['candidates' => $candidates], 200); // Returns JSON object containing an array of candidates
    }
  }

Eloquent uses the “id” prefix to locate foreign and local keys.

This means all candidates are expected to have a column called political_party_id that would be used to locate all candidates of a political party dynamically. 

This means we have the same table structure for one to many relationships as one to one relationships:

Laravel One to Many relationships with CRUD example

These column names can be replaced using the same convention as in the one to one relationships:

<?php
  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class PoliticalParty extends Model {
    
    /**
    * Get the candidates this party has
    */
    public function candidates() {
      return $this->hasMany(App\Model\Candidate::class, 'foreign_key', 'local_key'); 
    }
    
  }

Defining the Inverse of One to Many Relationships

In our hypothetical voting app, there are many scenarios where we’d want to get the political party each candidate belongs to.

We’ll do that by defining the inverse of the relationship.

To get the inverse of a one to many relationships, we use the belongsTo method in the same way we define the inverse of one to one relationships.

<?php 
  namespace App\Models;
  
  use Illuminate\Database\Eloquent\Model;
  
  class Candidate extends Model {
    
    /**
    * Gets the political party a candidate belongs to
    */
    public function politicalParty() {
      return $this->belongsTo(App\Models\PoliticalParty::class);
    }
  }

As usual, Eloquent uses the “id” naming convention to locate local and foreign keys.

And just like we’ve done everywhere above, we can change the names of these columns by using the second and third arguments of the belongsTo method.

<?php 
  namespace App\Models;
  
  use Illuminate\Database\Eloquent\Model;
  
  class Candidate extends Model {
    
    /**
    * Gets the political party a candidate belongs to
    */
    public function politicalParty() {
      return $this->belongsTo(App\Models\PoliticalParty::class, 'foreign_key', 'local_key');
    }
  }

Laravel Collections

One thing to keep in mind is :

When using one to many relationships (or any Laravel relationship where we expect to get multiple values), our dynamic property would return a Laravel Collection containing all the candidates this party has. 

Laravel Collection is a wrapper around PHP arrays that provide some helpful functions for manipulating arrays. Assuming we wanted to act as each candidate, we could use a foreach loop like this:

<?php

  namespace App\Http\Controllers;

  use App\Models\PoliticalParty;

  class RandomController extends Controller {
    /**
    * Gets candidates
    */
    public function getCandidates() {
      $party = PoliticalParty::find(1);
      $candidates = $party->candidates; // Returns a Laravel Collection
      foreach($candidates as $candidate) {
        // Do what you want with $candidate
      }
    }
  }

Using Dynamic Methods

For Laravel relationships that retrieve multiple values, it is possible to use the dynamic method to create different relationships.

An illustration would explain this better:

<?php
  namespace App\Models;

  use Illuminate\Database\Eloquent\Model;

  class PoliticalParty extends Model {
    
    /**
    * Get the candidates this party has
    */
    public function candidates() {
      return $this->hasMany(App\Model\Candidate::class, 'foreign_key', 'local_key'); 
    }
    
    /**
    * Get presidential candidates
    */
    public function presidentialCandidates() {
      return $this->candidates()->where(['type', 'president']);
    }
    
  }

The defined method can then be used as a dynamic property, just like any other defined relationship:

<?php

  namespace App\Http\Controllers;

  use App\Models\PoliticalParty;

  class RandomController extends Controller {
    /**
    * Gets candidates
    */
    public function getCandidates() {
      $party = PoliticalParty::find(1);
      $candidates = $party->presidentialCandidates; // Returns a Laravel Collection
      foreach($candidates as $candidate) {
        // Do what you want with $candidate
      }
    }
  }

Take a break and subscribe to get access to our free Laravel tips that will improve your productivity.

CRUD Example

To better illustrate one to many relationships, we’ve prepared a repository you can clone that shows one-to-many relationships in action.

In this repository, we build a simple app that lists our products based on the category you select.

It also allows you to create new categories and new products under a category. 

It builds on the relationship “One Category has Many Products”.  

Feel free to clone the repository to get started:

Laravel One to Many relationships with CRUD example

To run the application after cloning, you’d have install composer dependencies with:

composer install

Then you’d have to run the migrations (after creating a database and setting credentials in your  .env file of course:

php artisan migrate

Then, we’d have to seed the database:

php artisan db:seed

Finally, we’d have to serve our app:

php artisan serve

The application uses two entities:  Category and Product and they have one to many relationships.

Every Product belongs to one Category but each Category can have many products.

We first create this entity and its migrations and controllers.

We can run the commands:

php artisan make:model Category -m -c

php artisan make:model Product -m -c

Next, we define our migrations; they’ll have a simple structure for brevity:

<?php

namespace App\Models;

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

class Category extends Model
{
    use HasFactory;

    public function products() {
        return $this->hasMany(Product::class);
    }
}
<?php

namespace App\Models;

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

class Product extends Model
{
    use HasFactory;

    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}

Finally, we update the controllers to correctly display the products and categories and accept new categories and products.

Open the App\Http\Controllers\CategoryController and paste in the following code.

<?php

namespace App\Http\Controllers;

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

class CategoryController extends Controller
{
    public function products($id) {
        $categories = Category::all();
        $current_category = Category::findOrFail($id);
        $products = $current_category->products;
        return view('index', compact('current_category', 'categories', 'products'));
    }

    public function new() {
        return view('new.category');
    }

    public function add(Request $request) {
        $data = $request->validate(
            [
                'name' => ['string', 'required', 'unique:categories']
            ]
        );
        $category = new Category();
        $category->name = $data['name'];
        $category->save();
        session()->flash("status", "success");
        session()->flash("title", "Success!");
        session()->flash('message', "The category was created successfully!");
        return redirect()->route('index');
    }
}

Open the App\Http\Controllers\ProductController and paste in the following code.

<?php

namespace App\Http\Controllers;

use App\Models\Category;
use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    public function index(Request $request) {
        $categories = Category::all();
        $products = Product::all();
        return view('index', compact('categories', 'products',));
    }

    public function new() {
        $categories = Category::all();
        return view('new.product', compact('categories'));
    }

    public function add(Request $request) {
        $data = $request->validate([
            'name' => ['string', 'required'],
            'category' => ['required', 'numeric'],
            'picture' => ['file', 'required', 'image']
        ]);
        $category = Category::findOrFail($data['category']);
        $product = new Product();
        $product->name = $data['name'];
        $file = $request->file('picture')->store('uploads', 'public');
        $product->image = explode("/", $file)[1];
        $category->products()->save($product);
        session()->flash("status", "success");
        session()->flash("title", "Success!");
        session()->flash('message', "The product was created successfully!");
        return redirect()->route('index');
    }
}

The rest of the repository codes are front-end code and the code to render the words and matches on the frontend.

What’s Next?

You can get an in-depth article on “Laravel many to many relationships with example” that explain everything from creating the relationship, the inverse method of creating it, and how to retrieve the data.

Conclusion 

In this article, we had an in-depth look at Laravel One to Many relationships and an application implementing the relationship to solve a problem.

One to Many relationships are used extensively in real-world applications, and understanding them provides a lot of flexibility and power for handling lots of scenarios in the apps we build.  

Feel free to check out our other articles, where we similarly go in-depth to explain other Laravel relationships (there are 6 of them).

Feel free to let us know if you have any questions and we’d love to help.

Whenever you're ready

There are 4 ways we can help you become a great backend engineer:

The MB Platform

Join 1000+ backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.

The MB Academy

The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post like this, you will absolutely enjoy our exclusive weekly newsletter, Sharing exclusive backend engineering resources to help you become a great Backend Engineer.

Get Backend Jobs

Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board

Backend Tips, Every week

Backend Tips, Every week