PHP Luminova: How to Implement a RESTful API with CRUD Operations
This guide outlines the basic steps to implement a REST API with full CRUD operations using the Luminova Framework.
This guide demonstrates how to build a RESTful API in the Luminova PHP framework, supporting full CRUD (Create, Read, Update, Delete) operations using HTTP methods like GET
, POST
, PUT
, and DELETE
.
Prerequisites
Before starting, ensure you have:
- Luminova PHP Framework installed.
- A working database (e.g., MySQL).
Step 1: Define API Routes
Routes map client requests to specific controller actions. In Luminova, routes can be defined using method-based or attribute-based routing.
Method-Based Routing
Routes are defined in the /routes/api.php
file using method chaining, and linked to corresponding controller methods.
// /routes/api.php
<?php
$router->bind('/v1', function(Router $router) {
$router->middleware('ANY', '/posts/(:root)', 'PostV1Controller::auth');
$router->any('/', 'PostV1Controller::error');
// CRUD routes for posts
$router->get('/posts', 'PostV1Controller::index'); // Retrieve all posts
$router->get('/posts/(:int)', 'PostV1Controller::show'); // Retrieve a specific post by ID
$router->post('/posts', 'PostV1Controller::create'); // Create a new post
$router->put('/posts/(:int)', 'PostV1Controller::update'); // Update an existing post
$router->delete('/posts/(:int)', 'PostV1Controller::delete'); // Delete a post by ID
});
- Middleware: Protects the routes with authentication.
- Error handling: Ensures that invalid routes trigger the
error
method.
Attribute-Based Routing
With attribute-based routing, routes are defined directly on controller methods using PHP attributes.
// /app/Controllers/Http/PostV1Controller.php
<?php
namespace App\Controllers\Http;
use Luminova\Base\BaseController;
use Luminova\Attributes\Route;
use Luminova\Attributes\Prefix;
use App\Controllers\Errors\ViewErrors;
#[Prefix(pattern: '/api/v1/(:root)', onError: [ViewErrors::class, 'onRestError'])]
class PostV1Controller extends BaseController
{
#[Route('/api/v1/posts', methods: ['GET'])]
public function index(): int {}
#[Route('/api/v1/posts/(:int)', methods: ['GET'])]
public function show(int $id): int {}
#[Route('/api/v1/posts', methods: ['POST'])]
public function create(): int {}
#[Route('/api/v1/posts/(:int)', methods: ['PUT'])]
public function update(int $id): int {}
#[Route('/api/v1/posts/(:int)', methods: ['DELETE'])]
public function delete(int $id): int {}
#[Route('/api/v1/posts/(:root)', middleware: 'before', methods: ['ANY'])]
public function auth(): int {}
#[Route('/api/v1/(:root)', error: true, methods: ['ANY'])]
public function error(): int {}
private function rules(): void {}
}
- Attributes: Define routes directly in the controller, specifying HTTP methods and additional configurations like middleware.
- Prefix Handling: Routes are scoped under
/api/v1/*
, with a global error handler for non-existent endpoints.
Step 2: Define the Model
The model represents the Post
entity and interacts with the database.
// app/Models/Post.php
<?php
namespace App\Models;
use Luminova\Base\BaseModel;
class Post extends BaseModel
{
protected string $table = 'posts'; // Database table
protected array $updatable = ['title', 'content']; // Updatable fields
protected array $insertable = ['pid', 'title', 'content']; // Insertable fields
protected string $primaryKey = 'pid'; // Primary key
protected bool $cacheable = false; // Disable caching
}
- Fields: Define which fields are mass assignable for updates and inserts.
- Primary Key: Set the primary key for the model.
Step 3: Create Controller Methods
Each controller method in the PostV1Controller
class handles a specific CRUD operation, processing incoming API requests and interacting with the Post
model.
auth
Middleware Authentication:
public function auth(): int
{
$ok = false;
if(($bearer = $this->request->getAuth()) !== null) {
$ok = Auth::authorize(
$bearer,
escape($this->request->header->get('user_id'))
);
}
if(!$ok){
response(401)->send(['message' => 'Invalid credentials']);
}
return $ok ? STATUS_SUCCESS : STATUS_ERROR;
}
- HTTP Method:
ANY /api/v1/posts
- Functionality: This method handles authentication for requests made to the
/api/v1/posts
endpoint. It checks for a Bearer token from theAuthorization
header and theuser_id
from the request headers. If a valid token is found, it calls theAuth::authorize()
method to verify the user's identity. The method returns a success (STATUS_SUCCESS
) if authentication passes, otherwise it returns an error (STATUS_ERROR
). If no Bearer token is provided, it also defaults to returning an error status. This ensures that only authenticated users can access the API.
index
Retrieve All Posts:
public function index(Post $post): int
{
$data = $post->select();
if($data){
return response(200)->json($data);
}
return response(404)->json(['message' => 'No post found']);
}
- HTTP Method:
GET /api/v1/posts
- Functionality: Fetches all posts from the database. Returns a
200 OK
status with data if posts exist; otherwise, returns a204 No Content
status if no posts are found.
show
Retrieve a Single Post:
public function show(int $id, Post $post): int
{
$data = $post->find($id);
if($data){
return response(200)->json($data);
}
return response(404)->json(['message' => 'No post found']);
}
- HTTP Method:
GET /api/v1/posts/{id}
- Functionality: Fetches a specific post by its ID. Returns
200 OK
with the post data if found, or204 No Content
if the post does not exist.
create
Create a New Post:
public function create(Post $post): int
{
$response = ['status' => 2011, 'message' => 'Unable to add post.'];
$this->rules();
if ($this->validate->validate($this->request->getBody())) {
$ok = $post->insert([
'pid' => func()->random(5),
'title' => escape($this->request->getPost('title')),
'content' => escape($this->request->getPost('content')),
]);
if ($ok) {
$response = ['status' => 2001, 'message' => 'Post added successfully.'];
}
} else {
$response['message'] = $this->validate->getErrorLine();
}
return response(200)->json($response);
}
- HTTP Method:
POST /api/v1/posts
- Functionality: Creates a new post using the provided
title
andcontent
. Returns200 OK
if successful, or an error message if the creation fails. - Validation: Ensures that both the title and content are provided and meet the validation criteria before inserting the post.
update
Update an Existing Post:
public function update(int $id, Post $post): int
{
$response = ['status' => 4011, 'message' => 'Unable to update post.'];
$this->rules();
if ($this->validate->validate($this->request->getBody())) {
$ok = $post->update($id, [
'title' => escape($this->request->getPost('title')),
'content' => escape($this->request->getPost('content')),
]);
if ($ok) {
$response = ['status' => 2001, 'message' => 'Post updated successfully.'];
}
} else {
$response['message'] = $this->validate->getErrorLine();
}
return response(200)->json($response);
}
- HTTP Method:
PUT /api/v1/posts/{id}
- Functionality: Updates an existing post by its ID. Requires a
title
andcontent
in the request body. Returns200 OK
if the update is successful or an error message if the operation fails.
delete
Delete a Post:
public function delete(int $id, Post $post): int
{
$response = ['status' => 2001, 'message' => 'Post deleted successfully.'];
if (!$post->delete($id)) {
$response['message'] = 'Unable to delete post.';
}
return response(200)->json($response);
}
- HTTP Method:
DELETE /api/v1/posts/{id}
- Functionality: Deletes a post by its ID. Returns
204 No Content
if the deletion is successful, or an error message if the operation fails.
error
Invalid Endpoint:
public function error(): int
{
return response(501)->send(['error' => 'Endpoint not implemented']);
}
- HTTP Method: Any invalid or unimplemented request to
/api/v1/
- Functionality: Handles requests to invalid or unimplemented API endpoints within the
/api/v1/
route. Returns a501 Not Implemented
status code with a JSON response that includes an error message, indicating that the requested endpoint is not available.
rules
Define Validation Rules:
private function rules(): void
{
$this->validate->rules = [
'title' => 'required|string|max(255)',
'content' => 'required|string',
];
$this->validate->messages = [
'title' => [
'required' => 'Post title is required.',
'string' => 'Post title must be a string.',
'max' => 'Post title cannot exceed 255 characters.',
],
'content' => [
'required' => 'Post content is required.',
'string' => 'Post content must be a string.',
],
];
}
- Purpose: Defines validation rules for
title
andcontent
fields used in thecreate()
andupdate()
methods. - Validation Rules:
title
: Must be a required string with a maximum length of 255 characters.content
: Must be a required string.