1

I've started creating a RESTful API (well, I did my best, I'm trying to follow the patterns) and I have stumbled upon a scenario that I'm not really sure how to handle. I will explain the current structure:

My application has 4 controllers:

  • Customers
  • Payments
  • Log

Taking as example the Customers controller, I have defined the following actions:

  • GET /customers: returns a list of customers
  • POST /customers: creates a new customer
  • GET /customers/{id}: returns the customer with the provided id
  • PUT /customers/{id}: updates the customer with the provided id
  • DELETE /customers/{id}: destroys the customer

This is the full code of the Customer controller:

namespace App\Http\Controllers;

use App\Customer;
use Illuminate\Http\Request;

class CustomerController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return Customer::all();
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $customer = Customer::create($request->all());
        return response()->json($customer, 201);
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Customer  $customer
     * @return \Illuminate\Http\Response
     */
    public function show(Customer $customer)
    {
        return $customer;
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Customer  $customer
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Customer $customer)
    {
        $customer->update($request->all());
        return response()->json($customer, 200);

    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Customer  $customer
     * @return \Illuminate\Http\Response
     */
    public function destroy(Customer $customer)
    {
        $customer->delete();
        return response()->json(null, 204);
    }
}

The code is very similar in the other controllers. It's also important to note that:

  • A Customer can have multiple Payments
  • A Customer can have multiple records in the Log

The problem starts here:

I need to display in the front-end a summary page with all customer data (name, email, registration date, etc) and a box showing the number of payments made and another box showing the number of entries in the Log.

Do I need to make 3 requests? (One to /customers/id, other to customers/id/payments and other to customers/id/logs)

If I return all the customer related data in the customers/id call, am I breaking the RESTful convention?

TJ is too short
  • 827
  • 3
  • 15
  • 35
  • i guess logs and payments are tied together? – mrhn Jan 23 '20 at 14:16
  • Logs keeps records of all actions made by Customers in the system and when those actions were made. For example, customer1 logged in yesterday. Payments is a list of payments made by Customers. They need to pay to access the service – TJ is too short Jan 23 '20 at 16:34

2 Answers2

1

I am using apigility, but my answer still will be related to your question. According to the REST terminology (which could be find here https://apigility.org/documentation/intro/first-rest-service#terminology ) You are talking about entity and collection.

/customers/id - entity,
/customers/id/payments - collection,
/customers/id/logs - collection. 

These are 3 different requests. So, yes, you need make 3 different requests.

But, to be honest, if you don't need pagination over payments and logs you can have only one request to /customers/id and within response you can have fields with array

{
    "_links": {
        "self": {
            "href": "http://localhost:8080/status/3c10c391-f56c-4d04-a889-bd1bd8f746f0"
        }
    },
    "id": "3c10c391-f56c-4d04-a889-bd1bd8f746f0",
    ...
    _payments: [
        ...
    ],
    _logs: [
        ...
    ],
}

Upd (duplicate from comment for future visitors).

Also, you should pay attention to DTO. I suppose this link will be interesting https://stackoverflow.com/a/36175349/1581741 .

Upd2.

At current moment I treat your collection /customers/id/payments like this:

/payments?user_id=123

where user_id is filtering field on payments table.

webprogrammer
  • 2,393
  • 3
  • 21
  • 27
  • Thanks very much for your answer. That's exactly what I was thinking and the _payments and _logs key in my case just need to be a number. The summary page just displays a summary of the customer and the system has other screens to see full details. Just to confirm, this approach doesn't break the REST convention? I mean, I'm returning not only data from the Customer model, but I will be creating kind of a mixed model – TJ is too short Jan 23 '20 at 16:38
  • 1
    @TJistooshort, I am not sure about breaking REST convention. Also, you should pay attention to DTO. I suppose this link will be interesting https://stackoverflow.com/a/36175349/1581741 . – webprogrammer Jan 23 '20 at 17:11
  • Laravel provides a transformation layer called API Resources (https://laravel.com/docs/5.8/eloquent-resources). Am I right if I say that this is a DTO implementation? – TJ is too short Jan 23 '20 at 17:27
  • 1
    If you take a look at this link again https://apigility.org/documentation/intro/first-rest-service#terminology you will see the third term "resources". I suppose, laravel resources is the same. At current moment I didn't use DTO and didn't finished my researching about it. Wait a minute I will make update to my answer. – webprogrammer Jan 23 '20 at 17:31
  • thanks very much for your help. I will change my controllers to use resources then. – TJ is too short Jan 23 '20 at 17:41
  • 1
    for now I will keep the previous format of the url (customers/id/payments), just because the list of payments can be also filtered by status (pending, failed, successful), date, etc. However your approach seems correct as well. Sometimes I get frustrated when I need to find out what's the best option to accomplish something, when there are many options available – TJ is too short Jan 23 '20 at 17:45
  • I've ended up following your strategy because it seems more robust. For example, I needed to create a stats page showing how many payments were processed by day by the system and it was quite easier to have all the logic in an endpoint `/payments` and use filters as you suggested. Thanks very much! – TJ is too short Jan 27 '20 at 15:35
  • @TJistooshort, welcome! Yes, requirement of list with pagination is a good argument for endpoint. – webprogrammer Jan 27 '20 at 15:56
0

I think your problem that you confuse your REST API with your database. They don't have to follow the same structure. You can easily return the whole nested JSON for GET /customers/{id} if that's what you need from your REST API.

inf3rno
  • 24,976
  • 11
  • 115
  • 197