Slim Framework CRUD


  1. PHP + Mysql (With Mysqlnd extension)
  2. Composer:

Download Slim

  1. Using Composer
composer require slim/slim

Make sure to run Slim Framework download command inside /slim-crud directory. It will create a new folder called /vendor. Please see below section.

Folder structure

    - Config.php
    - DbConnect.php
    - DbHandler.php
    - PassHash.php
    - Utils.php
    - .htaccess
    - index.php
    - [Slim Framework library dir]

Pretty Urls

Instead of accessing your index.php by 'ugly' URL like localhost/slim-crud/v1/index.php, you might wanna have a prettier one without the index.php at the end of your URL. This might take a few steps involving server config (I'm using apache2). Please refer to this post under Setting up the server and Virtual host section for server configuration.

This step allows you to access this project via custom virtual host.

  1. Inside /v1, create a file called .htaccess
  2. Write the following code in it:
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ %{ENV:BASE}index.php [QSA,L]

Database setup

  1. Create a database called... anything you like. Let's say crud-db
  2. Run this query to create the table structures:

Project config classes

  • Config.php - global variables defined here
  • DbConnect.php - database connection config
  • DbHandler.php - database business logic
  • PassHash.php - generates password hash for login feature
  • Utils.php - custom utility class

1. Config.php

/** Database config */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'root');
define('DB_HOST', 'localhost');
define('DB_NAME', 'crud-db');

define('USER_CREATE_FAILED', 1);

/** Debug modes */
define('PHP_DEBUG_MODE', true);
define('SLIM_DEBUG', true);

2. DbConnect.php

class DbConnect {

    private $conn;

    function __construct(){ }

