11

I'm designing a RESTful API and I'm trying to figure out how to show and update foreign keys for resources.

Let's I have an object User and it has an id, name and a foreign key (many-to-one relation) to entity: Computer.

What I see in most of the examples online:

GET /users/1

{ id: 1, name: "Bob", computer: "<url>/computers/5" } 

Which I can understand, it's a link to another resource. But what do you do when you want to pick another computer for bob?

PUT /users/1

{ name: "Bob", computer: "<url>/computers/4" } 

This feels really weird. I'm also thinking about the following case: Say the person that has to implement the API can choose a computer for Bob using a dropdown and the current one should be selected, I'd need the id to do that. Do I have to parse the url myself to chop off the id?

Is there a reason why I should not just do:

GET /users/1

{ id: 1, name: "Bob", computerId: 5 } 

PUT /users/1

{ name: "Bob", computerId: 4 } 

3 Answers 3

4

I would be tempted to formalise the HATEOAS a little here and have:

GET /users/1

{ links: [ { rel: "computer", href: "/users/1/computers/5" }, ], user: { id: 1, name: "Bob", computer: 5, } } 

PUT /users/1

{ name: "Bob", computer: 4, } 

GET /users/1

{ links: [ { rel: "computer", href: "/users/1/computers/4" }, ], user: { id: 1, name: "Bob", computer: 4, } } 

The link URLs can obviously be /computers/n if that's your preference.

This allows your representations to consistently be just data, AND the body of GETs to supply the URLs that your application can use. This way, if you change your URL hierarchy (i.e from /computers/n to /users/m/computers/n) you do not need to change any client code that manually constructs URLs.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your answer. I think this is the most clear and easiest to work with way although it might not be completely following the HATEOAS principles.
3

By GET you have to use the link to meet with the HATEOAS constraint.

{ id: 1, name: "Bob", computer: {uri: "<url>/computers/5"} } 

By PUT you can use the id

{ name: "Bob", computer: {id: 4} } 

The idea about this that the URIs (or URI templates) must be generated by the server. So the client does not have to know how to build an URI, which makes the service+client code DRY, or in other terms it loosens the coupling between the client and the service implementation.

(You can use a standard (or a draft) solution instead of your custom one to describe hyperlinks. For example: ATOM, XLink, RDF+Hydra, HAL, etc..)

2 Comments

Thanks. I like the approach but I don't like using a different identifier in GET and PUT
@geoffreydv It is because the URI is for the clients to follow, while the id is for the server to process. You can send the URI in the PUT instead of the id if you want, but it does not have any advantage. You can add the id and other properties to the GET response, so it is not mandatory to use only a single fetch URI. Usually we give some properties and the clients can follow the URI if they want to elaborate.
2

If you're really embracing HATEOAS, you should just use the URI. The URI is your identifier, not the id field. I would remove that entirely. Sure, inside your application you'll have to parse the URI in the payload -- and you already have the parser, obviously -- but It's better for you to do that than asking the client to do.

If you or your clients are sending id fields other than URIs in the payload, or if you're asking the client to parse URIs and figure out semantics, you're defeating the purpose of using HATEOAS, as the client implementation will be coupled to that semantics. The best thing is to embrace URIs as the only identifiers you'll use to drive the interaction with clients.

2 Comments

Thanks for your answer. For some reason I still feel that while following the HATEOAS principles fully makes some things easier, it makes other things more complicated.
The purpose of REST is to make things easier and more efficient on the long-term. Many of the constraints are directly opposed to short-term simplicity and efficiency. If done right, It WILL make the design phase more complicated, that's for sure. If you don't have actual long-term requirements, you don't need strict adherence to the principles.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.