A RESTful server implementation for Codeigniter 3 based on CodeIgniter RestServer
- PHP:
>=7.4
- Codeigniter:
^3.1.0
- Composer
N.B: The current version
1.0.*
requires php 7.4 or higher (php supported versions)
This library uses Composer to by installed.
Run this command (recommended) in the same path as your composer.json
file:
composer require moudarir/codeigniter-rest-api
Or, In your composer.json
file, add the following code in require
section:
{
"require": {
"moudarir/codeigniter-rest-api": "^1.0"
}
}
And then run:
composer install
Language/Translation
You can find the file associated with your language in the application/languages/
folder. Based on the $config['language']
setting in your application/config/config.php
configuration file.
Supported languages:
- English
- French
- Arabic
Some changes to perform
The first thing to do is copy the rest-api.php
configuration file into the config
folder of your codeigniter application
Tip: If you find that the default configuration works for you, then copying the configuration file is optional.
Make sure that the enable_hooks
, subclass_prefix
and composer_autoload
keys in application/config/config.php
file are set as following:
$config['enable_hooks'] = true;
$config['subclass_prefix'] = 'Core';
$config['composer_autoload'] = true; // Or the path to 'autoload.php' file. Ex: APPPATH.'vendor/autoload.php'
Next, append the following code to your application/config/hooks.php
file:
$hook['pre_system'][] = [
'class' => 'InitAppCore',
'function' => 'initialize',
'filename' => 'InitAppCore.php',
'filepath' => 'hooks'
];
Then, create a new file called InitAppCore.php
in application/hooks/
folder and put the following code:
<?php
defined('BASEPATH') || exit('No direct script access allowed');
/**
* @property InitAppCore
*/
class InitAppCore
{
/**
* @return void
*/
public function initialize(): void
{
spl_autoload_register([__CLASS__, 'customCores']);
}
/**
* @param string $class_name
*/
public function customCores(string $class_name)
{
if (strpos($class_name, 'CI_') !== 0) {
$class_file = $class_name.'.php';
if (is_readable(APPPATH.'core'.DIRECTORY_SEPARATOR.$class_file)) {
require_once(APPPATH.'core'.DIRECTORY_SEPARATOR.$class_file);
}
}
}
}
And now, we can create a custom core class to use in our Controllers.
Referring to subclass_prefix
param, we should prefix the class name with Core
, Ex: /application/core/CoreApi.php
.
<?php
defined('BASEPATH') || exit('No direct script access allowed');
use Moudarir\CodeigniterApi\Http\Server;
class CoreApi extends Server
{
/**
* CoreServer constructor.
*/
public function __construct()
{
parent::__construct();
}
}
IMPORTANT
Execute the dumping/queries.sql
file to create the tables needed for the API to work properly.
Tables that will be created are users
, api_keys
and api_key_logs
.
You're now ready to begin using the library ๐.
A word about controller methods and requests
Basicly, There is a relationship between controller method
and request method
.
If you send a GET
request, the controller method MUST BE named as indexGet
Request examples:
GET /users HTTP/1.1 => indexGet
GET /users/1 HTTP/1.1 => indexGet
POST /users HTTP/1.1 => indexPost
POST /users/login HTTP/1.1 => loginPost
Adding some routes for the next example in application/config/routes.php
file.
$route['users'] = [
"get" => "users",
"post" => "users",
"head" => "users",
"options" => "users"
];
$route['users(\.)([a-zA-Z0-9_-]+)(.*)'] = [
"get" => "users/format/$2$3",
"post" => "users/format/$2$3"
];
$route['users/([0-9]+)'] = [
"get" => "users/index/id/$1",
"put" => "users/index/id/$1"
];
$route['users/([0-9]+)(\.)([a-zA-Z0-9_-]+)(.*)'] = [
"get" => "users/index/id/$1/format/$3$4",
"put" => "users/index/id/$1/format/$3$4"
];
$route['users/login'] = [
"post" => "users/login"
];
$route['users/login(\.)([a-zA-Z0-9_-]+)(.*)'] = [
"post" => "users/login/format/$2$3"
];
And now, we can create our application/controllers/Users.php
controller:
<?php
defined('BASEPATH') || exit('No direct script access allowed');
use Firebase\JWT\JWT;
use Moudarir\CodeigniterApi\Models\ApiKey;
use Moudarir\CodeigniterApi\Models\User;
/**
* @property Users
*/
class Users extends CoreApi
{
/**
* ApiUsers constructor.
*/
public function __construct()
{
parent::__construct();
}
/**
* @see ApiUsers::indexGet()
*/
public function indexGet()
{
$id = $this->get('id');
$entity = new User();
if ($id !== null) {
if ((int)$id <= 0) {
self::getResponse()->badRequest();
}
$item = $entity->find($id);
if ($item === null) {
self::getResponse()->notFound();
}
self::getResponse()->ok(['item' => $item]);
}
$options = [
'page' => $this->get('page'),
'limit' => $this->get('limit')
];
$total = $entity->count($options);
$response = [
'total' => $total,
'items' => $total === 0 ? [] : $entity->all($options),
];
if ($options['page'] !== null) {
$response['page'] = (int)$options['page'] === 0 ? 1 : (int)$options['page'];
}
self::getResponse()->ok($response);
}
/**
* @see ApiUsers::indexPost()
*/
public function indexPost()
{
$post = $this->post();
$errors = [];
if (array_key_exists('email', $post)) {
$email = $this->post('email');
if (empty($email) || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
$errors['email'] = "This field is not a valid email address.";
}
} else {
$errors['email'] = "This field is required.";
}
if (!empty($errors)) {
self::getResponse()->error($errors);
}
$hashedPassword = password_hash($post['password'], PASSWORD_ARGON2I, [
'memory_cost' => 1 << 12, // 4MB
'time_cost' => 2,
'threads' => 2
]);
$this->db->trans_start();
$data = [
'firstname' => $post['firstname'],
'lastname' => $post['lastname'],
'email' => $post['email'],
'password' => $hashedPassword,
];
$user_id = (new User())->add($data);
if ($user_id === null) {
$this->db->trans_rollback();
self::getResponse()->error("Error occurred during account creation.");
}
$akEntity = new ApiKey();
$akData = [
'user_id' => $user_id,
'key' => $akEntity->setKey(),
'username' => $akEntity->setUsername(),
'password' => $akEntity->setPassword(),
];
$api_key_id = $akEntity->add($akData);
if ($api_key_id === null) {
$this->db->trans_rollback();
self::getResponse()->error("Error occurred during account creation.");
}
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
} else {
$this->db->trans_commit();
}
self::getResponse()->ok([
'message' => "User account created successfully.",
'data' => [
'user_id' => $akData['user_id'],
'api_key' => $akData['key'],
'username' => $akData['username'],
'password' => $akData['password'],
]
]);
}
/**
* @see ApiUsers::indexPut()
*/
public function indexPut()
{
self::getResponse()->ok([
'data' => [
'info' => $this->getAuthData(),
'args' => $this->put()
]
]);
}
/**
* @see ApiUsers::loginPost()
*/
public function loginPost()
{
$secret = getenv("JWT_SECRET");
$secret !== false || $secret = $this->getApiConfig()['jwt_secret'];
$user = (new User())->find($this->getApiKey()['user_id']);
$payload = [
'iss' => 'http://example.org',
'aud' => 'http://example.com',
'iat' => 1356999524,
'nbf' => 1357000000,
'exp' => time() + (60 * 60),
'user' => [
'user_id' => $user['id'],
'firstname' => $user['firstname'],
'lastname' => $user['lastname'],
'email' => $user['email'],
]
];
self::getResponse()->ok([
'data' => [
'jwt_key' => JWT::encode($payload, $secret, 'HS256'),
]
]);
}
}
Authentication methods
The Rest Server can be used/combined with basic
and Bearer
authorization type. However, it's can be used without any authorization type (not secure
)
Downloaded our Postman collection, and import it into Postman.
We have also provided a Postman Environment that you need to import as well.
Note: To understand what Postman environments are, please check this link.
Edit the environment variables
Update the endpoint
variable to point to your Rest server. Ex: (https//myapi.com/) with trailing slash.
โจ That's it!
You can now use the Postman collection to test available requests.
Note: In the Postman collection, the order of execution of the requests must be respected.