Using Laravel's resource routes, I've set up an API to serve as the back-end of a React JS application. I'm attempting to access the 'update' method currently. I'm using Javascript's fetch()
to accomplish this, so its making one OPTIONS
request first, then making the POST
request (the form has a method spoof in it, setting _method
to PATCH
instead - this obviously doesn't affect the initial OPTIONS
call). This same page is also making a GET
request to the same endpoint via the same method, which works fine.
The fetch()
call is below. Of course, this being React, it's called through a Redux Saga process, but the actual fetch is there.
function postApi(values, endpoint, token) { // <-- values and endpoint are sent by the component, token is sent by a previous Saga function
return fetch(apiUrl + endpoint, { // <-- apiUrl is defined as a constant earlier in the file
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify(
values
)
}).then(handleApiErrors)
.then(response => response.json())
.catch((error) => {throw error})
}
And the Laravel routes:
Route::group(['middleware' => 'auth:api'], function() {
Route::resource('users', 'UserController');
}
I was encountering an error where the initial OPTIONS
request to the URL was returning a 404
error, which right away is strange, since the endpoint obviously exists, the exact same endpoint having just been queried seconds ago, but I assumed maybe Laravel was returning the wrong error, and I had used the wrong method. I did some digging and debugging trying to get the request to be correct before giving up and making the request in Postman. The thing is: it works fine in Postman.
Here are the response headers from the server (note that any access origin is permitted):
Access-Control-Allow-Origin:*
Cache-Control:no-cache, private
Connection:close
Content-Length:10
Content-Type:text/html; charset=UTF-8
Date:Thu, 21 Sep 2017 13:29:08 GMT
Server:Apache/2.4.27 (Unix) OpenSSL/1.0.2l PHP/7.0.22 mod_perl/2.0.8-dev Perl/v5.16.3
X-Powered-By:PHP/7.0.22
Here's the request headers for the request as made from the React JS app (the one that receives a 404 error):
Accept:*/*
Accept-Encoding:gzip, deflate, br
Accept-Language:en-US,en;q=0.8,fr;q=0.6,ga;q=0.4
Access-Control-Request-Headers:authorization,content-type
Access-Control-Request-Method:POST
Cache-Control:no-cache
Connection:keep-alive
Host:localhost
Origin:http://localhost:3000
Pragma:no-cache
Referer:http://localhost:3000/employees/edit/13
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
In Postman, I set up those exact same request headers and made the exact same OPTIONS
request to the server. And it worked fine! I received an empty 200 response.
Just to be sure, I double-checked the Apache access log. And sure enough:
...
::1 - - [20/Sep/2017:15:33:24 -0400] "OPTIONS /the/path/to/api/users/13 HTTP/1.1" 200 -
::1 - - [20/Sep/2017:15:40:26 -0400] "OPTIONS /the/path/to/api/users/13 HTTP/1.1" 404 10
...
Request method the exact same, request URL the exact same, except one returned 200, the other returned 404, for no discernable reason.
Additionally, I should add that another POST
request, to the create
method, works just fine.
What could be causing this?
ATTEMPTED SOLUTIONS
1. I saw this question (React Native + fetch + API : DELETE request fails in App, works in Postman), and even though I'm running Apache, not Nginx, I thought I'd try adding a trailing slash to the request URL. The OPTIONS
request now returns a 301 error (moved permanently).
2. I removed the trailing slash and continued trying to fix. Per comment suggestion, I removed the automatic route generation and created my own:
Route::get('/users', 'UserController@index');
Route::post('/users', 'UserController@create');
Route::put('/users/{user}', 'UserController@update');
Route::patch('/users/{user}', 'UserController@update');
Route::get('/users/{user}', 'UserController@show');
The Postman request still returns 200 OK, and the React request still returns 404 Not Found.
3. Eureka! Kind of. Per another comment suggestion, I exported the request from Chrome as cURL and imported it into Postman directly - maybe I missed something when copying the headers over. It seems I did, because now the Postman request also returns 404!
After playing around with disabling and/or enabling the imported headers, I've determined that the issue is the combination of the Origin
and Access-Control-Request-Method
headers. If only one is present the request returns 200, but if both are present I receive a 404.
This does still leave me with the question of how to fix the problem, however. At this point I wonder if the question might become more of a Laravel question - IE, why an OPTIONS
request to a perfectly valid Resource route would return 404. I assume because those resources routes are listening for PUT
or PATCH
but not OPTIONS
.
Since you have your CORS set up, all you need to do next is handle the 'preflight' OPTIONS request. You can do this using a middleware:
PreflightRequestMiddleware
:
if ($request->getMethod() === $request::METHOD_OPTIONS) {
return response()->json([],204);
}
return $next($request);
Add the above code in the handle()
method of the newly created middleware. Add the middleware in the global middleware stack.
Also, do not forget to add the OPTIONS method to Access-Control-Allow-Methods
in your CORS setup.
For more options, check this out.
Answer:
Read this article. When the browser sends OPTIONS request to your application, the application has no way of handling it since you only defined a GET/POST/DELETE/PUT/PATCH route for the given endpoint.
So, in order for this route to work with preflight requests:
Route::get('/users', 'UserController@index');
it would need a corresponding OPTIONS route:
Route::options('/users', 'UserController@options');
Note: You would use a middleware to handle all OPTIONS requests in one place. If, however, you are using OPTIONS requests for other purposes - check the first link in this answer for more options.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With