I want to create a RESTful API with Apache/PHP and the Slim Framework (3.x). The API should support URI-based versioning like <host>/rest/api/v1/<resource> and <host>/rest/api/latest/<resource>.

Overall I found a solution which works, but I not really happy with my solution and I'm want to known what can I make better. I'm a rookie/newbie about Slim. I'm looking for new ideas and how I can improve my knowledge about Slim.

Every new version should be a new project, which a own independent code base. The version dispatching should be done by the Apache server and no in the PHP/Slim code. I found some example which implements different versions of the API in on project by using the group-method. But I'm not very happy with this solution. I feel better to have independent projects for independent versions.

  • General improvements or comments?
  • Do you have a better/simpler solution?
  • Do you see problems in my solution?
  • Improvements for .htaccess / rewrite rules
  • Links to real implementations of a RESTful API with Slim
  • ....

I'm looking forward for your answers and I'll be curious to see what's new.

The implementation of all version of the API are in the Apache htdoc folder with following structure


The implementation of the API are in the file index.php. Map calls like /rest/api/v1/books to my implementation I create in every version folder a .htaccess file which includes rules for the rewrite Apache module:

RewriteEngine On
RewriteRule ^ index.php [QSA,L]

In index.php I create some routes

$app->get('/books', function ...
$app->get('/books/{id}', function ...

Everything works fine if I use a explicit version in the URI (<host>/rest/api/v2/books). For convenience if I create an alias latest (/rest/api/latest/books), which redirect the alias-call to the latest version of the API version.

Therefore I create the file rest/api/.htaccess which rewrites the URI to an implementation:

RewriteEngine On
RewriteRule ^latest.*$ ./v2/index.php [QSA,L]

The rewrite rule works fine and the v2 implementation are called. But the routes don't match anymore. I found out that the original path is now part of the path.

/rest/api/v2/books -> /books
/rest/api/latest/books -> /rest/api/latest/books

If I modified the rules like this, it works - but I don't like to implement every rule twice.

$app->get('/rest/api/latest/books', function ...
$app->get('/rest/api/latest/books/{id}', function ...

Therefore I wrote a middleware function which trims /rest/api/latest from the path before the routes are matched.

$app->add(function (Request $request, Response $response, callable $next) {
    $uri = $request->getUri();
    $path = $uri->getPath();
    if (substr($path,0,16) == "/rest/api/latest") {
        $uri = $uri->withPath(substr($path,16));
        return $next($request->withUri($uri), $response);
    return $next($request, $response);

Now I can use the same rules for both cases, explicit version call and implicit version call.

| improve this question | | | | |
  • \$\begingroup\$ ...not really worth posting as a proper review, but I recommend using strpos($path, $needle) === 0 instead of substr($path, 0, 16) == $needle. gist.github.com/vicb/1596588 (It is more of a habit rather than a micro-optimization for me because there is no need to extract the substring, just check if it is at position 0. Plus, if you ever need to adjust the needle, you don't have to recount the substring length with your finger on the monitor.) \$\endgroup\$ – mickmackusa Mar 20 '19 at 2:47
  • \$\begingroup\$ @mickmackusa Thank you for your answer. Yes you are absolutely right strpos is much better/nicer than substr. I cannot use my fingers, because I have only ten :-D \$\endgroup\$ – CachingFoX Mar 20 '19 at 9:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.