I'm working with Laravel 5.6 as my backend for a personal project and i've been doing something that seems (to me) as a bad practice, either way, I would like to know if it is actually that bad.
First of all, i'm using a Vue.js (CLI 3) project as a client and i'm making requests to my Laravel backend. Now, to deal with the notifications/toasts, i'm using the next format:
return response()->json([
'alert' => [
'title' => 'Server error.',
'text' => 'Error explanation text.',
'type' => 'error'
]
], 200);
It doesn't matter if I everything went right or wrong, i am always responding with this same format and an 200 status. Is it wrong? Should I use other statuses on my responses?
I am doing this because i can't get (i don't know how) the custom 'alert' array on my client side while using a 404 status (for example) and the only way I could find to deal with it was using this 200 status every single time.
HTTP status code are a mechanism to identify easily a response and will help to understand to clients if a requests was okey just checking it, for example a search engine robot that could distinguish between a error page just thanks to the status code.
In Axios for example, a HTTP client for JS, you can read response data even if was error https://stackoverflow.com/a/39153411.
Also have a look into this resource that will help you which status code to choose https://www.restapitutorial.com/httpstatuscodes.html
Related
A lot of resources say, that GraphQL should always respond with a 200 status code, even when an error occurred:
https://www.graph.cool/docs/faq/api-eep0ugh1wa/#how-does-error-handling-work-with-graphcool
https://github.com/rmosolgo/graphql-ruby/issues/1130#issuecomment-347373937
https://blog.hasura.io/handling-graphql-hasura-errors-with-react/
Because GraphQL can return multiple responses in one response, this makes sense. When a user requests two resources in one request, and only has access to the first resource, you can send back the first resource and return a forbidden error for the second resource.
However, this is just something I figured out along the way reading docs of multiple GraphQL libraries and blog posts. I didn't find anything about HTTP status codes in the offical specs, here https://spec.graphql.org/ or here https://graphql.org/
So I still have a few questions left:
Is it ok to return a HTTP 500 status code if I have an unexpected server error?
Is it ok to return a HTTP 401 status code, if credentials are wrong?
Should I include the potential HTTP status code inside the errors key of the GraphQL response like this
{
"errors" => [{
"message" => "Graphql::Forbidden",
"locations" => [],
"extensions" => {
"error_class" => "Graphql::Forbidden", "status" => 403
}
}]
}
Should I match common errors like a wrong field name to the HTTP status code 400 Bad Request?
{
"errors" => [{
"message" => "Field 'foobar' doesn't exist on type 'UserConnection'",
"locations" => [{
"line" => 1,
"column" => 11
}],
"path" => ["query", "users", "foobar"],
"extensions" => {
"status" => 400, "code" => "undefinedField", "typeName" => "UserConnection", "fieldName" => "foobar"
}
}]
}
I'd be great if you could share your experiences / resources / best practises when handling HTTP status codes in GraphQL.
GraphQL is transport-agnostic. While GraphQL services are commonly web services that accept requests over HTTP, they can and do accept requests over other transports as well. In fact, a GraphQL service can execute queries with no network requests at all -- all it needs is a query, and, optionally, a variables object and operation name.
Because of this, the GraphQL spec isn't concerned with methods, status codes or anything else specific to HTTP (it only mentions HTTP when discussing serialization). Any practices with regard to these things are at best conventions that have either evolved over time or are simply artifacts from some of the original libraries that were written for GraphQL. As such, any kind of answer to your question is going to be mostly based on opinion.
That said, because your GraphQL service shouldn't care about how its queries are received, arguably there should be a separation between its code and whatever code is handling receiving the requests and sending back the responses (like an Express app in Node.js). In other words, we could say it's never ok for your resolver code to mutate things like the response's status code. This is the current thinking in the community and most libraries only return one of two codes -- 400 if the request itself is somehow invalid and 200 otherwise.
If your entire GraphQL endpoint is guarded by some authentication logic (say your server checks for some header value), then a GraphQL request might come back with a 401 status. But this is something we handle at the web server level, not as part of your schema. It's no different if something went terribly wrong with your web server code and it had to return a 500 status, or the nginx server sitting in front of your returned a 494 (Request header too large), etc.
Traditionally, errors encountered during execution should be thrown and that's it. GraphQL extensions can be used to provide additional context when the errors are collected and serialized -- the name of the error, the stack trace, etc. However, it makes little sense to include HTTP status codes with these errors when, again, the errors have nothing to do with HTTP. Doing so unnecessarily mixes unrelated concepts -- if you want to identify the type of error, you're better off using descriptive codes like GENERIC_SERVER, INVALID_INPUT, etc.
However, conventions around error handling are also changing. Some services want to better distinguish client errors from other execution errors. It's becoming more common to see validation errors or other errors that would be shown to the end user to be returned as part of the data instead of being treated like an execution error.
type Mutation {
login(username: String!, password: String!): LoginPayload!
}
type LoginPayload {
user: User
error: Error
}
You can see payload types like these in action with public APIs like Shopify's. A variant on this approach is to utilize unions to represent a number of possible responses.
type Mutation {
login(username: String!, password: String!): LoginPayload!
}
union LoginPayload = User | InvalidCredentialsError | ExceededLoginAttemptsError
The end result is that the client errors are strongly typed and easily distinguishable from other errors that the end user doesn't care about. There's a lot of benefits to adopting these sort of conventions, but whether they are the right fit for your server is ultimately up to you.
I create Laravel+Vue simple REST API web-app.
In Vue component I have a method with an api request.
I simplified this to see the core of the problem:
phpValidate() {
axios
.post("api/validate", self.programmer)
.then(function(response) {
console.log(response.status);
});
}
In the controller I have a method validateIt(), which handle this "api/validate" request.
It returns:
return array('status' => $status, 'data' => $data);
The $status can be equal to 200 or 422, depends on the input data.
The problem is that from some point, it began to return $status of 200 always.
Even if I delete all the code from the method validateIt() and just leave two lines:
$status = 422;
return array('status' => $status);
I still receive 200.
If I delete the whole method in controller, it gives an Internal Server Error 500.
So, the route and function name is correct.
When I put it back, I can write there whatever I like, it doesn't have any sence - it still returns 200!
If I use debugger, I can see that at the end of validateIt() method it returns 422.
But, when I get the response in phpValidate() I see again 200.
Unbelievable!
I tried:
npm run dev
and
php artisan cache:clear
doesn't help!
Also I tried to restart the server and use different browsers, doesn't help.
Actually, this is not a problem of caching.
It looks like the variable name STATUS is reserved.
Doesn't matter what value you give to $status in the controller method.
The $status always contains the actual status of the request and you can't change it manually. Even if the method is empty it will return $status 200 because the request was sucessfull.
The solution is to use another variable name for your own data.
I had the same problem and to solve it add version where you include your vue frontend file,do it like this it will never cache again:
<script src="{{ asset('js/app.js?version='.date("ymdhis").'') }}"></script>
and you should make sure that your vue server is running use npm run watch or npm run dev
I made the below controller to demonstrate the issue of handling 500 errors I am having with dingo api in laravel. I want to be able to detect when a 500 error will be thrown so it never makes it to the client (as it is too much details to share with the client and they should only be logged by Laravel).
The methodgetUser() returns a 500 error intentionally due to the typo firsgt()
class TestController extends Controller {
public function getUser() {
$data = User::firsgt(); //returns 500 error
return $data;
}
}
This is what the client sees:
In my controllers, I handle errors manually by returning a success/error json response from within the controllers, but if an error occurs that I did not expect, the api returns it and it has too much details for the client to see. Instead, these unexpected errors should bubble up to some sort of handler to return a generic error occurred response. In Laravel, setting APP_DEBUG = false in .env works for laravel (but not for dingo api), this has no effect and the full error is returned to the client. Looking for a safety net for errors that slip through the cracks.
How can we return an error message like 'Error occurred' instead of the too much details for client 'Call to undefined method App\User::firsgt()'?
Note: I don't want to handle it one by one for each controller method, but instead capture any 500 before it is returned to client, and return the custom 500 generic message 'Error occurred'
You should check your dingo config and set these two parameters to false.
APP_DEBUG=false
API_DEBUG=false
If you still encounter the issue, just as suggested in the comment ensure you are in production.
Finally if you are still having the same issues (which by now normally should not exist after setting those fields to false) then you might be interested in checking this issue (date back since 2015).
The fix from one of the comment says (verbatim):
app(\Dingo\Api\Exception\Handler::class)->register(function (\Exception $exception) {
if (!env('API_DEBUG') && !$exception instanceof \Symfony\Component\HttpKernel\Exception\HttpException) {
// Whatever other handling you want goes here..
// Mimic the normal API response with a different message
return \Response::make([
'message' => 'Internal server error',
'status_code' => 500,
], 500);
}
});
Beware that I didnt test this myself.
If you haven't already done yet, publish dingo config file
php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"
Then, open config/api.php and edit the errorFormat value to whatever message you want.
Replace :message with a generic message
I am using laravel's RESTful resource routes and controllers.
I testing my api with PostMan and/or RestClient(firefox)
Everything was fine before I added laravel's controller validation. Now it respond with very strange responses like: status code 0, or even executes the code with not valid data. Or even shows strange results taken from the database (which are not included to the controller at all).
That's creepy.
For example:
public function store(Request $request) {
$this->validate($request, [
'room_id' => 'required|integer',
'body' => 'required'
]);
exit; // Stop the process after the validation...
// ... Logic to STORE the MESSAGE in the database ...
return response(null, 204);
}
This store function must only validate the data and respond with an error if validation fails,
But when I execute it from PostMan, It returns response with list of all rooms which belongs to this user. This is creepy, I cannot realize why this is happening.
When I use the jQuery.ajax() method with the same request options, it works fine. It validates the data and stores the message in the database.
Question : Is there a way to deal with postman?
Question : Is PostMan dangerous? Since the server respond with database info (which is not included to the controller responses).
If you take a closer look at laravel's validation document it says that when data is submitted via HTML form,the validate method would redirect you to a previous page and the error would be flashed into the session, if the validation fails. Although, you will be able to access these error messages in your view using the $error variable. Therefore you will need to submit the data via ajax in-order to get the JSON error message.
When validatioin failed, Laravel needs to know you are sending ajax request or you explicitly want json respond, otherwise it redirects you to previous page.
You can check my answer here
https://stackoverflow.com/a/38478362/6246592
Read bottom edit for workaround.
I'm having trouble implementing ajax pagination in my cakephp app. I've looked for resources and tutorials to help with the issue however most deal with cake 1.3. I've got my fingers crossed for a glaring and simple mistake on my part...
A description of where I'm at so far: I have built a simple search interface for my application - a used car search interface that searches the cars in my database. The search interface actually seems to be working fine. I have a hidden form that creates or destroys hidden elements based on the users input in the search form. This hidden form is submitted via Ajax successfully and I can return a set of paginated results such as "1-5 displayed of 16 found".
The problem I seem to be having is with the javascript enhanced number, next, and prev link/ajax requests that are being generated by the paginator helper.
My CarsController has the find_cars function that expects a get request (the $paginate var in my controller and the $this->Paginator options in the view both have dataType = querystring). The function works fine on the initial form submission with a Get request url like: http://localhost/myapp/cars/findCars?price=any&year=any&miles=any
I have in my ajax.ctp layout $this->Js->writeBuffer(); and in the ajax response I can see the jquery events written for the next, number, and prev links. An example of one event:
$("#link-1448104972").bind("click", function (event) {$.ajax({
dataType:"html", evalScripts:true, paramType:"querystring",
success:function (data, textStatus) {
$("#results").html(data);}, url:"\/myapp\/cars\/findCars?price=any&year=any&miles=any&page=2"});
The problem I'm having is with the URL value. When I paste the url in a browser, my find_cars function processes the parameters correctly as the &'s get sent to the controller as plain &. However when I click one of the pagination links that sends the ajax request, the URL still contains the & encoded ampersands and $this->request->query; in a var_dump after a pagination click looks something like:
array
'price' => string 'any' (length=3)
'amp;year' => string 'any' (length=3)
'amp;miles' => string 'any' (length=3)
'amp;body' =>
array
0 => string '1' (length=1)
1 => string '2' (length=1)
2 => string '6' (length=1)
'amp;page' => string '2' (length=1)
The only other discussion I can find anywhere that seems to mention this same issue is in this cakephp lighthouse bug ticket #127 . However the discussion is for cakephp 1.3 and the github commit it links to is a 404.
Edit: I guess I should also add that I was trying to use GET requests so that I would have an easier time implementing expected user navigation & bookmarking capabilities to save search queries and navigate forward and backward through results with the browser as well as in app pagination links.
Edit (Workaround / Solution) -
Working from #islandmyth's suggestion I switched over from doing a Get request to doing a Post, changed the paramType from 'querystring' to 'named'. I pass the query info from $this->request->named to the view save the info to $this->Paginator->options['data'] having used php's http_build_query() function to preserve the nested arrays.
Cake php Ajax Paginator not seems to be working fine. I had similar issues also.
I would recommend you to use the cakephp plugin Cakephp-DataTable
This plugin has implemented the pagination and it has most of the features by default. They have also provided documentation and if you find any difficulty in implementing please go throught the issues section
Also the developer is very responsive and can get clarifications for that plugin if you have any.