I want to ask question about a better way for implementing single endpoint REST.
I want to build a rest API but I want it to only have single endpoint like: graphql
, where you only have like: host/graphql
.
I already doing this example in laravel
where you need to put a key and method to go to a certain API.
The body look like this (example of calling product API with single endpoint).
Calling get all product:
{
"key":"product/all",
"method":"GET"
}
And this is the get one (product/{id}):
{
"key":"product",
"method":"GET",
"payload":"{\"id\":\"1\"}"
}
This is also work fine with post, put and delete.
Here is the single endpoint code (I use php laravel
as example because it fast to make API):
public function bridge(Request $request)
{
$host = "http://127.0.0.1:8000/api/";
if ($request->has("payload")) {
$bounds = html_entity_decode($request->payload);
$payload = json_decode($bounds, true);
}
if (strtoupper($request->method) == "POST") {
try {
$url = $host . $request->key;
$request = Request::create($url, 'POST', []);
$response = Route::dispatch($request);
return $response;
} catch (\Throwable $th) {
$data["data"] = [];
$data["success"] = false;
$data["code"] = 500;
$data["message"] = $th->getMessage();
return $data;
}
} else if (strtoupper($request->method) == "PUT") {
try {
$url = $host . $request->key . '/' . $payload['id'];
$request = Request::create($url, 'PUT', []);
$response = Route::dispatch($request);
return $response;
} catch (\Throwable $th) {
$data["data"] = [];
$data["success"] = false;
$data["code"] = 500;
$data["message"] = $th->getMessage();
return $data;
}
} else if (strtoupper($request->method) == "DELETE") {
try {
$url = $host . $request->key . '/' . $payload['id'];
$request = Request::create($url, 'DELETE', []);
$response = Route::dispatch($request);
return $response;
} catch (\Throwable $th) {
$data["data"] = [];
$data["success"] = false;
$data["code"] = 500;
$data["message"] = $th->getMessage();
return $data;
}
} else {
$url = $host . $request->key;
try {
if ($request->has("payload")) {
$url = $host . $request->key . "/" . $payload['id'];
}
$request = Request::create($url, 'GET');
$response = Route::dispatch($request);
return $response;
} catch (\Throwable $th) {
$data["data"] = [];
$data["success"] = false;
$data["code"] = 500;
$data["message"] = $th->getMessage();
return $data;
}
}
}
As you can see there. It is calling the API twice "bridge" and the "key" API.
The cons here:
- call request twice;
- you can only put one /{id} variable (or more if you declare it).
Pros:
- it only one endpoint so it will be easy to make an api helper in the frontend (maybe XD).
Can you show me a better way of doing this? Tell me your tips and thought or maybe show some code (any language is fine).
1 Answer 1
I only have a few quibbling remarks:
- Don't Repeat Yourself (D.R.Y.)
- Declare
$url = $host . $request->key
unconditionally at the start of your method (say, after$host
). Then append text to it if/when needed$url .= '/' . $payload['id'];
. strtoupper($request->method)
is done in each conditional expression. It is better practice to mutate the string once. You're also checking the same data for different strings, this is whatswitch-case
blocks are for.- Rather than writing
$data
(a non-descriptive variable name) over and over, omit the variable declaration entirely and write the data directly into thereturn
. I regularly advise against single-use variable declarations unless they valuably reduce code width or improve readability.
- Declare
else if
should beelseif
in PHP to comply with the PSR-12 coding standard.- All of your thrown exceptions are handled identically, so it seems to be a much cleaner choice to wrap your whole switch block in a single
try-catch
block. - Maybe you'd like to see the payload when there is a thrown exception; if so, use
"data" => $payload ?? []
in the return.
Your code might be rewrite as:
public function bridge(Request $request)
{
$host = "http://127.0.0.1:8000/api/";
$url = $host . $request->key;
if ($request->has("payload")) {
$payload = json_decode(html_entity_decode($request->payload), true);
}
try {
switch (strtoupper($request->method)) {
case "POST":
return Route::dispatch(Request::create($url, 'POST', []));
case "PUT":
$url .= '/' . $payload['id'];
return Route::dispatch(Request::create($url, 'PUT', []));
case "DELETE":
$url .= '/' . $payload['id'];
return Route::dispatch(Request::create($url, 'DELETE', []));
default:
if ($request->has("payload")) {
$url .= "/" . $payload['id'];
}
return Route::dispatch(Request::create($url, 'GET'));
}
} catch (\Throwable $th) {
return [
"data" => $payload ?? [],
"success" => false,
"code" => 500,
"message" => $th->getMessage(),
];
}
}