In a RESTful API backend for a mobile app client we have a resource, User, with the database field birthday.
When Sam is authenticated an request his own profile, GET /user/1, we receive a resource like this:
{
id: 1,
name: "Sam Smith",
birthday: "1990-01-01",
age: 27
}
In this scenario, age is merely a convenience for the client, that won't have to calculate Sam's age.
But when a stranger requests Sam's profile, GET /user/1, he receives a slightly different resource:
{
id: 1,
name: "Sam Smith",
age: 27
}
Here, birthday is omitted for privacy, and now age is not just about convenience, it is needed, because it's not possible for the client to calculate age without the birthday.
I am already instructing the client that some fields may be omitted depending on permissions.
But when Sam wants to update his profile, he takes the resource he received before, alters it, and PUT /user/1.
{
id: 1,
name: "Sam Smith",
birthday: "2000-02-02", // Birthday was changed
age: 27 // Should he update the age? It's the same data source as birthday.
}
Server-side I could choose to ignore age, but a client might think they can alter the age field directly. I could forbid the age field on PUT resources, but then the client would have to modify resource schema that it received from the GET request.
What are best principles in a situation like this?
Should the client really calculate the new age in the PUT request, even though it will then be ignored by the server, otherwise returning a HTTP 409 conflict response?
Related
I have a REST API with a GET endpoint to retrieve a product based on the id. Now I need to retrieve a product based on another attribute and I don't know if I have to use the same endpoint and handle the multiple cases there or write different endpoints, one for each case.
Thanks!
Let's say you have
GET /product/{id}
And you want to be able to lookup products by some other attribute. If the attribute uniquely identifies a product you could do:
GET /product?someOtherAttribute=foo
return
302 FOUND
Location: /product/5
If you want to avoid the roundtrip you could return the contents of product directly, but as you pointed out that may require additional logic in your controller.
If the attribute does not uniquely identify a product, I'd recommend a paginated collection response:
GET /products?someOtherAttribute=foo
return
200 OK
{
products: [{
url: "/product/5"
}, {
url: "/product/6"
}]
}
I have two collections: persons and pets. A person may have as many pets as they want.
Person document:
person {
id: person-id
data: person-info
}
Pet document:
pet {
id: pet-id
data: pet-info
personId: person-id
}
This is my API naming design
GET all pets from a person: /api/pets/:personId
GET all pets with condition: /api/pets/:personId?age_greater_than=4
POST create new pet: /api/pets with the request body that contains person-id
PUT update pet info: /api/pets/:petId with the request body that contains person-id and updated info
DELETE delete pet: /api/pets/:personId with request body that contains pet-ids
Is there something wrong with my API naming convention and how can this be improved? I think that passing person-id directly to /api/pets is kind of weird.
Naming conventions can be found here: https://restfulapi.net/resource-naming/
Please also check again HTTP methods and REST.
Basic concept is that URLs represent resources and HTTP methods you apply to those URLs indicate what you want to do with those resources.
GET - Read resource(s)
PUT/POST - Create resource(s)
PATCH - Update resource(s)
DELTE - Delete resource(s)
So maybe you could use PATCH instead of PUT for updating resources if you only change a part of the resource's attributes.
Also in the DELETE example you should use the pet id as path variable and not person id.
For getting all pets of a person, I think /api/person/id/pets could be more straightforward as /api/pets/personid because when you see the URL /api/pets/23 you don't know if it is pet 23 or all pets of person with id 23.
Think about the best practices again (just use a search engine of your choice) ;)
Let's say we have schools with some data including a name and a list of students, and students with some data including courses they're enrolled in and a reference to their school. On the client:
I'd like to show a screen that shows information about a school, which includes a list of all of its students by name.
I'd like to show a screen that shows information about a student, including the name of their school and the names of courses they're taking.
I'd like to cache this information so that I can show the same screen without waiting on a new fetch. I should be able to go from school to student and back to school without fetching the school again.
I'd like to show each screen with only one fetch. Going from the school page to the student page can take a separate fetch, but I should be able to show a school with the full list of student names in one fetch.
I'd like to avoid duplicating data, so that if the school name changes, one fetch to update the school will lead to the correct name being shown both on the school page and the student pages.
Is there a good way to do all of this, or will some of the constraints have to be lifted?
A first approach would be to have an API that does something like this:
GET /school/1
{
id: 1,
name: "Jefferson High",
students: [
{
id: 1
name: "Joel Kim"
},
{
id: 2,
name: "Chris Green"
}
...
]
}
GET /student/1
{
id: 1,
name: "Joel Kim",
school: {
id: 1,
name: "Jefferson High"
}
courses: [
{
id: 3
name: "Algebra 1"
},
{
id: 5,
name: "World History"
}
...
]
}
An advantage of this approach is that, for each screen, we can just do a single fetch. On the client side, we could normalize schools and students so that they reference eachother with IDs, and then store the objects in different data stores. However, the student object nested inside of school isn't a full object -- it doesn't include the nested courses, or a reference back to the school. Likewise, the school object inside of student doesn't have a list of all attending students. Storing partial representations of objects in data stores would lead to a bunch of complicated logic on the client side.
Instead of normalizing these objects, we could store schools and students with their nested partial objects. However, this means data duplication -- each student at Jefferson High would have the name of the school nested. If the school name changed just before doing a fetch for a specific student, then we'd show the right school name for that student but the wrong name everywhere else, including on the "school details" page.
Another approach could be to design the API to just return the ids of nested objects:
GET /school/1
{
id: 1,
name: "Jefferson High",
students: [1, 2]
}
GET /student/1
{
id: 1,
name: "Joel Kim",
school: 1,
courses: [3, 5]
}
We'd always have "complete" representations of objects with all of their references, so it's pretty easy to store this information in data-stores client side. However, this would require multiple fetches to show each screen. To show information about a student, we'd have to fetch the student and then fetch their school, as well as their courses.
Is there a smarter approach that would allow us to cache just one copy of each object, and to prevent multiple fetches to show basic screens?
You might be mixing two concepts: Storage and Representations. You can give back a non-normalized representation (the first option you suggested) without also storing those "partial" object in your database.
So I would suggest to try to return non-normalized representations, but storing them normalized (if you are using a relational DB).
Also, an improvement suggestion: You may want to use proper URIs instead of Ids in your representations. You probably want the clients to know "where" to get that object from, it's easier therefore to just supply the URI. Otherwise the client needs to figure out how to produce a URI out of an Id, and that usually ends up being hard-coded in the client, which is a no-no in REST.
I'm dealing with tree-structred resources. Every item resource has children. Each child type may be content resource or subject resource - which is determind by the type_id. (There could be more children types in the future).
What URI should be used for creating a new child for an item?
POST /api/items/<item_id>/children
(deliver the type_id via the JSON)
OR:
POST /api/items/<item_id>/children/contents
POST /api/items/<item_id>/children/subjects
OR: redirection according to the type_id to:
POST /api/contents
POST /api/subjects
And then using the GUID of the new resource for creating the hierarchy connection.
Thanks!
If your children have an attribute named type which can be subjects or contents, you can treat that as any other attribute, e.g. a gender that can be male or female.
Ideally, you would create a new child with
POST /api/items/<item_id>/children
{
"some_value": 50
"type": "subject",
}
or
POST /api/items/<item_id>/children
{
"some_value": 134
"type": "content",
}
No need to make a confusing endpoint for a simple attribute. If you'd do this for the type attribute, you might as well do it for all the other attributes as well, leading to a great many more endpoints pointing to basically the same resources, that is not something you want.
Later you can fetch them by type, for example getting all subjects would be
GET /api/items/<item_id>/children?type=subject
One of the requirements for our REST interface is that each resource be identifiable by unique fields (aside from the primary identifier). The reason for this is that we want to be able to handle bulk importing data - in which case the client can't know the system generated primary identifiers.
This means we have to be able to reference our resources by unique fields. Using a primary key our read requests look like this:
GET example.com/rest/customers/1
and to get orders related to that customer
GET example.com/rest/customers/1/orders
Now, lets assume two fields in customer identify it uniquely, name ("foo") and businessId ("bar"). Given that, I came up with the following URI to get the orders for this customer:
GET example.com/someotherpath/customers/foo,bar/orders
But I don't like that I have a different path to identify that this is a resource being accessed via unique fields. How would you structure the above query in a RESTful way using unique fields instead of the primary key?
Further, an order looks like this:
{
<SNIP>
"orderId" : "42"
"_links": {
"customer": {
"href" : "rest/customers/1"
"key": [ "foo", "bar" ]
}
},
}
Any issues with allowing client to interchangeably specify href OR key when communicating with the interface?
For the first bit, I just wouldn't do it. If a customer has a unique id (and they should), I wouldn't allow end users to specify N other fields that happen to also uniquely identify the customer. It's messy for the user (which field goes first?) and also messy for you on the back end.
For the second bit, the issue is: what happens when they specify both? Which takes precedence? Are they going to remember? Do you want to have to support both? It's generally a good idea to only allow one way to do any particular thing if you can get away with it.