REST APIs power most modern web and mobile applications. When a frontend app needs data, it usually talks to a backend through an API. That API decides what data is shared, how it’s structured, and who can access it. If you’re working with PHP, Laravel is one of the best tools for building these APIs.
In this guide, you’ll learn how to build a REST API using Laravel from scratch. We’ll start with project setup, then move step by step through creating CRUD endpoints, adding validation, securing the API with authentication, and improving it with features like pagination and versioning.
This guide is for PHP developers, Laravel beginners, and anyone who wants to expose application data through clean REST endpoints. You don’t need advanced Laravel knowledge, but basic PHP and Laravel concepts will help.
By the end, you’ll have a working example of a simple Todo API that supports creating, reading, updating, and deleting data using proper REST principles.
What is a REST API?
A REST API is a way for applications to communicate with each other over HTTP. REST stands for Representational State Transfer. In simple terms, it defines a set of rules for how a client (like a web or mobile app) can request data from a server and how the server should respond.
In a REST API, everything is treated as a resource. A resource can be a user, a task, a product, or any piece of data your app works with. Each resource is accessed through a URL, and actions on that resource are performed using standard HTTP methods.
Key characteristics of REST APIs
Stateless
Each request is independent. The server does not remember previous requests. All required data (like authentication tokens) must be sent with every request.
Uniform interface
REST APIs follow consistent rules for URLs, request methods, and responses. This makes APIs predictable and easy to use.
Resource-based structure
Endpoints represent nouns, not actions. For example:
/api/tasks
/api/users
Common HTTP Methods in REST
REST APIs rely on HTTP verbs to describe actions:
- GET – Fetch data
- POST – Create new data
- PUT / PATCH – Update existing data
- DELETE – Remove data
Simple REST API Example
Imagine we’re building a Todo API.
a. Get all tasks
GET /api/tasks
b. Response
[
{
"id": 1,
"title": "Learn Laravel APIs",
"completed": false
}
]
c. Create a new task
POST /api/tasks
d. Request body
{
"title": "Build a REST API",
"completed": false
}
e. Response
{
"id": 2,
"title": "Build a REST API",
"completed": false
}
f. Update a task
PUT /api/tasks/2
g. Request body
{
"completed": true
}
h. Delete a task
DELETE /api/tasks/2
Why JSON Is Used
REST APIs usually return data in JSON (JavaScript Object Notation) format because it is:
- Easy to read and write
- Lightweight
- Supported by almost every programming language
Laravel works with JSON out of the box, which makes it a natural fit for building REST APIs.
Why Build REST APIs with Laravel?
Laravel is a popular PHP framework, and one big reason is how easy it makes API development. You don’t have to glue things together manually. Most of what you need is already built in.
Here’s why Laravel works so well for REST APIs.
a. Clean and simple structure
Laravel follows the MVC pattern (Model, View, Controller). For APIs, you mainly work with models and controllers.
This keeps your code organized and easy to maintain as the API grows.
You always know:
- Models handle data
- Controllers handle requests and responses
- Routes connect everything
b. Built-in routing for APIs
Laravel has a dedicated file for API routes:
routes/api.php
All routes are defined automatically:
- Use the /api prefix
- Return JSON responses by default
- Stay separate from web routes
Laravel also provides Route::apiResource(), which can generate all CRUD routes in one line. This saves time and avoids mistakes.
c. Eloquent ORM makes database work easy
Laravel’s Eloquent ORM lets you work with the database using PHP code instead of raw SQL.
For example, instead of writing queries manually, you can do:
Task::all();
This makes your API code:
- Easier to read
- Easier to update
- Less error-prone
d. Artisan commands speed things up
Laravel comes with a command-line tool called Artisan.
You can generate files in seconds, like:
- Models
- Controllers
- Migrations
- API resources
This means less setup work and more focus on logic.
e. Built-in support for common API needs
Laravel already handles many things APIs need:
- Request validation
- JSON responses
- Authentication (Sanctum or Passport)
- Rate limiting
- API testing
You don’t need extra libraries just to get started.
How to set up a Laravel project
Now we’ll set up a fresh Laravel project for our REST API. We’ll also connect a database so we can store tasks for our Todo API.
What You Need Before You Start
Make sure you have these installed on your computer:
- PHP (Laravel needs PHP to run)
- Composer (it installs Laravel and PHP packages)
- A database like MySQL (you can also use SQLite)
If you already run Laravel projects, you’re good to go.
Step 1: Create a new Laravel project
Open your terminal and run:
composer create-project laravel/laravel todo-api
Go inside the project folder:
cd todo-api
Step 2: Run the project
Start the Laravel server:
php artisan serve
You’ll see something like this:
http://127.0.0.1:8000
Open that link in your browser. If you see the Laravel welcome page, your setup is working.
Step 3: Set Up Your Database
Laravel uses a file named .env to store project settings.
Open the .env file and update these lines (example for MySQL):
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=todo_api
DB_USERNAME=root
DB_PASSWORD=
Now, create a database named todo_api in your database tool (phpMyAdmin, TablePlus, MySQL CLI, etc.).
Quick tip: Using SQLite (optional, easier)
If you want the easiest setup, use SQLite:
1. Create a file:
touch database/database.sqlite
2. Update .env:
DB_CONNECTION=sqlite
DB_DATABASE=/full/path/to/your-project/database/database.sqlite
SQLite is great for learning and small projects.
Step 4: Run Migrations
Migrations create database tables automatically.
Run this:
php artisan migrate
If everything is correct, Laravel will create the default tables like
- users
- password reset tokens
- sessions (depends on version)
Why migrations matter
Migrations are like “saved steps” for your database.
So if your teammate runs the project, they can create the same tables by running one command.
Step 5: Confirm API Routes Are Ready
Laravel already separates API routes for you.
Open:
routes/api.php
This file is where we’ll add our API endpoints, like:
GET /api/tasks
POST /api/tasks
Building CRUD Endpoints in Laravel
We’ll build a simple Task (Todo) API with these endpoints:
GET /api/tasks → list tasks
POST /api/tasks → create a task
GET /api/tasks/{id} → get one task
PUT /api/tasks/{id} → update a task
DELETE /api/tasks/{id} → delete a task
Step 1: Create Model + Migration
Run this command:
php artisan make:model Task -m
Now open the migration file inside:
database/migrations/xxxx_xx_xx_create_tasks_table.php
Update the table like this:
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
Run migrations:
php artisan migrate
Step 2: Allow Mass Assignment in the Model
Open:
app/Models/Task.php
Add $fillable so Laravel can save these fields safely:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = ['title', 'completed'];
}
Step 3: Create an API Controller
Run:
php artisan make:controller API/TaskController --api
This creates:
app/Http/Controllers/API/TaskController.php
Now paste this full CRUD code:
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
// GET /api/tasks
public function index()
{
$tasks = Task::latest()->get();
return response()->json([
'data' => $tasks
], 200);
}
// POST /api/tasks
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'completed' => 'sometimes|boolean',
]);
$task = Task::create($validated);
return response()->json([
'message' => 'Task created successfully.',
'data' => $task
], 201);
}
// GET /api/tasks/{task}
public function show(Task $task)
{
return response()->json([
'data' => $task
], 200);
}
// PUT /api/tasks/{task}
public function update(Request $request, Task $task)
{
$validated = $request->validate([
'title' => 'sometimes|required|string|max:255',
'completed' => 'sometimes|boolean',
]);
$task->update($validated);
return response()->json([
'message' => 'Task updated successfully.',
'data' => $task
], 200);
}
// DELETE /api/tasks/{task}
public function destroy(Task $task)
{
$task->delete();
return response()->json([
'message' => 'Task deleted successfully.'
], 200);
}
}
Why Task $task works
Laravel automatically finds the task by ID.
So /api/tasks/5 will automatically load task #5 (or return 404 if not found).
Step 4: Add API Routes
Open:
routes/api.php
Add:
use App\Http\Controllers\API\TaskController;
use Illuminate\Support\Facades\Route;
Route::apiResource('tasks', TaskController::class);
That one line creates all CRUD routes for you.
Step 5: Test the Endpoints (Quick Examples)
1) List tasks
curl http://127.0.0.1:8000/api/tasks
2) Create a task
curl -X POST http://127.0.0.1:8000/api/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Learn Laravel API","completed":false}'
3) Get one task
curl http://127.0.0.1:8000/api/tasks/1
4) Update a task
curl -X PUT http://127.0.0.1:8000/api/tasks/1 \
-H "Content-Type: application/json" \
-d '{"completed":true}'
5) Delete a task
curl -X DELETE http://127.0.0.1:8000/api/tasks/1
Real-World REST API Example: From Specification to Implementation
So far, we have built a simple Todo API to understand how REST works in Laravel. That’s great for learning. But real projects are usually more complex. To bridge that gap, let’s look at a real-world API specification and understand how it fits into what we’ve learned.
This example comes from an actual API specification document used in production. We won’t cover everything in the document. Instead, we’ll focus on how it’s structured and how you would build something like this in Laravel
What does this API do?
This API is used by a client app to:
- Authenticate using an API key (auth_key)
- Fetch updates from the server
- Sync data using version numbers
- Handle errors using proper HTTP status codes
- Send and receive data in JSON format
All of these follow REST principles.
Example endpoint: Get updates
Request:
POST /api/updates
Headers:
auth_key: your_api_key_here
Request body:
{
"version": 3
}
Response:
{
"recipes": [
{
"recipe_id": 10,
"title": "Green Leaf Curry",
"category": 1,
"ingredients": {
"Green leaf": "1 kg",
"Salt": "0.5 tbsp"
},
"steps": [
"First clean and cut the leaves",
"Now you can eat."
],
"remarks": "serves 2 people"
}
],
"version": "4"
}
Why does this design make sense?
Let’s break down why this API is designed this way.
Header-based authentication
- The API expects an auth_key in the request header.
- This keeps authentication separate from the request body and works well for mobile and external clients.
Version-based syncing
- The client sends its current version number.
- The server returns only new or updated data and sends back the latest version.
- This reduces data transfer and improves performance.
Clear status codes
The API uses standard HTTP codes:
- 200 for success
- 400 for bad requests
- 401 for auth errors
- 500 for server errors
This makes error handling predictable for clients.
JSON-only responses
All responses are JSON. This keeps the API lightweight and easy to use across platforms.
How does this map to Laravel
Here’s how you would build this same idea using Laravel features:
| API Concept | Laravel Feature |
|---|---|
| API key in header | Custom middleware |
| Request validation | $request->validate() |
| JSON responses | response()->json() |
| Versioning | /api/v1 route groups |
| Error handling | Exceptions + status codes |
How to test the API?
Once your CRUD endpoints are ready, you should test them to make sure:
- Routes work
- Data saves correctly
- You get the right JSON response
- Errors return the right status codes
You can test in three simple ways:
- Postman (easiest)
- cURL (quick from terminal)
- Laravel automated tests (best for long-term)
1) Test with Postman (Recommended)
Step 1: Run your Laravel server
php artisan serve
Your base URL will look like:
http://127.0.0.1:8000
Step 2: Test each endpoint
A) GET all tasks
- Method: GET
- URL: http://127.0.0.1:8000/api/tasks
Expected result:
- Status: 200
- JSON list of tasks
B) POST create a task
- Method: POST
- URL: http://127.0.0.1:8000/api/tasks
- Body → raw → JSON:
{
"title": "Learn Laravel API",
"completed": false
}
Expected result:
- Status: 201
- JSON of the created task
C) GET one task
- Method: GET
- URL: http://127.0.0.1:8000/api/tasks/1
Expected result:
- Status: 200
- JSON for task ID 1
D) PUT update a task
- Method: PUT
- URL: http://127.0.0.1:8000/api/tasks/1
- Body → raw → JSON:
{
"completed": true
}
Expected result:
- Status: 200
- Updated task JSON
E) DELETE a task
- Method: DELETE
- URL: http://127.0.0.1:8000/api/tasks/1
Expected result:
- Status: 200
- Success message
2) Test with cURL (Fast from Terminal)
GET all tasks
curl http://127.0.0.1:8000/api/tasks
Create a task
curl -X POST http://127.0.0.1:8000/api/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Test task","completed":false}'
Update a task
curl -X PUT http://127.0.0.1:8000/api/tasks/1 \
-H “Content-Type: application/json” \
-d ‘{“completed”:true}’
Delete a task
curl -X DELETE http://127.0.0.1:8000/api/tasks/1
3) Test Error Cases (Very Important)
Good APIs handle bad requests nicely.
Example: Create a task without a title
POST /api/tasks with:
{
"completed": false
}
Expected result:
- Status: 422
- JSON error message (validation failed)
This confirms your validation works.
4) Automated Testing in Laravel (Optional but Strong)
Laravel lets you test APIs using built-in feature tests.
Create a test file
php artisan make:test TaskApiTest
Open:
tests/Feature/TaskApiTest.php
Example test:
public function test_can_list_tasks(): void
{
$this->getJson('/api/tasks')
->assertStatus(200);
}
Run tests:
php artisan test
This is useful because you can re-run tests after every change and catch bugs early.
How to add validation and error handling
Validation and error handling keep your API stable. If a client sends bad data, your API should:
- reject it
- explain what went wrong
- return the right HTTP status code
Let’s do this in a clean and Laravel-friendly way.
1) Validate requests
You can validate directly in the controller, but the cleaner way is to use a Form Request.
It keeps your controller short and easy to read.
Step 1: Create a form request
php artisan make:request StoreTaskRequest
php artisan make:request UpdateTaskRequest
You’ll get two files in:
app/Http/Requests/
2) Add rules for creating a task
Open: app/Http/Requests/StoreTaskRequest.php
Update it like this:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreTaskRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'completed' => ['sometimes', 'boolean'],
];
}
}
What these rules mean
- title must exist and be a short text
- completed is optional, but if it exists, it must be true/false
3) Add Rules for updating a task
Open: app/Http/Requests/UpdateTaskRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateTaskRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['sometimes', 'required', 'string', 'max:255'],
'completed' => ['sometimes', 'boolean'],
];
}
}
This means:
- title is optional, but if you send it, it cannot be empty
- completed is also optional, but must be boolean if sent
4) Use these requests in your controller
Open: app/Http/Controllers/API/TaskController.php
Update store() and update() like this:
use App\Http\Requests\StoreTaskRequest;
use App\Http\Requests\UpdateTaskRequest;
Store method
public function store(StoreTaskRequest $request)
{
$task = Task::create($request->validated());
return response()->json([
'message' => 'Task created successfully.',
'data' => $task
], 201);
}
Update method
public function update(UpdateTaskRequest $request, Task $task)
{
$task->update($request->validated());
return response()->json([
'message' => 'Task updated successfully.',
'data' => $task
], 200);
}
Now validation happens automatically before these methods run.
5) What validation errors look like
If someone sends a bad request, Laravel returns 422 Unprocessable Entity.
Example request:
{
"completed": false
}
Response:
{
"message": "The title field is required.",
"errors": {
"title": ["The title field is required."]
}
}
That’s already good, but we can make the format more consistent if we want.
6) Handle “Not Found” errors (404) cleanly
If you use route model binding like this:
public function show(Task $task)
Laravel automatically returns a 404 if the task doesn’t exist.
Example: GET /api/tasks/99999
Response will be 404. To make it more API-friendly, we can customize the message.
7) Optional: Make error responses consistent (Recommended)
If you want your API responses to follow one format, you can handle errors globally.
Open: app/Exceptions/Handler.php
Inside register() add this:
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException;
public function register(): void
{
$this->renderable(function (ModelNotFoundException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Resource not found.'
], 404);
}
});
$this->renderable(function (ValidationException $e, $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $e->errors(),
], 422);
}
});
}
Now your API always returns a clean message for:
- validation issues (422)
- missing resources (404)
Authentication & authorization
Authentication and authorization protect your API from unauthorized access. Without them, anyone can create, update, or delete data.
Why does it matter?
- Keeps your data secure
- Controls who can access certain endpoints
- Required for real-world APIs (mobile apps, dashboards, third-party access)
Token-based authentication in Laravel
For APIs, Laravel mainly offers two options:
- Laravel Sanctum (simple, recommended for most APIs)
- Laravel Passport (OAuth2, for complex cases)
For most projects, Sanctum is enough.
Basic sanctum setup
Install Sanctum:
composer require laravel/sanctum
php artisan migrate
Protect routes:
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('tasks', TaskController::class);
});
Now:
- Public users can read data (GET)
- Only authenticated users can create, update, or delete
What authorization adds
Authorization decides what an authenticated user can do.
Example:
- User can update their own tasks
- User cannot delete others’ tasks
Laravel handles this using policies.
When to use it
Use authentication and authorization when:
- Your API has user accounts
- You expose APIs publicly
- You handle sensitive data
What are the advanced enhancements?
Once your CRUD, validation, and authentication work, you can level up your API with a few upgrades that make it easier to maintain and safer to run in production.
API Versioning (Example: /api/v1)
Versioning helps you ship changes without breaking existing apps.
Why it helps
- Mobile apps might still use old endpoints
- You can improve your API without forcing everyone to update
Simple versioned routes
Route::prefix('v1')->group(function () {
Route::apiResource('tasks', TaskController::class);
});
Now your endpoints look like:
- GET /api/v1/tasks
- POST /api/v1/tasks
Later, you can add /api/v2 with changes, while keeping v1 stable.
Pagination (handling large datasets)
If you return thousands of records, responses get slow and heavy. Pagination fixes that.
Example
public function index()
{
return response()->json(Task::latest()->paginate(10));
}
This returns:
- 10 tasks per page
- metadata like total items, current page, last page, next page URL
Clients can request different pages like:
- /api/v1/tasks?page=2
API documentation (Swagger/OpenAPI)
Docs help other devs (or your future self) understand and use your API quickly.
What good API docs include
- endpoint list
- request headers
- request body examples
- response examples
- error responses
Common options
- Swagger (OpenAPI) for interactive docs UI
- (Laravel-friendly alternative) Laravel Scribe if you want fast auto docs
Even basic docs make your API easier to use and reduce support questions.
Monitoring & analytics (Treblle)
Once your API is live, you need visibility:
- Which endpoints are slow?
- Which requests fail most?
- What errors are happening right now?
That’s where monitoring tools help.
Treblle integration for real-time API analytics
Treblle is a monitoring tool that tracks API requests and responses so you can debug issues faster and improve performance.
What you get
- real-time request tracking
- error reporting
- response time stats
- endpoint usage analytics
Visual dashboard & observability
A dashboard helps you see:
- traffic spikes
- failure rates
- slow endpoints
- top-used routes
This is useful for catching problems before users complain.
API performance insights and logs
Treblle-style insights usually include:
- average response time per endpoint
- error breakdown (401, 422, 500, etc.)
- request/response logs (good for debugging)
- suspicious patterns (like repeated failed auth)
Conclusion
Laravel makes building REST APIs simple and reliable. It gives you clean routing, powerful controllers, and Eloquent ORM to handle data without writing complex SQL.
Features like request validation, middleware, authentication, and pagination come built in, so you can focus on your API logic instead of setup work.
With Laravel, you can move from a basic CRUD API to a secure, production-ready system without changing frameworks or rewriting code.
If you want to get better at APIs, the best next step is practice. Try building a small project like a Todo app, notes app, or simple user management API. Add features step by step, such as authentication, versioning, and pagination.
For deeper learning, check the official Laravel documentation, API authentication guides for Passport or Sanctum, and community tutorials and blogs that focus on real-world Laravel API use cases.















