Introduction This is yet another blog post about how to build a REST API, however from my perspective and focusing on using Microsoft .NET technologies.
Guidelines Consistency - While it’s not essential to adhere strictly to particular standards, it’s crucial to maintain consistency in your approach. Follow the OpenAPI spec, either generate the OpenAPI spec from your API or write the spec and generate your API. Having an OpenAPI spec means a client can be generated, documentation can be generated, contract testing tooling can be bootstrapped and more. Use nouns instead of verbs GET /users, not GET /getUsers Use plurals instead of singular nouns GET /users/1/activites/10 and GET /users/1/activites instead of GET /user/1/activity/10 and GET /user/1/activity Use the right verbs GET, Retrieve a record GET /users/1 or collection of records GET /users POST, Create a record PUT, Update a record, should be idempotent - calling it once or many times produces the same result with no side effects DELETE, Delete a record PATCH, Partially update a record by supplying only the necessary information. Can be combined with a standard like JSON Patch Use the right status codes, I most commonly find myself using the following: 200 - OK, Succesful, with the response containing a body. For a GET this might be the requested resource, for a POST this might be the created resource 204 - No Content, Succesful, but no response body. We’ve done a PUT to update a resource 400 - Bad Request, Something in the request object is invalid, this could be something like the name of a property or the value of a property 401 - Unauthorized, You’re not logged in 403 - Forbidden, You’re logged in but you can’t access this resource 404 - Not Found, It doesn’t exist 409 - Conflict, You’re trying to create something that already exists 500 - Internal Server Error, 40x error codes indicate a problem on the client, and 500 indicates something unexpected went wrong on the server. Avoid nesting, or at least avoid nesting more than one level deep. Nesting creates a dependency between the entities. Use the query string to Filter, this could be general filtering based on a value, sorting, paging, or selection of fields. GET /users?country=UK GET /users?sort=birthdate_date:asc GET /users?limit=120&offset=1 GET /users/1?fields=name,email or GET /users?fields=name,email for a collection Error handling should be done in a consistent way that can be handled by the calling client with ease. I like to split an error into a unique identifier, a name, and a description 1 { 2 "code": "validation-error", 3 "message": "Name too long", 4 "description": "Name can't contain more than 100 characters" 5 } Wiggle room As with a lot of patterns and principles, how they’re used can depend on the context. As an example I think it’s fine to do a PUT to update a record and then return that record in the response, sometimes this is more convenient, maybe there are calculated fields that would require the following GET to retrieve but by returning the object in the response we can remove the need for that GET. An example might be that we display the last updated date of the record. What I believe is important is that we’re consistent with this behavior, if we’re returning an object as part of a PUT for one entity we should do it for all.
...