Chapter 14: Companies Module - CRUD Operations
This directory contains code samples for implementing full CRUD operations for the Companies module in the Laravel CRM.
Files
Backend
- CompanyController.php — Handles CRUD operations (Index with search/filtering, Create, Store, Show, Edit, Update, Destroy)
- CompanyRequest.php — FormRequest validation with team-specific uniqueness rules
- CompanyPolicy.php — Authorization policy ensuring users can only access their team's companies
- CompanyModel.php — Complete Company Eloquent model with relationships and methods
- routes-web.php — Route definition snippet showing how to register resourceful routes
Frontend (React/Inertia)
- CompaniesIndexView.tsx — List all companies with pagination and quick actions
- CompaniesShowView.tsx — Display company details with related contacts and deals
- CompaniesCreateEditView.tsx — Form for creating and editing companies
Key Concepts
FormRequest Validation
The controller uses CompanyRequest FormRequest for validation with team-specific uniqueness:
// In CompanyController@store and @update
public function store(CompanyRequest $request)
{
Company::create($request->validated());
}The FormRequest enforces that company names are unique only within the current team.
Search and Filtering
The controller implements search and filtering with query optimization:
// In CompanyController@index
$companies = Company::query()
->when($request->input('search'), function ($query, $search) {
$query->where('name', 'like', "%{$search}%");
})
->when($request->input('industry'), function ($query, $industry) {
$query->where('industry', $industry);
})
->withCount(['contacts', 'deals']) // Optimization for counts
->latest()
->paginate(15)
->withQueryString(); // Preserve filters in pagination linksThe frontend uses debounced search (300ms delay) to reduce API calls while typing.
Eager Loading
The controller uses load() with query callbacks to eager load relationships and prevent N+1 queries:
// In CompanyController@show
$company->load([
'contacts' => fn ($query) => $query->latest(),
'deals' => fn ($query) => $query->latest(),
]);The withCount() method efficiently loads relationship counts without N+1 queries:
// Efficiently loads contacts_count and deals_count
$companies = Company::withCount(['contacts', 'deals'])->get();Team-Based Isolation
The Company model uses the HasTeamScope trait to automatically filter by team:
// Global scope automatically applied
$companies = Company::all(); // Only current team's companiesAuthorization
The CompanyPolicy ensures users can only view/edit/delete companies in their team:
public function view(User $user, Company $company): bool
{
return $user->current_team_id === $company->team_id;
}Relational Data Display
The Show view displays related contacts and deals:
<Card>
<CardHeader>
<CardTitle className="text-lg">Contacts</CardTitle>
</CardHeader>
<CardContent>
{contacts.data.map((contact) => (
// Display each contact
))}
</CardContent>
</Card>Usage
- Copy
CompanyController.phptoapp/Http/Controllers/ - Copy
CompanyPolicy.phptoapp/Policies/ - Register the policy in
app/Providers/AuthServiceProvider.php - Add route registration from
routes-web.phptoroutes/web.php - Create views from the React components in
resources/js/Pages/Companies/
Relational Form Handling
The chapter also demonstrates updating Contact forms to allow selecting a company:
- Update
ContactController@createand@editto pass companies list - Add
company_idvalidation toContactRequest - Add company dropdown to Contact form component
Next Steps
In Chapter 15, you'll build on this foundation to create the Deals module with a visual sales pipeline interface.