Getting Started with an API-Only Laravel 12 Project: Event Management

If you’re just stepping into Laravel API development, building a simple Event Management API is a great way to understand the fundamentals. In this guide, I’ll walk you through each step you listed, explain why we do it, and show small examples so things stay clear and beginner-friendly.

1. Create a New Laravel App

First, create a fresh Laravel project:

composer create-project laravel/laravel event-management
cd event-management
php artisan serve

Now your app is running on:

http://127.0.0.1:8000

2. Install Helpful Development Packages

🔹 Debugbar

composer require fruitcake/laravel-debugbar --dev

Why?
This package shows useful debug info (queries, request data, performance) in your app. It’s mostly helpful during development.


🔹 Artisan Browse

composer require joshembling/artisan-browse

Why?
It lets you quickly inspect your database tables directly from the terminal.

Example:

php artisan browse

3. Database Setup, Migration & Seeding

Configure .env

Set your database credentials:

DB_DATABASE=event_management
DB_USERNAME=root
DB_PASSWORD=

Run Default Migrations

php artisan migrate

This creates default tables like users, password_reset_tokens, etc.


Seed the Database

Laravel includes a default seeder for users.

php artisan db:seed

Now your users table will have sample data.


4. API-Only Approach (No Views)

You mentioned:

“we don’t need browser form/layout/access”

That means:

  • ❌ No Blade templates
  • ❌ No form submissions
  • ✅ Only JSON requests/responses

Example response:

{
  "message": "Event created successfully"
}

5. Create Models & Migrations

Create Event Model

php artisan make:model Event -m

Create Attendee Model

php artisan make:model Attendee -m

This generates:

  • Model file
  • Migration file

6. Define Database Structure

Event Migration Example

Schema::create('events', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description')->nullable();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->timestamps();
});

Attendee Migration Example

Schema::create('attendees', function (Blueprint $table) {
    $table->id();
    $table->foreignId('event_id')->constrained()->cascadeOnDelete();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->timestamps();
});

Then run:

php artisan migrate

7. Create API Controllers

php artisan make:controller Api/EventController --api
php artisan make:controller Api/AttendeeController --api

Why --api?

It creates a controller without:

  • create()
  • edit()

Because those are for HTML forms, which we are not using.


Example Controller Methods

public function index()
{
    return Event::all();
}

public function store(Request $request)
{
    $event = Event::create($request->all());
    return response()->json($event, 201);
}

8. Define Relationships

Event Model

class Event extends Model
{
    protected $fillable = ['title', 'description', 'user_id'];

    public function owner()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function attendees()
    {
        return $this->hasMany(Attendee::class);
    }
}

Attendee Model

class Attendee extends Model
{
    protected $fillable = ['event_id', 'user_id'];

    public function event()
    {
        return $this->belongsTo(Event::class);
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

User Model (update)

public function events()
{
    return $this->hasMany(Event::class);
}

public function attendingEvents()
{
    return $this->belongsToMany(Event::class, 'attendees');
}

9. Relationship Summary

  • ✅ One User owns many Events
  • ✅ One Event has many Attendees
  • ✅ One User can attend many Events

This structure is very common in real-world apps.


10. Define API Routes (Nested Resources)

Instead of simple routes, you’re using nested API resources, which is a smarter and more realistic approach for related data like events and attendees.

Open routes/api.php and add:

use App\Http\Controllers\Api\EventController;
use App\Http\Controllers\Api\AttendeeController;

Route::apiResource('events', EventController::class);
Route::apiResource('events.attendees', AttendeeController::class)
    ->scoped(['attendee' => 'event']);

🔍 What’s Different Here?

1. Standard Event Routes

Route::apiResource('events', EventController::class);

This gives you endpoints like:

  • GET /api/events
  • POST /api/events
  • GET /api/events/{event}

2. Nested Attendee Routes

Route::apiResource('events.attendees', AttendeeController::class);

Now attendees are always accessed within an event context:

MethodEndpoint
GET/api/events/{event}/attendees
POST/api/events/{event}/attendees
GET/api/events/{event}/attendees/{attendee}
PUT/api/events/{event}/attendees/{attendee}
DELETE/api/events/{event}/attendees/{attendee}

👉 This makes your API more logical:

  • You don’t fetch attendees globally
  • You always know which event they belong to

3. Scoped Binding (Important 🚨)

->scoped(['attendee' => 'event']);

This ensures:

  • The attendee must belong to the given event
  • Prevents accessing unrelated data

🧠 Example

Without scoped binding:

GET /api/events/1/attendees/99

Even if attendee 99 belongs to another event, Laravel might still return it ❌


With scoped binding:

  • Laravel checks:”Does attendee 99 belong to event 1?”

If not:

{
  "message": "Not Found"
}

✅ This adds data safety automatically


🎯 Why This Approach Is Better

  • Cleaner API structure
  • Strong relationship enforcement
  • Prevents invalid data access
  • Matches real-world logic

⚡ Controller Tip

In your AttendeeController, you can now safely assume:

public function index(Event $event)
{
    return $event->attendees;
}

Laravel automatically injects the correct Event model based on the URL.


This small routing change makes your API much more robust and professional 👍


11. Testing Your API

You can use:

  • Postman
  • Thunder Client (VS Code)
  • curl

Example request:

POST /api/events
{
  "title": "Laravel Workshop",
  "description": "Learning APIs",
  "user_id": 1
}

🎯 Final Thoughts

This setup gives you:

  • A clean API-only Laravel project
  • Proper database relationships
  • Scalable structure for real apps

From here, you can extend:

  • Authentication (login/register)
  • Validation
  • API Resources (for better JSON formatting)
  • Pagination