Laravel API Development: Building Professional REST & GraphQL APIs
Laravel API Development: Building Professional REST & GraphQL APIs
Expert Guide by Alaa Amer – Professional Web Developer & Applications Designer
Laravel provides powerful tools for API development. Master REST APIs, GraphQL implementation, authentication strategies, and enterprise-level API architecture patterns.
2️⃣ Advanced API Validation & Requests
Comprehensive API Request Validation:
<?php
// app/Http/Requests/Api/BaseApiRequest.php
namespace App\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class BaseApiRequest extends FormRequest
{
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(
response()->json([
'success' => false,
'message' => 'Validation failed',
'errors' => $validator->errors(),
'timestamp' => now()->toISOString()
], 422)
);
}
protected function failedAuthorization()
{
throw new HttpResponseException(
response()->json([
'success' => false,
'message' => 'This action is unauthorized',
'timestamp' => now()->toISOString()
], 403)
);
}
}
// app/Http/Requests/Api/StorePostRequest.php
namespace App\Http\Requests\Api;
class StorePostRequest extends BaseApiRequest
{
public function authorize(): bool
{
return auth()->check() && auth()->user()->can('create-posts');
}
public function rules(): array
{
return [
'title' => 'required|string|min:5|max:255|unique:posts,title',
'slug' => 'nullable|string|max:255|unique:posts,slug',
'excerpt' => 'nullable|string|max:500',
'content' => 'required|string|min:100',
'status' => 'required|in:draft,published,scheduled',
'published_at' => 'nullable|date|after:now',
'category_id' => 'required|exists:categories,id',
'tags' => 'nullable|array|max:10',
'tags.*' => 'string|max:50',
'featured_image' => 'nullable|image|mimes:jpeg,png,webp|max:2048',
'settings' => 'nullable|array',
'settings.allow_comments' => 'boolean',
'settings.is_featured' => 'boolean',
'meta_title' => 'nullable|string|max:60',
'meta_description' => 'nullable|string|max:160'
];
}
public function messages(): array
{
return [
'title.required' => 'Post title is required',
'title.min' => 'Post title must be at least 5 characters',
'title.unique' => 'A post with this title already exists',
'content.required' => 'Post content is required',
'content.min' => 'Post content must be at least 100 characters',
'category_id.required' => 'Please select a category',
'category_id.exists' => 'Selected category does not exist',
'featured_image.image' => 'Featured image must be a valid image file',
'featured_image.max' => 'Featured image size cannot exceed 2MB'
];
}
public function attributes(): array
{
return [
'category_id' => 'category',
'published_at' => 'publication date'
];
}
protected function prepareForValidation(): void
{
// Auto-generate slug if not provided
if (!$this->has('slug') && $this->has('title')) {
$this->merge([
'slug' => \Str::slug($this->title)
]);
}
// Set default published_at for scheduled posts
if ($this->status === 'scheduled' && !$this->has('published_at')) {
$this->merge([
'published_at' => now()->addHour()->toISOString()
]);
}
}
public function withValidator($validator): void
{
$validator->after(function ($validator) {
// Custom validation: scheduled posts must have future date
if ($this->status === 'scheduled') {
if (!$this->published_at || $this->published_at <= now()) {
$validator->errors()->add('published_at', 'Scheduled posts must have a future publication date');
}
}
// Custom validation: featured posts need image
if ($this->input('settings.is_featured') && !$this->hasFile('featured_image')) {
$validator->errors()->add('featured_image', 'Featured posts require an image');
}
});
}
}
// app/Rules/UniqueSlugForUser.php - Custom Validation Rule
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use App\Models\Post;
class UniqueSlugForUser implements Rule
{
protected $userId;
protected $excludeId;
public function __construct($userId, $excludeId = null)
{
$this->userId = $userId;
$this->excludeId = $excludeId;
}
public function passes($attribute, $value): bool
{
$query = Post::where('user_id', $this->userId)
->where('slug', $value);
if ($this->excludeId) {
$query->where('id', '!=', $this->excludeId);
}
return !$query->exists();
}
public function message(): string
{
return 'You already have a post with this slug.';
}
}
4️⃣ GraphQL API Implementation
Laravel GraphQL Setup:
<?php
// Install lighthouse-php/lighthouse
// composer require pusher/pusher-php-server lighthouse/lighthouse
// config/lighthouse.php
return [
'route' => [
'uri' => '/graphql',
'middleware' => [
'api',
\Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
],
'prefix' => '',
'name' => 'lighthouse',
],
'schema' => [
'register' => base_path('graphql/schema.graphql'),
],
'namespaces' => [
'models' => 'App\\Models',
'queries' => 'App\\GraphQL\\Queries',
'mutations' => 'App\\GraphQL\\Mutations',
],
];
// graphql/schema.graphql
"""
Main GraphQL Schema
"""
type Query {
posts(
first: Int = 15
page: Int = 1
orderBy: [PostOrderByClause!]
filter: PostFilter
): PostPaginator! @paginate(defaultCount: 15, maxCount: 100)
post(id: ID! @eq): Post @find
categories: [Category!]! @all
me: User @auth
}
type Mutation {
createPost(input: CreatePostInput! @spread): Post
@create
@can(ability: "create", model: "App\\Models\\Post")
updatePost(id: ID!, input: UpdatePostInput! @spread): Post
@update
@can(ability: "update", find: "id")
deletePost(id: ID!): Post
@delete
@can(ability: "delete", find: "id")
login(input: LoginInput! @spread): AuthPayload
@field(resolver: "App\\GraphQL\\Mutations\\Auth@login")
logout: LogoutResponse
@field(resolver: "App\\GraphQL\\Mutations\\Auth@logout")
@guard
}
type Post {
id: ID!
title: String!
slug: String!
excerpt: String
content: String!
status: PostStatus!
published_at: DateTime
created_at: DateTime!
updated_at: DateTime!
# Relationships
author: User! @belongsTo(relation: "user")
category: Category! @belongsTo
tags: [Tag!]! @belongsToMany
comments: [Comment!]! @hasMany
# Computed fields
reading_time: Int @method(name: "getReadingTimeAttribute")
can_edit: Boolean @method(name: "userCanEdit")
}
type User {
id: ID!
name: String!
email: String!
avatar: String
created_at: DateTime!
posts: [Post!]! @hasMany
}
type Category {
id: ID!
name: String!
slug: String!
description: String
posts_count: Int @count(relation: "posts")
}
enum PostStatus {
DRAFT @enum(value: "draft")
PUBLISHED @enum(value: "published")
SCHEDULED @enum(value: "scheduled")
}
input PostFilter {
title: String @where(operator: "like")
status: PostStatus @eq
category_id: ID @eq
published_at: DateRange @whereBetween
}
input CreatePostInput {
title: String! @rules(apply: ["required", "min:5", "max:255"])
slug: String @rules(apply: ["nullable", "unique:posts,slug"])
excerpt: String @rules(apply: ["nullable", "max:500"])
content: String! @rules(apply: ["required", "min:100"])
status: PostStatus! @rules(apply: ["required"])
category_id: ID! @rules(apply: ["required", "exists:categories,id"])
tags: [ID!] @rules(apply: ["nullable", "array"])
published_at: DateTime
}
# GraphQL Mutations
// app/GraphQL/Mutations/Auth.php
namespace App\GraphQL\Mutations;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
class Auth
{
public function login($root, array $args, GraphQLContext $context, ResolveInfo $info)
{
$credentials = $args['input'];
$user = User::where('email', $credentials['email'])->first();
if (!$user || !Hash::check($credentials['password'], $user->password)) {
throw new \Exception('Invalid credentials');
}
if ($user->isLocked()) {
throw new \Exception('Account temporarily locked');
}
$token = $user->createApiToken('GraphQL API');
$user->recordSuccessfulLogin();
return [
'user' => $user,
'access_token' => $token,
'token_type' => 'Bearer'
];
}
public function logout($root, array $args, GraphQLContext $context, ResolveInfo $info)
{
$user = auth()->user();
$user->currentAccessToken()->delete();
return [
'message' => 'Successfully logged out'
];
}
}
Advanced GraphQL Features:
<?php
// app/GraphQL/Queries/PostQueries.php
namespace App\GraphQL\Queries;
use App\Models\Post;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
class PostQueries
{
public function trending($root, array $args, GraphQLContext $context, ResolveInfo $info)
{
$days = $args['days'] ?? 7;
return Post::published()
->where('published_at', '>=', now()->subDays($days))
->withCount(['comments', 'likes'])
->orderByDesc('likes_count')
->limit($args['limit'] ?? 10)
->get();
}
public function search($root, array $args, GraphQLContext $context, ResolveInfo $info)
{
$query = $args['query'];
return Post::published()
->where(function ($q) use ($query) {
$q->where('title', 'LIKE', "%{$query}%")
->orWhere('content', 'LIKE', "%{$query}%")
->orWhereFullText(['title', 'content'], $query);
})
->with(['user', 'category'])
->paginate($args['first'] ?? 15);
}
}
// app/GraphQL/Scalars/Upload.php - File Upload Support
namespace App\GraphQL\Scalars;
use GraphQL\Type\Definition\ScalarType;
use Illuminate\Http\UploadedFile;
class Upload extends ScalarType
{
public function serialize($value)
{
return $value;
}
public function parseValue($value)
{
if ($value instanceof UploadedFile) {
return $value;
}
throw new \Exception('Value must be an uploaded file');
}
public function parseLiteral($valueNode, array $variables = null)
{
throw new \Exception('Upload scalar can only parse variables');
}
}
Next Steps
Learn comprehensive Testing in Laravel to ensure your APIs are bulletproof and maintainable.
📩 Need help with Laravel APIs?
Article Category
Laravel API Development: Building Professional REST & GraphQL APIs
Master Laravel API development with REST, GraphQL, authentication, versioning, documentation, testing, and performance optimization for enterprise applications.
Consultation & Communication
Direct communication via WhatsApp or phone to understand your project needs precisely.
Planning & Scheduling
Creating clear work plan with specific timeline for each project phase.
Development & Coding
Building projects with latest technologies ensuring high performance and security.
Testing & Delivery
Comprehensive testing and thorough review before final project delivery.