Geoffrey De Vylder
Geoffrey De Vylder

Reputation: 4153

RESTful API design - how to handle foreign keys?

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
}

Upvotes: 11

Views: 10899

Answers (3)

Pedro Werneck
Pedro Werneck

Reputation: 41898

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.

Upvotes: 2

inf3rno
inf3rno

Reputation: 26129

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

Upvotes: 3

Mark Gibson
Mark Gibson

Reputation: 134

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.

Upvotes: 4

Related Questions