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