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) ;)
Related
In most OpenAPI 3.0 documentation (like the official one), links are introduced with a combination of a POST endpoint to create an entity returning an ID linking to a GET endpoint to fetch that entity by that same returned ID:
POST /users -> UserID
GET /users/{UserID} -> User
In Schemathesis context, this would assume an empty data storage and simulate a create+fetch scenario. I would like to test my GET endpoints on a pre-warmed data storage and implement a "fetch a list, then fetch each entity from the list by ID" scenario:
GET /users -> List[User] -> List[UserID]
GET /users/{UserID} -> User for each entry in the list
For this to work, I need an OpenAPI link from the list GET endpoint to the entity GET endpoint, however I can't seem to find either an example or a confirmation that this is indeed possible.
Can I and if so how can I create such a link in OpenAPI 3.0?
OpenAPI links are defined using JSON Pointers. JSON Pointers are quite limited - they can only point to a specific field within a JSON Structure (such as "the first element of this array", or the entire array) and don't support wildcard expressions (such as "any/each element of this array").
This means, for example, that if you have the GET /users endpoint that returns
[
{
"id": "29d590d1-02b2-4aa1-9ce5-98202cc9a619",
"username": "nikolai",
...
},
{
"id": "58091b14-eb06-47ee-b896-50cebdda20ef",
"username": "helen",
...
},
...
]
you can define a link like $response.body#/0/id to indicate that the id of the first user in the list can be used as a parameter to another endpoint. But there's no way for a link to reference an arbitrary/each user from the list.
You can check these discussions in the OpenAPI Specification repository for more details:
Link for array element
Variables/Parameters in Links
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 must design an API to manage a Document entity: the originality of this entity is it can have two different ids:
id1 (number, i.e. 1234)
id2 (number, i.e. 89)
For each document, one and only one id is available (id1 or id2, not both)
Usually I solve this issue by using query parameters to perform some kind of "search" feature:
GET /documents?id1=1234
GET /documents?id2=89
But it works only if there is no sub-entity...
Let's say I want to get the authors of the documents :
GET /documents/1234/authors
Impossible because I can't know what type of id I get: is it id1 or id2 ?
GET /documents/authors?id1=1234
Not really REST I think because id1 then refers to the "Author" entity, not "Document" anymore...
GET /id1-documents/1234/authors
GET /id2-documents/1234/authors
Then you create two URIs that return the same entity (/author) not really REST compliant.
GET /documents/id1=1234/authors
GET /documents/id2=89/authors
It looks like a composite key created only for the API, it has no "backend" meaning. For me it sounds strange to create a "composite" key on the fly.
GET /document-authors?id1=1234
GET /document-authors?id2=89
In this case you completely lose the notion of tree... You end up with an API that contains only root entities.
Do you see another alternative ?
Which one looks the best ?
Thank you very much.
It seems to me that you're conflating two different resources here - documents and authors. A document has a relationship with an author, but they should be separate resources because the authors have existence from any individual document. With that in mind you need to ask whether your clients are searching for authors or documents. If it's authors, then they should be querying an authors API rather than a documents API.
e.g.For all the authors of documents with id1 89 or id1 1234 or id2 4444 you might query like this...
GET /authors?docId1=89&docId1=1234&docId2=4444
That should return a list of author representations. If people care about the documents themselves, the author representations could contain links to the documents.
Alternatively, if you're looking for documents then you should be querying that directly...
GET /documents?id1=89&id1=1234&id2=4444
What you're modelling as a sub-resource isn't really a subresource. It's a relationship between 2 independent resources and should be modelled as a set of links. Each document returned from the documents api should contain a set of authors links (if people really care about the authors) and vice versa from the authors to the documents.
Here's an opinionated solution from SlashDB, which allows for record filtering and traversing to related resources at the same time.
The example is similar to yours - two entities Artist and Album.
Let's identify the Artist first.
Artist by ID:
https://demo.slashdb.com/db/Chinook/Artist/ArtistId/2
Artist by Name:
https://demo.slashdb.com/db/Chinook/Artist/Name/Accept
An Artist may have issued Albums. The two entities are related. We allow extending the URL with the name of the related entity, like so:
https://demo.slashdb.com/db/Chinook/Artist/Name/Accept/Album
You can keep "going", say to get to the Tracks from those albums
https://demo.slashdb.com/db/Chinook/Artist/Name/Accept/Album/Track
And even continue filtering too i.e. only tracks, which are shorter than 300000 milliseconds:
https://demo.slashdb.com/db/Chinook/Artist/Name/Accept/Album/Track/Milliseconds/..300000
I have a question about designing REST APIs.
Lets say that GET /schools/1/students returns all students from a school with the id of 1.
Should POST, PUT and DELETE requests be sent to /schools/1/students to manipulate student resources going forward or to /students?
GET all students - /schools/1/students
GET one student - /schools/1/students/:student_id
POST - create new student (/schools/1/students)
PUT - update ONE student (/schools/1/students/:student_id)
DELETE - delete ONE student (/schools/1/students/:student_id)
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