When building with Laravel, it’s easy to fall into common traps that affect performance, security, and scalability. In this guide, we’ll break down the most frequent Laravel API mistakes and show you how to avoid them with best practices.
Avoiding these Laravel API mistakes helps you write more reliable, scalable endpoints.
1. Returning Raw Models
❌ Wrong Example:
public function show($id)
{
return User::find($id);
}
❌ What happens here:
- Sends everything from the database table (including
password
,created_at
, etc.) - You lose control over what the API exposes.
- Any future column added to the model will be auto-exposed unless you manually hide it.
✅ Correct Example:
// app/Http/Resources/UserResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
// In Controller:
public function show($id)
{
$user = User::findOrFail($id);
return new UserResource($user);
}
✅ Why this is better:
- Controlled output: You choose exactly which fields to return.
- Security: No chance of leaking private data like hashed passwords or internal IDs.
- Consistency: Every API response looks clean and structured.
2. Mixing Logic in Controllers
❌ Wrong Example:
public function store(Request $request)
{
$user = new User();
$user->name = $request->name;
$user->email = $request->email;
$user->password = bcrypt($request->password);
$user->save();
return response()->json($user);
}
❌ Why this is bad:
- Controller is doing everything: receiving the request, creating the model, hashing the password.
- Violates Single Responsibility Principle.
- Hard to test and reuse this logic.
✅ Correct Example:
Step 1: Create a FormRequest
php artisan make:request StoreUserRequest
Step 2: In StoreUserRequest.php
public function rules()
{
return [
'name' => 'required|string',
'email' => 'required|email|unique:users',
'password' => 'required|min:6',
];
}
Step 3: Create a Service
// app/Services/UserService.php
class UserService {
public function create(array $data)
{
$data['password'] = bcrypt($data['password']);
return User::create($data);
}
}
Step 4: Use in Controller
public function store(StoreUserRequest $request, UserService $userService)
{
$user = $userService->create($request->validated());
return new UserResource($user);
}
✅ Why this is better:
- Validation is reusable and testable.
- Business logic is handled in a service (not the controller).
- Clean structure → controller is focused only on HTTP-related stuff.
3. No API Versioning
❌ Wrong Example:
Route::get('/users', 'UserController@index');
❌ Why it’s bad:
- If you change the response format later, all your users/clients break.
- You can’t run multiple API versions at once.
✅ Correct Example:
Route::prefix('v1')->group(function () {
Route::get('/users', [UserController::class, 'index']);
});
✅ Why this is better:
- You can safely release
/api/v2/users
later with new logic or format. - Allows backward compatibility for older apps.
- Gives you flexibility as your API grows.
4. No Exception Handling (Letting Laravel Dump Errors)
❌ Wrong Example:
public function show($id)
{
return User::findOrFail($id); // If not found, Laravel throws ugly exception
}
❌ Why it’s bad:
- Laravel will dump a full stack trace.
- In production, users might see database errors or file paths. Not safe.
✅ Correct Example:
public function show($id)
{
try {
$user = User::findOrFail($id);
return new UserResource($user);
} catch (ModelNotFoundException $e) {
return response()->json(['message' => 'User not found'], 404);
}
}
✅ Why this is better:
- User-friendly and secure error messages.
- Avoids exposing internal Laravel or database details.
5. Inline Validation Instead of Form Requests
❌ Wrong Example:
public function store(Request $request)
{
$request->validate([
'title' => 'required|string',
'body' => 'required|string'
]);
}
❌ Why it’s bad:
- Controller becomes messy.
- Not reusable or testable.
- Validation logic is hidden deep inside the controller.
✅ Correct Example:
// Create PostRequest
php artisan make:request StorePostRequest
// In StorePostRequest.php
public function rules()
{
return [
'title' => 'required|string',
'body' => 'required|string'
];
}
// In Controller:
public function store(StorePostRequest $request)
{
Post::create($request->validated());
}
✅ Why this is better:
- Clean controller.
- Validation is now centralized, reusable, and testable.
- Laravel automatically sends validation error responses.
6. Not Using Resource Collections for Lists
❌ Wrong Example:
public function index()
{
return User::all();
}
❌ Why it’s bad:
- Sends all fields for each user.
- No formatting or transformation.
- Hard to add pagination later.
✅ Correct Example:
Resource Collection:
// app/Http/Resources/UserResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
Controller:
public function index()
{
$users = User::paginate(10);
return UserResource::collection($users);
}
✅ Why this is better:
- You control the output format.
- Ready for pagination.
- Consistent structure for every item in the list.
7. Not Using Eager Loading (N+1 Problem)
❌ Wrong Example:
public function index()
{
$posts = Post::all(); // Now each post will run its own DB query for the author
foreach ($posts as $post) {
$post->user->name;
}
return $posts;
}
❌ What’s wrong here:
- If you have 100 posts, Laravel runs 101 queries!
- That’s the N+1 query problem — big performance killer.
✅ Correct Example:
public function index()
{
$posts = Post::with('user')->get(); // One query for posts, one for users
return PostResource::collection($posts);
}
✅ Why this is better:
- Only 2 database queries: one for posts, one for users.
- Efficient and scalable.
- Works great with Resources too.
8. Hardcoding HTTP Status Codes and Messages
❌ Wrong Example:
return response()->json(['error' => 'Not found'], 404);
❌ Why it’s bad:
- You repeat messages everywhere.
- Hard to localize or maintain error formats.
✅ Correct Example:
Use Laravel’s helper methods and constants:
use Symfony\\Component\\HttpFoundation\\Response;
return response()->json([
'message' => 'User not found',
], Response::HTTP_NOT_FOUND);
Or better yet, use a helper response structure:
// app/Traits/ApiResponse.php
trait ApiResponse {
protected function success($data, $code = 200) {
return response()->json([
'status' => true,
'data' => $data
], $code);
}
protected function error($message, $code = 400) {
return response()->json([
'status' => false,
'message' => $message
], $code);
}
}
✅ Why this is better:
- Cleaner code everywhere.
- Consistent response structure (important for frontend).
- Easier debugging and logging.
9. Not Using API Resources for Nested Relationships
❌ Wrong Example:
public function show($id)
{
$order = Order::with('user')->find($id);
return response()->json($order);
}
❌ Why this is bad:
- Sends raw user model inside order.
- You lose control of nested data too.
✅ Correct Example:
Step 1: Create UserResource
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'name' => $this->name,
'email' => $this->email,
];
}
}
Step 2: Use inside OrderResource
class OrderResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'total' => $this->total,
'user' => new UserResource($this->whenLoaded('user')),
];
}
}
Step 3: Controller
public function show($id)
{
$order = Order::with('user')->findOrFail($id);
return new OrderResource($order);
}
✅ Why this is better:
- Full control over nested data.
- Keeps API responses consistent and minimal.
- Future-proof as you grow.
Summary of Laravel API Mistakes and Best Practices:
To sum up, avoiding Laravel API mistakes is key to writing clean, secure, and scalable applications. From returning raw models and skipping validation to ignoring API resources and eager loading—each mistake can slow you down or break your app. By following best practices and writing structured, consistent code, you’ll save time, reduce bugs, and build better APIs. Keep learning and improving—Laravel gives you the tools, it’s up to you to use them wisely!
For more insightful tutorials, visit our Tech Blogs and explore the latest in Laravel, AI, and Vue.js development!