Empty Request Body in Route Accepting POST Requests

Hi there!

I’m having an issue regarding a custom route that I registered in a plugin that has me scratching my head for some days now. The route is supposed to accept a POST request containing some JSON that I will use to dynamically create pages. While the route is registered successfully and I can reach it and return content, I’m having trouble reading the JSON that I send to it.

I have been testing the route from JS as well as a Browser extension API tester (Talend) and found that as soon as I send the request with no specific header or a Content-Type: application/x-www-form-urlencoded header, I can access the data using kirbys $request->body() and get() functions. As soon as I send the request with the appropriate Content-Type: application/json header though, it stops working and the request body is completely empty. I’m not sure what’s causing this or if I misunderstand some fundamental thing. Does anyone see an issue with the route itself?

<?php 

return Array(
    'pattern' => '/(:any)/(:any)/add_comment.json',                     // pattern is topicId, postId
    'method' => 'POST',
    'action'  => function($topicId, $postId) use ($kirby) {
        $request = $kirby->request();
        
        // all of these are empty as soon as request header is `Content-Type: application/json` 
        $data = $request->body()->toArray();            
        $data = $request->body();
        $data = get();

        return json_encode($data);  // for now just returning the data to see if it can be read
    }
);

As always, any help or clarification is much appreciated. Thanks!

Update: I’ve gotten this to work by reading the raw data stream using

 $data = file_get_contents("php://input");
 $data = json_decode($data, true);

but bypassing Kirbys built-in methods to user lower level php functions feels kind of hacky. Still looking for a way to get this to work the *kirby way*

You should have access to the raw data (the equivalent of reading "php://input") via $request->body()->contents():

$data = kirby()->request()->body()->contents();
$data = json_decode($data, true);

However, Kirby should do this automatically: kirby()->request()->body()->toArray() should already return an associative array…
You don’t want to json_encode your response though; otherwise, being a string, the response would be sent as text/html.

As a minimal example, this seems to work:

<?php

use Kirby\Cms\App as Kirby;

Kirby::plugin('my/plugin', [
    'routes' => [
        [
            'pattern' => 'echo',
            'method'  => 'POST',
            'action'  => function () {             
                $data = kirby()->request()->body()->toArray();
                return $data ?: [ 'error' => 'No data' ];
            }
        ]
    ]
]);	

In the browser console:

await fetch('/echo', { 
  method: 'post', 
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({foo: "bar"}) 
}).then(e => e.json())

 > {foo: 'bar'}

await fetch('/echo', { 
  method: 'post', 
  headers: { 'Content-Type': 'application/json' },
}).then(e => e.json())

 > {error: 'No data'}