Chapter 2.5: API Routes
Time to learn: 45 minutes
1. What is a Route? A Simple Explanation
Imagine your controller (PlanetController
) is a large office building, and each of its methods (index
, store
, show
) is a separate department doing its job.
A Route is the directory sign at the building's entrance. It says:
- "If someone arrives at the
/planets
address with a GET method — send them to theindex
department (show all)." - "If someone arrives at the
/planets
address with a POST method carrying a package (data) — send them to thestore
department (create new)."
Without routes, no request from the outside world would find the right department in your code. The main file for these "address signs" in an API is routes/api.php
.
In Laravel 11+, there is no "API address book" by default. We created it ourselves by running the php artisan install:api
command. Now we have the routes/api.php
file—this is the main control center for all our API's routes.
The key difference between api.php
and web.php
:
/api
prefix: Laravel automatically adds/api
to all URLs from this file. The/planets
route becomes/api/planets
.- "Stateless": There are no sessions or cookies here like in a regular web application. Each request is independent and must contain all the information needed for authentication (usually an API token in the headers).
2. The Beginner's Path: Creating a Route Manually
Let's create a single route by hand to understand the principle. Our goal is to make the /api/planets
URL show a list of all planets.
Open routes/api.php
and write:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PlanetController; // Specify where our controller is
// (1) (2) (3)
Route::get( '/planets', [PlanetController::class, 'index'] );
// ^ ^ ^
// (HTTP Method) (URL Address) (Which controller and method to call)
Let's break down this line:
Route::get(...)
— We are saying: "This route only works for GET requests."'/planets'
— This is the URL that Laravel will listen for. With the/api
prefix, the full address will behttp://space-api.test/api/planets
.[PlanetController::class, 'index']
— This is the "destination." We are saying: "When the request comes in, find thePlanetController
class and call theindex()
method within it."
Now everything is connected! Request -> Route -> Controller -> Method.
What if we need to get a single planet by its ID? For example, /api/planets/5
.
// Route to get a specific planet
Route::get('/planets/{planet}', [PlanetController::class, 'show']);
Here, {planet}
is a "wildcard" or variable. Laravel understands that anything can be in its place (an ID, a slug). It then passes this value to the show(Planet $planet)
method. This "magic," where Laravel finds the planet by its ID automatically, is called Route Model Binding.
3. The Master's Path: apiResource
—One Line to Rule Them All
Creating each route manually (for index
, show
, store
, update
, destroy
) is tedious. The developers of Laravel understand this, so they created a powerful helper: apiResource
.
Delete everything we wrote and replace it with a single line:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PlanetController;
Route::apiResource('planets', PlanetController::class);
What does this one line do under the hood? It automatically creates a whole set of routes for the standard CRUD operations that we have already implemented in the controller.
Method | URL | Dispatches to Method | Purpose |
---|---|---|---|
GET | /api/planets |
index() |
Get a list of all planets |
POST | /api/planets |
store() |
Create a new planet |
GET | /api/planets/{planet} |
show() |
Show one specific planet |
PUT/PATCH | /api/planets/{planet} |
update() |
Update an existing planet |
DELETE | /api/planets/{planet} |
destroy() |
Delete a planet |
You can see for yourself. Run this command in your terminal:
You will see a table with all the created routes. apiResource
is your best friend for saving time when creating standard APIs.
4. Special Missions and Route Order
What if we need a custom route that isn't in apiResource
? For example, to get a random planet at /api/planets/random
.
Let's add it. But there is a critically important trap here that 9 out of 10 beginners fall into.
Incorrect Order (DOES NOT WORK!):
Route::apiResource('planets', PlanetController::class);
Route::get('/planets/random', [PlanetController::class, 'random']); // <-- WILL NOT WORK
Route::apiResource
, which created the GET /planets/{planet}
route. When you request /planets/random
, Laravel will think that "random" is the ID of a planet, try to find a planet with the ID "random" in the database, and you will get an error.
Correct Order (WORKS!):
<?php
use App\Http\Controllers\PlanetController;
use Illuminate\Support\Facades\Route;
// 1. Declare SPECIFIC routes first
Route::get('/planets/random', [PlanetController::class, 'random']);
// 2. Then, declare GENERAL routes with variables
Route::apiResource('planets', PlanetController::class);
⚠️ Important!
To test the
api/planets/random
route, you need to add a new handler inPlanetController
:
The Rule: Always declare more specific routes (like /random
) before more general and wildcard routes (like /{planet}
).
5. Grouping: Bringing Order
When you have many routes, you can and should group them.
A. API Versioning
To avoid breaking old applications that use your API in the future, it is common practice to add a version to the URL, for example, /api/v1/...
.
<?php
Route::prefix('v1')->group(function () {
// All routes within this group will get the /v1 prefix
// Final URL: /api/v1/planets
Route::get('/planets/random', [PlanetController::class, 'random']);
Route::apiResource('planets', PlanetController::class);
});
B. Protecting Routes (Middleware) Imagine that anyone can view planets, but only authenticated users can create, update, and delete them.
<?php
// Public routes, accessible to everyone
Route::get('/planets', [PlanetController::class, 'index']);
Route::get('/planets/{planet}', [PlanetController::class, 'show']);
// Group of routes requiring a "pass" (authentication)
Route::middleware('auth:sanctum')->group(function () {
Route::post('/planets', [PlanetController::class, 'store']);
Route::put('/planets/{planet}', [PlanetController::class, 'update']);
Route::delete('/planets/{planet}', [PlanetController::class, 'destroy']);
});
Here, middleware('auth:sanctum')
is like a security guard who checks the "pass" of everyone trying to access the routes within the group.
6. Testing with Postman
Now that all the routes are laid out, it's time to test them.
- If you are using Herd: Your site is already running at an address like
http://space-api.test
. - If not: Start the local server with the command
php artisan serve
. The address will behttp://localhost:8000
.
Open Postman and send the requests:
GET http://space-api.test/api/planets
GET http://space-api.test/api/planets/random
POST http://space-api.test/api/planets
(don't forget to add a JSON request body in theBody
->raw
->JSON
tab).
Example POST request:
{
"name": "Kepler-186f",
"description": "The first Earth-sized planet in the habitable zone",
"size_km": 15000,
"solar_system": "Kepler-186",
"is_habitable": true
}
7. Common Routing Errors
- 404 Not Found
- Incorrect URL (
/api/planet
instead of/api/planets
) - Forgot to run
php artisan serve
- Incorrect URL (
- 405 Method Not Allowed
- Incorrect HTTP method (e.g., GET instead of POST)
- Missing Controller
- Typo in the controller name (
PlanetControler
)
- Typo in the controller name (
- Route Name Collision
- Duplicate route names
Review Quiz
🚀 Chapter Summary:
You have built the "hyperspace routes" for the space API! Now:
- 🗺️ All endpoints are accessible via
/api/...
- 🔗 Resource routes are connected to the controller
- 🛡️ Custom routes for special operations have been added
- ✅ The routes have been tested with Postman
The universe is open for requests! Next, we will add protection against "space debris"—data validation.
📌 Checkpoint:
- Run
php artisan route:list
- Make sure you see 5+ routes for planets
- Test
GET /api/planets
in your browser/Postman⚠️ If you get a 404 error:
- Check for
Route::apiResource
inroutes/api.php
- Make sure the server is running (
php artisan serve
)- For Windows: allow port 8000 in the firewall