Kirby REST API via HTTP in JSON format

Thank you guys for your help!

@jevets: Yes, stumbled upon the quotes by mistake – already corrected. Unfortunately it doesn’t fix it for me. At least the route seems to work now as it doesn’t load the Error-template anymore, but I still get an Internal Server Error upon accessing http://localhost:3000/api/v1/blog in the browser or when trying to log it to the console.

@rppld Can you try it with the built-in php server? php -S localhost:9000 or some other port?

Do you still get a 500 at localhost:9000/api/v1/blog?

What worked for me is to replace the $collection line with this:

$collection = page('blog')->children()->visible();

Yes, that did in fact work! http://localhost:3000/api/v1/blog is now outputting Array<br />Array<br />Array and the error 500 is gone!

How would I go about accessing this data elsewhere now?

$.getJSON('/api/v1/blog', function(r) {
 console.log(r);
});

This snippet returns nothing in the console.

Just a side note:

I think the reason page('blog') works and pages()->find('blog') doesn’t is because these routes are loaded before pages() (or $pages) have been populated with an index of all the site’s pages.

@rppld Are you running this from a javascript file or directly in the browser’s console?

$.getJSON('/api/v1/blog', function(r) {
 console.log(r);
});

Alright guys, I solved it: The $data array needs to be converted to a json string when returning it:

return response::json(json_encode($data));

@jevets: I’m running the snippet in the sites footer.
Thanks again for your help, @lukasbestle and @jevets!

@rppld Sure thing!

By the way, there’s also collection::toArray and collection::toJson

response::json() should convert whatever data is passed to it to json automatically.

I’ve found doing this can be more explicit and easier to figure out in the future, when working with collections of pages (instead of a single page object):

// in the route
    'action' => function() {
      // get the article pages
      $articles = page('blog')->children()->visible();
      $data = [];

      // Transform for API delivery
      foreach ($articles as $article) 
      {
        $data[] = $article->serialize(); 
      }

      // Now you're sending a plain old array to `response::json()`
      return response::json($data);
    }

I was just trying to optimize it :relaxed: Yes, that’s way more elegant, thanks!

For future reference for us and others…

I’ve found that when using kirby()->routes() in this way from a plugin, there’s no way to specify route filters.

The alternative way to setup routes is instantiate a router yourself, something like this:

$router = new \Router();

$router->filter( /* ... */ );

$router->route( /* ... */ );

// But then you'll have to run the router and exit,
// which isn't quite as nice-looking in my opinion

@lukasbestle Do you happen to know of any way to add route filters to kirby() from plugins?

This kind of stuff doesn’t work:

kirby()->router()->filter( /* ... */ );

kirby()->routes()->filter( /* ... */ );

Nope, unfortunately that won’t work. The router itself is started after the plugins have been loaded, kirby()->routes() only adds the routes to an array that is used to initialize the router with.

Thanks @lukasbestle

Is there any way to add route filters to kirby’s router (after plugins are loaded)? Or must one create his own Router instance to use user-defined route filters?

Generally that would be kirby()->router()->filter( /* ... */ );, but as the router is run immediately after it is created, the filters won’t apply at all, no matter when adding the filter.

I recommend using your own router. That makes you completely independent from the built-in router and is not that much more code. :wink:

Thanks again, @lukasbestle

For future reference on the topic, you’ll probably eventually need to filter your routes, i.e. to make sure a user is logged in or to make sure the user has permission to access the resource. You can’t add filters when using kirby()->routes() in a plugin, but you can instantiate your own router.

Use your own router (see toolkit/routing docs)

In plugins, do this instead of using kirby()->routes():

  • instantiate your own router
  • register your filters and routes
  • run your router

(This code is untested but should be enough to get you going)

// 1. Instantiate your own router

$router = new \Router();


// 2. Register your filters and routes

// filters
$router->filter('auth', function() {
    if (!site()->user()) go('login');
});
$router->filter('guest', function() {
    if (site()->user()) go('dashboard');
});

// routes
$router->register('login', [
    'method' => 'GET|POST',

     // this will redirect the user if she's already logged in
    'filter' => 'guest',

    'action' => function() {
        // GET shows a login form
        // POST attempts to log in the user
    }
]);
$router->register('logout', [
    'filter' => 'auth',
    'action' => function() {
        if (site()->user()) site()->user()->logout();

        go('login');
    }
]);
$router->register('dashboard', [
    'filter' => 'auth',
    'action' => function() {
        // show the logged-in user his dashboard
    }
]);


// 3. Run your router

$route = $router->run( kirby()->path() );
if (! is_null($route)) {
    die( call($route->action(), $route->arguments()) );
}

Hope this helps!

1 Like

Looks good. Thanks for posting!

Thanks for this great article. I’m getting a basic return with api/v1/blog, but no secondary data such as text and image urls:

{
data: {
blog/first-post: null,
blog/second-post: null,
blog/third-post: null
}
}

``
Could you explain how the code below relates to the article.php, it doesn’t seem to be effecting it

'action' => function() {
      // get the article pages
      $articles = page('blog')->children()->visible();
      $data = [];

  // Transform for API delivery
  foreach ($articles as $article) 
  {
    $data[] = $article->serialize(); 
  }

  // Now you're sending a plain old array to `response::json()`
  return response::json($data);
}

Thank you,

To make your code work, you need to define a serialize method in a page model, like described in this post.

Heya @rppld — Mind sharing your final solution?

1 Like

Hi, @brandondurham there’s 3 main files, I stay with page called “Events”, but it can be a Blog, Portfolio etc.:

// plugins/api.php
<?php

$prefix = "api/v1";

kirby()->routes([
  [
    'method' => 'GET',
    'pattern' => "{$prefix}/events",
    'action' => function() {

      // Get the events pages
      $events = page('events')->children()->visible();
      $data = [];

      // Transform for API delivery
      foreach ($events as $event) {
        $data[] = $event->serialize();
      }

      return response::json($data);
    }
  ]

]);

?>

// controllers/events.php
<?php

return function ($site, $pages, $page) {
  if (get('format') == 'json') {
    $data = [
      'uid'   => $page->uid(),
      'title' => $page->title()->toString(),
      'text'  => (string) $page->kirbytext(),
    ];

    die(response::json($data, 200));
  }
}

?>

// models/event.php
<?php

class EventPage extends Page {
  public function serialize() {
    return [
      'uid'     => $this->uid(),
      'url'     => $this->url(),
      'title'   => $this->title()->html()->toString(),
      'text'    => $this->text()->kirbytext()->toString()
    ];
  }
}

?>

So visiting /api/v1/events you get a nice 200 status JSON response. Here’s my commit.

1 Like

I came across this post a couple of months ago because I obviously had some similar requirements. I found the various responses here very helpful but I figured that there should be a plugin that somewhat simplifies and standardizes the process of creating custom APIs. So I built one.

In case anybody else is interested - which, judging from the conversation some of you might be;

I even wrote some quite extensive documentation.

8 Likes

@lord-executor THIS IS AMAZING – it works perfectly!!!

But there’s one question left for me. Maybe you’ve got an idea for this…

The output of the markdown is pure text. Is there a solution to output interpreted html code for the text field, or is it more wise to interpret it via the javascript?

e.g. i’ve got this output:

{
"id":"content",
"url":"http:\/\/kickstarter.kirby\/api\/content",
"uid":"content",
"title":"content",
"text":"hi **how** is it?\n\n# this is a headline\n## this is a smaller subline\n\n(image: gallery_01.jpg)\n\n****"
}