    function connect(){
      include_once dirname(__FILE__) . '/Config.php';
      $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
      if (mysqli_connect_errno()) {
        echo "Failed to connect to Mysql: " . mysqli_connect_error();
      return $this->conn;

3. DbHandler.php

Only showing function names for brevity.

Full code:

class DbHandler {

    private $conn;

    function __construct() { ... }
    public function createUser($name, $email, $password) { ... }
    public function checkLogin($email, $password) { ... }
    private function isUserExists($email) { ... }
    public function getUserByEmail($email) { ... }
    public function getApiKeyById($user_id) { ... }
    public function getUserId($api_key) { ... }
    public function isValidApiKey($api_key) { ... }
    private function generateApiKey() { ... }
    public function createTask($user_id, $task) { ... }
    public function getTask($task_id, $user_id) { ... }
    public function getAllUserTasks($user_id) { ... }
    public function updateTask($user_id, $task_id, $task, $status) { ... }
    public function deleteTask($user_id, $task_id) { ... }
    public function createUserTask($user_id, $task_id) { ... }


4. PassHash.php

class PassHash {

  // blowfish
  private static $algo = '$2a';
  // cost parameter
  private static $cost = '$10';

  // internal use
  public static function unique_salt() {
    return substr(sha1(mt_rand()),0,22);

  // generate hash
  public static function hash($password) {
    return crypt($password, self::$algo . self::$cost . '$' . self::unique_salt());

  // compare password and hash
  public static function check_password($hash, $password) {
    $full_salt = substr($hash, 0, 29);
    $new_hash = crypt($password, $full_salt);
    return ($hash == $new_hash);

5. Utils.php

Showing function names for brevity.

Full code:

require '../vendor/autoload.php';
require_once 'Config.php';

// (Optional) Set PHP_DEBUG_MODE to true in Config.php to display PHP error
  ini_set('display_errors', 'On');

// Used to recognize user session by authorization header
// Global variable
$user_id = NULL;

* Verifying required params posted or not
function verifyRequiredParams($required_fields) { ... }

* Validating email address
function validateEmail($email){ ... }

* Echo json response
* @param String $status_code http response code
* @param Int $response Json response
function echoResponse($status_code, $response) { ... }

* Adding Middle Layer to authenticate every request
* Checking if the request has valid api key in the 'Authorization' header
* returns true if $api_key in Authorization Header is correct
function authenticate(\Slim\Route $route) { ... }

* (Optional) Debugging utility
function p($input, $exit=1) { ... }
function j($input, $encode=true, $exit=1) { ... }

Restful API routes

Description Route Method Params Authorization Header
Register a user /register POST name, email, password No
Login /login POST email, password No
Creating a new task /tasks POST task Yes
Listing all tasks of authorized user /tasks GET Yes
Listing single task of authorized user /tasks/:task_id GET Yes
Updating a task /tasks/:task_id PUT task, status Yes
Deleting a task /tasks/:task_id DELETE Yes

Using Slim

Showing functions for brevity. Description of lines are written in the comment.

Full code:

// Include config files
require_once '../include/DbHandler.php';
require_once '../include/PassHash.php';
require_once '../include/Utils.php';
require '../vendor/autoload.php';

// Instantiate Slim object
$app = new \Slim\Slim();

// (Optional) Check if debug is true, show Slim debug report

// Route test START
$app->get('/', function () {
    echo "Hello World";
$app->get('/test/:name', function ($name) {
    echo "Hello, $name";
// Route test ENDS

// Project routes START
$app->post('/register', function() use ($app) { ... );
$app->post('/login', function() use ($app) { ... );
$app->post('/tasks', 'authenticate', function() use ($app){ ... );
$app->get('/tasks', 'authenticate', function(){ ... );
$app->get('/tasks/:task_id', 'authenticate', function($task_id){ ... );
$app->put('/tasks/:task_id', 'authenticate', function($task_id) use($app) { ... );
$app->delete('/tasks/:task_id', 'authenticate', function($task_id) use($app) { ... );
// Project routes END

// Run Slim

Basic workflow of routes

  • Verify required parameters using verifyRequiredParams() function
  • Validate email if necessary
  • Consume DbHandler function for database queries
  • Using 'authenticate' in each routes that needs API key authorization
    • Each user have their own unique API key for authentication
    • Authenticated User ID is stored in the $user_id global var to be passed for db query
    • More information on Middle ware
  • Prints out appropriate JSON response using echoResponse() function

Testing routes

  1. Use REST Easy addon for Firefox to test these API routes
  2. Enter the URL of one of the routes, example http://localhost/slim-crud/v1/ with GET method
  3. Or you can use curl for testing

GET: http://localhost/slim-crud/v1/


Hello World

GET: http://localhost/slim-crud/v1/test/najib

najib is a parameter set in the route (:name). The parameter is passed to be consumed by /test/:name route function.


Hello, najib


Let's try to register a user.

POST: http://localhost/slim-crud/v1/register



{"error":false,"message":"You are successfully registered"}

Hooray! Let's create a task.

POST: http://localhost/slim-crud/v1/tasks


  • task: beli jet untuk abang jib


  • Authorization: user api key here. take from database


{"error":false,"message":"Task created successfully","task_id":4}


Now, would you like to see all of tasks of one particular user? Firstly, You need to acquire the user's API key (just access your database and take it from there). In practice, you would want to acquire the API key from login, and add it to HTTP Header using setHeader() in PHP.

GET: http://localhost/slim-crud/v1/tasks


  • Authorization: user api key here. check in database


{"error":false,"id":4,"task":"beli jet untuk abg jib","status":0,"createdAt":"2015-07-05 08:53:06"}

To get one particular task of an authorized user, simply pass a task id in the URL.

GET: http://localhost/slim-crud/v1/tasks/4


  • Authorization: user api key here. check in database


{"error":false,"id":4,"task":"beli jet untuk abg jib","status":0,"createdAt":"2015-07-05 08:53:06"}


PUT: http://localhost/slim-crud/v1/tasks/4


  • task: beli jet untuk abang jib tersayang
  • status: 1


  • Authorization: user api key here. take from database


{"error":false,"message":"Task updated successfully"}


Simply pass a task id to delete route to remove the task from database.

DELETE: http://localhost/slim-crud/v1/tasks/4


  • Authorization: user api key here. take from database


{"error":false,"message":"Task deleted succesfully"}


For best practice, define your routes according to what it does. For example, delete route should be tasks/remove/:id. This will make it easier for you to know which route does what.

Use p() or j() function in Utils to debug if any unseen error occurred. This function is so handy that if you use it right, could tell you which line of your code has error. Basic usage: p(1) will print 1 at that line and die.

Issues I've encountered

  1. 404 error page - might caused by non-existant or disabled mysqlnd extension in apache2 config. Enable it or install if you haven't already. Fix
  2. Check if include and include_once directory is correct


Show Comments

Get the latest posts delivered right to your inbox.