Slim 4 POST Endpoint always throws error 500 but GET works flawlessly

Setup

Apache Virtual Host configured for: https://api.mydomain.com

SLIM Setup installed in the root of the web (DocumentRoot) as this Virtual Host is for
API endpoints only (no shared web content)
Setup taken from : Installation - Slim Framework

DocumentRoot
   |
   +--- composer.json
   +--- composer.lock
   +--- vendor
   +--- public         // GET  Endpoint (OK)
   |     +--- index.php
   |
   +--- geotracking    // POST Endpoint (Error 500)
         +--- index.php

Setup commands used:

# composer require slim/slim:"4.*"
# composer require slim/psr7
# mkdir public
# mkdir geotracking
# cd public

Created an index.php with the following GET example

----- public/index.php ----------------------------------------------------------
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();
$app->setBasePath('/public');

$app->get('/', function (Request $request, Response $response, $args) {
    $response->getBody()->write("Hello world (PUBLIC)!");
    return $response;
});

$app->run();

---------------------------------------------------------------------------------

Calling https://api.mydomain/public works flawlessly and returns “Hello World (PUBLIC)!”
Using PowerShell 7 with

PS> Invoke-RestMethod -Uri "https://api.mydomain.com" -Method Get

returns “Hello World (PUBLIC)!” as well.

I can do similar GET examples in the “geotracking” subfolder by simply adjusting the
setBasePath(‘/geotracking’) and calling the URI

https://api.mydomain.com/geotracking

So far so good. Now I wanted to do a POST sample which simply echo’s a JSON-Body
back to the caller.

!!! And here everything breaks with Apache returning error 500.

I have no clue why. I spent hours and I don’t see the problem.

----- geotracking/index.php -----------------------------------------------------
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Exception\HttpBadRequestException;

require __DIR__ . '/../vendor/autoload.php';

error_reporting(E_ALL);
ini_set('display_errors', 1);

$app = AppFactory::create();
$app->setBasePath('/geotracking');
$app->addBodyParsingMiddleware(); // Enable JSON parsing

// Enable error details (only for development!)
$errorMiddleware = $app->addErrorMiddleware(true, true, true);

//$app->map(['GET', 'POST']'/', function (Request $request, Response $response) {
$app->post('/', function (Request $request, Response $response) {
    $data = $request->getParsedBody();

    if (!is_array($data)) {
        $response->getBody()->write(json_encode(['error' => 'Invalid JSON']));
        return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
    }

    $response->getBody()->write(json_encode($data));
    return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
});
---------------------------------------------------------------------------------

Then I call the endpoint from PowerShell 7 with the following code sequence and I get an error 500.

----- PowerShell invocation -----------------------------------------------------

$uri     = 'https://api.mydomain.com/geotracking'
$headers = @{
    "Content-Type" = "application/json; charset=utf-8"
}

$body = @{
    "name" = "John"
    "age" = 30
}

# Convert the body to JSON format
$jsonBody = $body | ConvertTo-Json

# Make the REST API call
$response = Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $jsonBody
Invoke-RestMethod -Uri $uri -Method Post -Body $jsonBody -Verbose

Output

PS> Invoke-RestMethod -Uri $uri -Method Post -Body $jsonBody -Verbose
VERBOSE: Requested HTTP/1.1 POST with 36-byte payload
VERBOSE: Received HTTP/1.1 response of content type text/html of unknown size
Invoke-RestMethod: 405 Method Not Allowed body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif} h1{margin:0;font-size:48px;font-weight:normal;line-height:48px} strong{display:inline-block;width:65px} 405 Method Not Allowed The application could not run because of the following error:DetailsType: Slim\Exception\HttpMethodNotAllowedExceptionCode: 405Message: Method not allowed. Must be one of: POSTFile: /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.phpLine: 79Trace#0 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/Routing/RouteRunner.php(62): Slim\Middleware\RoutingMiddleware->performRouting()
#1 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/Middleware/BodyParsingMiddleware.php(65): Slim\Routing\RouteRunner->handle()
#2 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/MiddlewareDispatcher.php(129): Slim\Middleware\BodyParsingMiddleware->process()
#3 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php(77): Psr\Http\Server\RequestHandlerInterface@anonymous->handle()
#4 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/MiddlewareDispatcher.php(129): Slim\Middleware\ErrorMiddleware->process()
#5 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/MiddlewareDispatcher.php(73): Psr\Http\Server\RequestHandlerInterface@anonymous->handle()
#6 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/App.php(209): Slim\MiddlewareDispatcher->handle()
#7 /user1/WWWROOT/api.mydomain.com/html/vendor/slim/slim/Slim/App.php(193): Slim\App->handle()
#8 /user1/WWWROOT/api.mydomain.com/html/geotracking/index.php(32): Slim\App->run()
#9 {main} Go Back

As you can see I tried as well with

