What would be the right way to design a restful API?

As an example I would have assumed that it would be something like:

  • Post api/users (creates a new user)
  • Get api/users (returns a list of all users)
  • Get api/users/:id (returns a single user)

etc.

I’m asking as the provided example for the Apis and Microservices Projects - Exercise Tracker project [See Here] suggest new users be created via POST /api/exercise/new-user.

In m,y mind this wouldn’t follow restful principles?

A related concern is the suggest that this GET /api/exercise/log?{userId}[&from][&to][&limit] would be the appropriate way to design an API that allows you to return a list of exercises for a user. As I understand it the more accepted way would be GET api/user/:userID/exercises/[search params].

Any comments one way or the other?

Yeah, I think the structure of an API’s endpoints is not really well defined there.

I try to think in terms of, for example, NoSQL (like Mongo) model schemas. The Schema itself would seem to be the User (the ‘root node’ of the JSON object, perhaps), and the exercises exist only in the context of a given user. Now, if I can create a new user and, once I log in as that user, only see my own activities, then it would no longer make sense to even offer that /user option – it’s assumed that the /user/:user-id would be me. At that point, the endpoint COULD be /exercise, but then it would be GET, POST, PATCH, DELETE or whatever at that endpoint.

It would seem that:

  • GET /exercise should return the complete exercise log for the current user.
  • GET /exercise?from=<start-date>?&to=<end-date>&limit=<limit> should return the filtered exercise list for the current user (or GET /exercise/from/:startDate/to/:endDate/limit/:limit )
  • POST /exercise with form data should add an exercise event to the current user.
  • DELETE /exercise/:id should delete an exercise from the current user.

All this would be predicate on some sort of JWT, or some stateless mechanism to track the currently-logged-in user.

If, instead, I’m simulating a superuser, then I should have the same API for users:

  • GET /user should return all users
  • GET /user/:id should return a specific user
  • POST /user should allow the creation of a user, and return that user object.
  • DELETE /user/:id should remove all trace of a given user record.

And under that:

  • GET /user/:id/exercise should return the full exercise log of a given user.
  • … all the exercise activity should be the same as the first list above, predicated with /user/:id

Then there are the margin cases of, for example, what would be the API call to find all users who ran a treadmill on August 11th,2018? Would that be at the /user endpoint? Or perhaps GET /user/exercise/filter/:filterType/from/:startDate/to/:endDate with no user specified and the exercise itself as a filter?

Largely a thought experiment, as they have specified the API they want. Were this a customer spec, I’d probably meet with them and see if they are married to a given endpoint list, or if this is flexible. In some cases, you are trying to create a set of endpoints to match something that already exists, so you are stuck with what they give you. As an example, if they’re upgrading the back end from perl (?!) to node, but nothing on the front end is changing. Again, margin case.

2 Likes

Hey @snowmonkey, firstly thanks for your super in-depth reply! :slight_smile:

Glad to hear that I wasn’t totally crazy.

You’ve raised a bunch of really interesting points that I hadn’t considered. The superuser use case, in particular, is interesting as I could see in a larger system it requiring a number of very specific API’s.

The margin case is an interesting one. If you also consider how apps change over time (and quite rapidly in early iterations) then margin cases are often more likely to occur than you would first think. As an example, if a designer decides to add a panel that shows the most pushups recorded for the day then you have another API to create. In my mind, it lends weight to two different approaches.

  1. Offload lots of the business logic to the frontend i.e. provide very broad API’s and allow the frontend to manipulate, filter etc. (this has obvious impacts on performance when you get large data sets).
  2. GraphQL also seems like an interesting way to address the problem
1 Like