Is there a way to declare multiple API routes from a single file in Arrow?
Example: Say you want to declare multiple endpoints for a user API:
GET /api/user/:id
DELETE /api/user/:id/delete
POST /api/user
It would make sense to keep these in the same file since they are related and could share code, instead of splitting them into their own files.
I'm referring to these docs.
At this moment the only way to keep them in the same file is to use ALL as the method and then in the action use req.method to delegate to the right logic. E.g.:
..
method: 'ALL',
action: function(req, res, next) {
switch (req.method) {
case 'GET':
..
break;
case 'DELETE':
..
break;
default:
return res.notfound(next);
break;
}
}
..
Related
I've got a Controller.php whose show($id) method is hit by a route.
public function show($id)
{
// fetch a couple attributes from the request ...
$this->checkEverythingIsOk($attributes);
// ... return the requested resource.
return $response;
}
Now, in checkEverythingIsOk(), I perform some validation and authorization stuff. These checks are common to several routes within the same controller, so I'd like to extract these checks and call the method everytime I need to perform the same operations.
The problem is, I'm unable to send some responses from this method:
private function checkEverythingIsOk($attributes)
{
if (checkSomething()) {
return response()->json('Something went wrong'); // this does not work - it will return, but the response won't be sent.
}
// more checks...
return response()->callAResponseMacro('Something else went wrong'); // does not work either.
dd($attributes); // this works.
abort(422); // this works too.
}
Note: Yes, I know in general one can use middleware or validation services to perform the checks before the request hits the controller, but I don't want to. I need to do it this way.
As of Laravel 5.6 you can now use for example response()->json([1])->send();.
There is no need for it to be the return value of a controller method.
Note that calling send() will not terminate the output. You may want to call exit; manually after send().
You are probably looking for this:
function checkEverythingIsOk() {
if (checkSomething()) {
return Response::json('Something went wrong');
}
if(checkSomethingElse()) {
return Response::someMacro('Something else is wrong')
}
return null; // all is fine
}
And in the controller method:
$response = $this->checkEverythingIsOk();
if($response !== null) { // $response instanceof Response
return $response;
}
It's probably overkill, but I will throw it in anyway. You might want to look into internal requests. Also this is just pseudoish code, I have not actually done this, so take this bit of information with caution.
// build a new request
$returnEarly = Request::create('/returnearly');
// dispatch the new request
app()->handle($newRequest);
// have a route set up to catch those
Route::get('/returnearly', ...);
Now you can have a Controller sitting at the end of that route and interpret the parameters, or you use multiple routes answered by multiple Controllers/Methods ... up to you, but the approach stays the same.
UPDATE
Ok I just tried that myself, creating a new request and dispatching that, it works this way. Problem is, the execution does not stop after the child-request has exited. It goes on in the parent request. Which makes this whole approach kind of useless.
But I was thinking about another way, why not throw an Exception and catch it in an appropriate place to return a specified response?
Turns out, thats already built into Laravel:
// create intended Response
$response = Response::create(''); // or use the response() helper
// throw it, it is a Illuminate\Http\Exception\HttpResponseException
$response->throwResponse();
Now usually an Exception would be logged and you if you are in Debug mode, you would see it on screen etc. etc. But if you take a look into \Illuminate\Foundation\Exceptions\Handler within the render method you can see that it inspects the thrown Exception if it is an instance of HttpResponseException. If it is then the Response will be returned immediately.
To me the most simple and elegant way is:
response()->json($messages_array, $status_code)->throwResponse();
(you don`t need return)
It can be called from a private function or another class...
I use this in a helper class to check for permissions, and if the user doesn`t have it I throw with the above code.
What are the fundamental differences of those functions? All I know is all three result in a 201, which is appropriate for a successful POST request.
I only follow examples I see online, but they don't really explain why they're doing what they're doing.
We're supposed to provide a name for our GET (1 record by id):
[HttpGet("{id}", Name="MyStuff")]
public async Task<IActionResult> GetAsync(int id)
{
return new ObjectResult(new MyStuff(id));
}
What is the purpose of naming this get function, besides that it's "probably" required for the POST function below:
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtRoute("MyStuff", new { id = myStuff.Id }, myStuff);
}
I notice that CreatedAtRoute also has an overload that does not take in the route name.
There is also CreatedAtAction that takes in similar parameters. Why does this variant exist?
There is also Created which expects a URL and the object we want to return. Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?
I'm not sure why there are so many variants just to be able to return a 201 to the client. In most cases, all I want to do is to return the "app-assigned" (most likely from a database) unique id or a version of my entity that has minimal information.
I think that ultimately, a 201 response "should" create a location header which has the URL of the newly-created resource, which I believe all 3 and their overloads end up doing. Why should I always return a location header? My JavaScript clients, native mobile, and desktop apps never use it. If I issue an HTTP POST, for example, to create billing statements and send them out to users, what would such a location URL be? (My apologies for not digging deeper into the history of the Internet to find an answer for this.)
Why create names for actions and routes? What's the difference between action names and route names?
I'm confused about this, so I resorted to returning the Ok(), which returns 200, which is inappropriate for POST.
There's a few different questions here which should probably be split out, but I think this covers the bulk of your issues.
Why create names for actions and routes? What's the difference between action names and route names?
First of all, actions and routes are very different.
An Action lives on a controller. A route specifies a complete end point that consists of a Controller, and Action and potentially additional other route parameters.
You can give a name to a route, which allows you to reference it in your application. for example
routes.MapRoute(
name: "MyRouteName",
url: "SomePrefix/{action}/{id}",
defaults: new { controller = "Section", action = "Index" }
);
The reason for action names are covered in this question: Purpose of ActionName
It allows you to start your action with a number or include any character that .net does not allow in an identifier. - The most common reason is it allows you have two Actions with the same signature (see the GET/POST Delete actions of any scaffolded controller)
What are the fundamental differences of those functions?
These 3 functions all perform essentially the same function - returning a 201 Created response, with a Location header pointing to the url for the newly created response, and the object itself in the body. The url should be the url at which a GET request would return the object url. This would be considered the 'Correct' behaviour in a RESTful system.
For the example Post code in your question, you would actually want to use CreatedAtAction.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtAction("MyStuff", new { id = myStuff.Id }, myStuff);
}
Assuming you have the default route configured, this will add a Location header pointing to the MyStuff action on the same controller.
If you wanted the location url to point to a specific route (as we defined earlier, you could use e.g.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return CreatedAtRoute("MyRouteName", new { id = myStuff.Id }, myStuff);
}
Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?
If you really don't want to use a CreatedResult, you can use a simple StatusCodeResult, which will return a 201, without the Location Header or body.
[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
// actual insertion code left out
return StatusCode(201);
}
I believe there is an example for you here.
Remembering that I'm using .NET 6
[HttpPost]
public IActionResult CadastrarCerveja([FromBody] Cerveja cerveja)
{
using (var ctx = new CervejaContext())
{
ctx.Cervejas.Add(cerveja);
ctx.SaveChanges();
}
return CreatedAtAction(
nameof(LerCerveja),
new { IdCerveja = cerveja.Id },
cerveja);
}
[HttpGet("{IdCerveja}")]
public IActionResult LerCerveja(int IdCerveja)
{
var ctx = new CervejaContext();
var cerv = ctx.Cervejas.FirstOrDefault(c => c.Id == IdCerveja);
if (cerv == null)
return NotFound();
else
return Ok(cerv);
}
Is it possible in Laravel 4 to define a route that returns either html or json based on if .json is added to the request url?
Eg:
POST example.app/user/new would respond with a Redirect::to another route giving html
POST example.app/user/new.json would respond with a Response::json response giving json
Is it possible to achieve this? And if so how?
You may try:
Route::post('user/new{extension?}', function($extension = null)
{
switch ($extension) {
case '.json':
return Response::json();
break;
default:
return Redirect::to('/');
break;
}
});
I want to build fancy url in my site with these url patterns:
http://domain.com/specialization/eye
http://domain.com/clinic-dr-house
http://domain.com/faq
The first url has a simple route pattern:
Route::get('/specialization/{slug}', 'FrontController#specialization');
The second and the third url refers to two different controller actions:
SiteController#clinic
SiteController#page
I try with this filter:
Route::filter('/{slug}',function()
{
if(Clinic::where('slug',$slug)->count() == 1)
Route::get('/{slug}','FrontController#clinic');
if(Page::where('slug',$slug)->count() == 1)
Route::get('/{slug}','FrontController#page');
});
And I have an Exception... there is a less painful method?
To declare a filter you should use a filter a static name, for example:
Route::filter('filtername',function()
{
// ...
});
Then you may use this filter in your routes like this way:
Route::get('/specialization/{slug}', array('before' => 'filtername', 'uses' => 'FrontController#specialization'));
So, whenever you use http://domain.com/specialization/eye the filter attached to this route will be executed before the route is dispatched. Read more on documentation.
Update: For second and third routes you may check the route parameter in thew filter and do different things depending on the parameter. Also, you may use one method for both urls, technically both urls are identical to one route so use one route and depending on the param, do different things, for example you have following urls:
http://domain.com/clinic-dr-house
http://domain.com/faq
Use a single route for both url, for example, use:
Route::get('/{param}', 'FrontController#common');
Create common method in your FrontController like this:
public function common($param)
{
// Check the param, if param is clinic-dr-house
// the do something or do something else for faq
// or you may use redirect as well
}
I have the following filter:
Route::filter('security', function()
{
//do security checks
//send to my gateway controller and test() method
});
Route::when('/gateway', 'security');
The above does not seem to work, where am I going wrong?
What should I put inside the filter to load my test method in my gateway controller?
How can I test that the call is an ajax call using:
Request::ajax()
In order to make this code work you need to have a route /gateway created
Route::filter('security', function()
{
if(Request::ajax())
{
//do security checks
return Redirect::action('GatewayController#test');
}
});
Route::when('gateway', 'security');
Route::get('/gateway', 'GatewayController#test');
Note that the slash / has been removed in Route::when('/gateway', 'security');.
This is because the Router adds a slash when checking registered patterns against the path info for the current request