4
\$\begingroup\$

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).

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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 what switch-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 the return. I regularly advise against single-use variable declarations unless they valuably reduce code width or improve readability.
  • else if should be elseif 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(),
                ];
     }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.