$app->map([‘GET’, ‘POST’]‘/’, function (Request $request, Response $response) {

but this doesn’t work neither.

Help / Ideas ?

So any idea why such a simple POST example does not work ?
Why does it throw a 405 Method not Allowed???

I am out of ideas for the moment :frowning:

Regards,
Oliver

The public/index.php should be the only file you need for all routes.
and the public/ directory should be the DocumentRoot of your Webserver.

He, thank you for helping

I fixed that comma (stupid error :slight_smile: )and it started working “somehow”. When calling from PowerShell with POST it returns

Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $jsonBody

{
  "error": "Invalid JSON"
}

So far so good. It traverses my callback function successfully.
But it seems that the Body doesn’t contain any data.

I tried to write down some trace messages to a file but with the exception of my “DEBUG” message I don’t see any data. And that results in the invalid JSON as on return it cannot json_encode it again.

So I removed the ‘GET’ from the mapping and thought maybe that is the problem. But if I do so I am back on field one 405 Method not allowed

So here is my full index.php

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Exception\HttpBadRequestException;

require __DIR__ . '/../vendor/autoload.php';

error_reporting(E_ALL);
ini_set('display_errors', 1);

$app = AppFactory::create();
$app->setBasePath('/geotracking');
$app->addBodyParsingMiddleware(); // Enable JSON parsing

// Enable error details (only for development!)
$errorMiddleware = $app->addErrorMiddleware(true, true, true);

$app->map(['GET','POST'],'/', function (Request $request, Response $response, array $args) {
    $data = $request->getParsedBody();
    file_put_contents('output.log', 'DEBUG 1:');
    file_put_contents('output.log', $data, FILE_APPEND);

    if (!is_array($data)) {
        $response->getBody()->write(json_encode(['error' => 'Invalid JSON']));
        return $response->withHeader('Content-Type', 'application/json; charset=utf-8')->withStatus(400);
    }

    $response->getBody()->write(json_encode($data));
    return $response->withHeader('Content-Type', 'application/json; charset=utf-8');
});

That one returns Invalid JSON so kind of successful execution of the callback handler.

Changing the mapping to

$app->map(['POST'],'/', function (Request $request, Response $response, array $args) {

and calling from Powershell

Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $jsonBody

fails with 405 Method Not Allowed

In my apache logs I always see

192.168.1.129 - - [18/Jul/2025:08:50:17 +0200] "POST /geotracking HTTP/1.1" 301 251 "-" "Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26100; de-CH) PowerShell/7.6.0"
192.168.1.129 - - [18/Jul/2025:08:50:17 +0200] "GET /geotracking/ HTTP/1.1" 400 24 "-" "Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.26100; de-CH) PowerShell/7.6.0"

First a 301 and then a GET (400). Strange.

So :slight_smile: any more ideas

Regards,
Oliver

Hi. Let’s try to find first quick solution.

According to PSR-7: HTTP message interfaces - PHP-FIG, function getParsedBody() : “… return null|array|object The deserialized body parameters, if any. These will typically be an array or object…”

So, try instead of this:


...
$data = $request->getParsedBody();
...

do that:


...
$data = (array) $request->getParsedBody();
...

Also. Look Slim 4 docs Request - Slim Framework.

And more. It’s better to see what exactely you transfer from client to server (PHP app). Is POST body really in proper JSON format to deserialize it properly by function getParsedBody()?

For comfortable REST API testing you can try Postman.

Regards, Eugene.

As for “… Changing the mapping to $app->map(['POST'],'/', function (… fails with 405 Method Not Allowed…”, I think, you can get answers by getting deeper in Slim 4 code. Start from slim\slim\Slim\Routing\RouteCollectorProxy.php.

Okay. The explicit type conversion fixed the Invalid JSON error. The response then had an empty JSON array structure .

Downloaded Postman and tested from there. I got very interesting results.
When I fire the Invoke-RestMethod to an endpoint I created myself using bare PHP primitives to grab the content.

$rawData = file_get_contents("php://input");
$telemetryData = str_replace("\r", "", $rawData);

it works. In PM Raw Log I see Request/Response (Headers and Bodies).
Fireing the same JSON-Body against the SLIM 4 Endpoint results in an EMPTY Request Body.
Yes, you read correctly. It doesn’t send the body with the same Invoke-RestMethod statement. How can that be !!!

When using the SLIM 4 endpoint I always see a POST with 301 redirect first and then a GET

192.168.1.129 - - [18/Jul/2025:16:39:55 +0200] "POST /geotracking HTTP/1.1" 301 251 "-" "PostmanRuntime/7.44.1"
192.168.1.129 - - [18/Jul/2025:16:39:55 +0200] "GET /geotracking/ HTTP/1.1" 200 2 "https://api.mydomain.com/geotracking" "PostmanRuntime/7.44.1"

I have the feeling that my .htaccess file is kind of wrong.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

I have no clue what SLIM is doing there but it seems to me that it returns a 301 redirect and that results in a second call but this one uses GET and no JSON-Body.
If I specify the full index.php it bails out again with “405 Method not allowed”

I am thinking of quitting this SLIM 4 adventure and just stay with my simple PHP POST endpoint script which works flawlessly.

Okay. Got it working.

I put my index.php into the public folder and set the DocumentRoot to the public folder as you said. Then I placed the .htaccess file from above to the public folder .

I modified the index.php as follows:

  • Removed: $app->setBasePath(‘/geotracking’);
  • Changed: $app->map([‘POST’],‘/geotracking’, function (Request $request, Response $response, array $args) {

and it started working. I get my JSON body echoed.

This simply tells me that

$app->setBasePath('/geotracking');

is broken and does simply not work as intended.

Thanks for helping anyway. I learned a lot from you :slight_smile:

Regards,
Oliver

1 Like