Notes/Advanced Programming/projects/API Design Research.md

162 lines
8.1 KiB
Markdown
Raw Normal View History

2024-12-07 21:07:38 +01:00
---
type: practical
---
- [Updating information](#Updating%20information)
- [The existence of the `PATCH` request](#The%20existence%20of%20the%20%60PATCH%60%20request)
- [Usage of `PUT`](#Usage%20of%20%60PUT%60)
- [Complete request example](#Complete%20request%20example)
- [Incomplete request example](#Incomplete%20request%20example)
- [Usage of `POST`](#Usage%20of%20%60POST%60)
- [General "style" remarks](#General%20%22style%22%20remarks)
- [Conclusion / TL;DR](#Conclusion%20/%20TL;DR)
## Updating information
Can we have optional fields in the update `PUT` and `POST` request?
### The existence of the `PATCH` request
>The **`PATCH`** HTTP method applies partial modifications to a resource.
>`PATCH` is somewhat analogous to the "update" concept found in [CRUD](https://developer.mozilla.org/en-US/docs/Glossary/CRUD) (in general, HTTP is different than [CRUD](https://developer.mozilla.org/en-US/docs/Glossary/CRUD), and the two should not be confused).[^1]
Hence, this definition also answers the question as to why we **should be careful with the usage of `PATCH`** if we consider it.
### Usage of `PUT`
>The PUT method requests that the state of the target resource be
created or replaced with the state **defined by the representation**
**enclosed in the request message payload**. [^3]
This implies that complete resource representation **is required** (all the fields), as Jackson(Spring) will reset the missing ones to their default values (e.g. int = 0, boolean = false, String = null, etc.)
#### Complete request example
*Request:*
```json
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"author": "Yo mama",
"genre": "Horror",
"publisher": "Team 22",
"publishDate": "2023-09-01",
"pages": 320
}
```
*Response:*
```json
<<< 200 OK
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"author": "Yo mama",
"genre": "Horror",
"publisher": "Team 22",
"publishDate": "2023-09-01",
"pages": 320
}
```
#### Incomplete request example
(this is assuming the above request happened already)
*Request:*
```json
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"publisher": "Team 22",
"pages": 320
}
```
*Response*:
```json
<<< 200 OK
{
"isbn": "978-3-16-148410-0",
"name": "Among us story",
"author": null,
"genre": null,
"publisher": "Team 22",
"publishDate": null,
"pages": 320
}
```
### Usage of `POST`
Contrary to `PUT`:
>The `POST` method is **used to request that the target resource process the enclosed representation according to the resource's own specific semantics**. The meaning of a `POST` request is determined by the server and is usually dependent on the resource identified by the Request-URI.[^4]
Therefore we *can* send requests which contain data that are non-complete resource representations. We do have to explain this to the other team tho...
i.e.
*Request:*
```json
{
"isbn": "978-3-16-148410-0"
}
```
*Response:*
``` json
<<< 201 Created
{
"isbn": "978-3-16-148410-0",
"name": null,
"author": null,
"genre": null,
"publisher": null,
"publishDate": null,
"pages": 0
}
```
## General "style" remarks
(based on the API conventions + CRUD principle)
>The base URL should be neat, elegant, and simple so that developers using your product can easily use them in their web applications. A long and difficult-to-read base URL is not just bad to look at, but can also be prone to mistakes when trying to recode it. Nouns should always be trusted.[^2]
Hence, using a verb in `POST /api/v1/book/create` and `PUT /api/v1/book/update` is not ideal.
Table[^2] of common conventions for the usage of HTTP requests:
| **Resource** | `POST` | `GET` | `PUT` | `DELETE` |
| ------------------- | --------------------------------- | ----------------------------------- | --------------------------------------------- | -------------------------------- |
| /customers | Create a new customer | Retrieve all customers | Bulk update of customers | Remove all customers |
| /customers/1 | Error | Retrieve the details for customer 1 | Update the details of customer 1 if it exists | Remove customer 1 |
| /customers/1/orders | Create a new order for customer 1 | Retrieve all orders for customer 1 | Bulk update of orders for customer 1 | Remove all orders for customer 1 |
Hence, in our case:
| **Resource** | `POST` | `GET` | `PUT` | `DELETE` |
| ------------- | ----------------- | -------------------------------------------------------- | ------------------------------------------------ | ----------------------- |
| /books | Create a new book | Retrieve all books (+ optional parameters for filtering) | 405 | Remove all books |
| /books/{isbn} | 405 | Retrieve the details for book with `isbn` | Update the details of book with `isbn` if exists | Remove book with `isbn` |
## Conclusion / TL;DR
Given the information provided above, I propose we change the current specification as follows:
| Method | Old URL/Description | New URL/Description | Reasoning |
| -------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `POST` | `/api/v1/book/create` - Create book - pass JSON body (isbn is required) | `/api/v1/books` - Create a book - pass JSON body (isbn is required) | Use resource-based naming convention (`books` as a collection). HTTP POST implies creation; no need for `/create`. |
| `PUT` | `/api/v1/book/update` - Update book - pass JSON body (isbn is required) | `/api/v1/books/{isbn}` - Update a specific book - pass JSON body | Use path parameter (`{isbn}`) to specify which book to update. HTTP PUT implies updating a specific resource. |
| `GET` | `/api/v1/book?genre={genre}&author={author}&data="csv"` - Get book(s) by property in csv/json format | `/api/v1/books?genre={genre}&author={author}` - Get books by property. Use `Accept` header for csv/json format | Use plural `books` for collections. Use HTTP `Accept` header for content negotiation (CSV/JSON), keeping URLs clean. |
| `DELETE` | `/api/v1/book?genre={genre}&author={author}` - Delete book(s) by property | `/api/v1/books` - Delete books - pass filter criteria in body or delete one by `/api/v1/books/{isbn}` | `DELETE` requests with body for filtering criteria, or use path param for deleting a single resource for better clarity. |
| `POST` | `/api/v1/book/import?data="csv"` - Import books from csv/json file (isbn is required for each) | `/api/v1/books/import` - Import books from CSV/JSON file - pass file in body; use `Content-Type` header | Use resource-based naming (`books/import`). The `Content-Type` header indicates file type (CSV/JSON); keep query params clean. |
[^1]: [Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH)
[^2]: [Swagger](https://swagger.io/resources/articles/best-practices-in-api-design/)
[^3]: [RFC 7231 - PUT](https://datatracker.ietf.org/doc/html/rfc7231#autoid-37)
[^4]: [RFC 7231 - POST](https://datatracker.ietf.org/doc/html/rfc7231#autoid-36)