Question

I have a question about how to structure a response where I return objects related to other objects (one-to-many/many-to-one), and to tell me if the way I've structured my endpoints are wrong or not, or if there is a better way.

My scenario: I have an JS app built on Flux where I have these stores: - Meetings - Users

For the sake of simplicity, let's say the enduser is a meeting manager, and wants to see a table/list of all meetings, and each row will display the meeting description and date, plus the attendees' names, emails, and phones.

So on page load I do a request to the route /meetings which returns an object like this:

{
    meetings: [
        {id: 1, date: '2016-01-01', attendees: [1, 2, 3]},
        {id: 2, date: '2016-01-02', attendees: [2, 3, 4]},
    ],
    allUsersIntersectedByMeetings: [
        {1, name: 'a', phone: '1800-01111'},
        {2, name: 'b', phone: '1800-02222'},
        {3, name: 'c', phone: '1800-03333'},
        {4, name: 'd', phone: '1800-04444'},
    ]
}

If you notice, I've separated the users data from the meetings array, this is to compact the result and prevent sending duplicated information about the same user (in my example the user data is pretty small, but in real life there could be many columns on the user table).

The reason I've decoupled meetings and users, is that I don't want to return tons of duplicated data, because in real life I'll be pulling at least one hundred meetings in one request, and users could repeat many times across different meetings.

So with this structure, I'll put all the customers in my front-end flux store 'Users', and I'd keep all my data n'sync. Example, If I wanted to edit a user's phone, I'll do it directly on the Users store, and that would propagate thru the UI where other meetings reference the same user. I don't have to touch the meetings objects at all.

Also, I could run into an issue where I'd also need to return nested data on the Users, like their company info:

{userId: 1, name: 'a', phone: '1800-0111', email: 'bla@bla.com', companyId: 10}

{companyId: 10, phone: '1900-02222', name: 'ACME INC'}.

Do you think I'm worrying too much about network bandwidth? Should I just return the meetings with the populated users' data (ala' MongoDB)?.

Or should I just return the meetings and execute a separate request to fetch unique users under users API endpoint like '/users?filterById=1,2,3' ? Although I like this approach I'd be worried about speed, if for example I let the enduser to search meetings on an input box and on each input change event refresh the results (like an elastic search). I think the results would feel very slow to show up if I was required to show its users' info at the same time. Thoughts?

Is there a defacto way to solve this issues with REST? What would be your suggestions?

Thanks.

Was it helpful?

Solution

I don't know Flux, and my answer is "it depends".

Edit with summary: I would choose between either (A) including attendee resrouces directly under meetings (and not optimizing as you have done), or (B) not include them at all and only provide URIs of the attendee resources. Choice (A) is a reasonable compromise in my eyes.

If you're aiming to be as RESTful as possible, including two top-level resources (meetingsand allUsersIntersectedByMeetings) don't fit well.

Instead, you could have your /meetings collection of meetings that's responsible for returning meetings. A network maybe-not-efficient way is to add links from a meeting resource to the URL of each of its attendees.

It's possible then that your browser will cache attendees (people) that it has already request in the past.

Rather than links, you can include the attendee objects direclty, like:

{ "meetings": [ { "id": 1, "attendees": [ { "id": 1, "name": "Oscar" } ] ] }

By the way, the "JSON API" standard uses an includes section like your original example. I'm not sure how RESTful or not that approach is.

OTHER TIPS

An equally good argument to normalize or flatten nested data is when it comes to updating this data. This is well explained here. Because this is a common use-case, Dan Abramov created normalizr, later re-written and now maintained by Paul Armstrong.

But if you also control the API, and you probably store the data in normalized form in the database, it's rather silly to de-normalize it in your API endpoint only to normalize it straight after in your client (using normalizr)... In that case, certainly if you're the only consumer, just leave the data normalized and pass it over the wire to your client like that.

Concretely you'd have something like this

GET /meetings

{
    "result": ["1", "2"],
    "entities": {
        "meetings": {
            "1": { "id": 1, "date": "2016-01-01", "attendees": [1, 2, 3] },
            "2": { "id": 2, "date": "2016-01-02", "attendees": [2, 3, 4] }
        },
        "users": {
            "1": { "id": 1, "name": "User 1" },
            "2": { "id": 2, "name": "User 2" },
            "3": { "id": 3, "name": "User 3" },
            "4": { "id": 4, "name": "User 4" }
        }
    }
}

Such response is very straightforward to merge into your store in a generic way.

If you have multiple consumers, you might opt for a normalize=true query parameter. You might also want to combine this with some kind of expand|include=entities,to,include query parameter.

Finally, note that the JSON API spec doesn't play nicely with the normalized structure of flux/redux stores.

Further reading:

Licensed under: CC-BY-SA with attribution
scroll top