Get 20% off web development packages
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.
Services Related to This Article
All ServicesWant to apply this article to your project?
If this topic is relevant to your current project, you can jump to one of the services above or browse the services page to choose the most suitable solution.