I recently did some work on a new ASP.NET Web API project using .Net Core. While doing research and development I could see some themes and patterns on What makes a great API
and decided to collate this information.
This was for a RESTful (Representational State Transfer) Web based API (hypertext-driven) with JSON resonse, this was not the API of a new class interface. I dont believe being RESTful makes your API great, SOAP (Simple Object Access Protocol) could work just as well however REST is certainly more popular today. Have a look at SOAP vs. REST: A Look at Two Different API Styles.
For the URI format see HTTP verbs.
Pillars
Clear pillars are needed for a great API, this is certainly not an exhaustive list but these stood out for me.
Security
Dont bolt security on at the end, secure your API from the start.
- Use OAuth 2.0 for authorization
- Never put stack traces in responses, this can disclose sensitive information
- Errors must be represented in Problem JSON (RFC 7807) format, providing error
type
(URI), and optionallytitle
,status
,detail
along with others.
Supported & Well Architected
- Use the OpenAPI specification
- Later updates to the specification can be done at editor.swagger.io
- Document the API first as a proposal and then implement the empty controllers and Generate the OpenAPI specification via Swashbuckle. Use the generated specification to test that the implementation respects the contract.
- Write API documentation and build API client SDKs (Software development kit)
- Utilize patterns, standards, templates, and frameworks to realize the features of an API which are not domain specific.
Consistent
- Be stateless
- have a common look and feel
- The number of items returned by a collection must be limited and paginated (Sorting & Paging)
- Fielding defined appropriate use within HTTP of the canonical verbs
GET
,POST
,PUT
,PATCH
,DELETE
,HEAD
, andOPTIONS
. So don’t change state with GET :). See http-verbs and verb notes below. - Search by criteria, examples
/audits/date?start=&end=
/audits/search?q=foobar
- HTTP header Fields should be in Hyphenated-Pascal-Case format
- Date and Time values must be represented as
YYYY-MM-DDThh:mm:ss[.sss]Z
format strings.- This is ISO 8601
- Dont use null for any represented types
- Resource endpoints must use plural resource forms, example
/foos
- Resources and sub-resources must be hierarchically identified, examples
/{resources}/[resource-id]/{sub-resources}/[sub-resource-id]
- Use hyphens to to improve readability
- GOOD:
/{resources}/[resource-id]
- BAD:
/{resources}/[resourceId]
- GOOD:
- Always use lower case in URI paths
- Resources naming
- document, this is a single resource inside the collection. eg:
/{resources}/
document would beresource
- collection, this is a collection of documents. eg:
/{resources}/
- store, WAT
- controller, this is a procedural concept / executable function with parameters. eg:
/{resources}/{id}/some-thing-related-to-resource/archive
(archive is the controller)
- document, this is a single resource inside the collection. eg:
Verb Notes
GET
- returns
200
when the resource or collection is found, the resource or collection is returned in the body - returns
404
and no body when nothing was found - should not have a payload, if you are in a situation where you need to send heaps of data as encoded query parameters then rather use a POST with content body
GET Examples
1 | GET /users/12345 ~ filter in user 12345 |
1 | GET /users?name=John%20Doe ~ filter by name, this would return an array |
1 | GET /users ~ get all users |
1 | GET /users ~ example of how pagination can be added |
PUT
- used to do a resource update, complete content is provided
- sometimes partial resource is provided (here you should consider a PATCH)
- returns
200
if the update was completed and no body, generally rather return204
No Content - returns
201
if something was created (here you should consider a POST)
PUT Examples
1 | PUT /users/12345 |
This request will replace the entire user resource with the new data.
POST
- used to create a single complete resource, not idempotent
- returns
200
if successful, the resource or collection is returned in the body - returns
201
if something was created, the resource or collection is returned in the body - returns
202
if the request was accepted and will be completed later - returns
204
withLocation
header if the resource is not returned
POST Examples
1 | POST /users |
PATCH
- used to do a resource update, partial content is provided
The main difference between HTTP PATCH and PUT is that PATCH is used for partial updates to a resource, while PUT is used for full updates. This means that with PATCH, you can send only the data that you want to update, without having to send the entire resource again. With PUT, you must send the entire resource, even if you only want to update a small part of it.
PATCH Examples
1 | PATCH /users/12345 |
This request will update the user’s name to “John Doe”, without modifying any of their other data.
DELETE
- used to delete a resource
- returns
200
with the deleted resource in the body - returns
204
if you dont wish to return the deleted resource - returns
404
if the resource you wanted to delete was not found - returns
410
if the resource was already deleted
DELETE Examples
1 | DELETE /users/12345 |
HEAD
- used to retrieve header information, often used by web crawlers to check the availability and last modification date of resources before downloading them
- its the same as a GET but only returns header and never a body
HEAD Examples
1 | HEAD /index.html |
OPTIONS
- used to inspect the avalible operations (so the HTTP verbs) of a given endpoint
OPTIONS Examples
1 | OPTIONS /users HTTP/1.1 |
1 | Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS |
Performance
- Allow to scale horizontally by using Docker containers and orchestration software like Kubernetes
- Rate limit client requests using HTTP Status Code 429 Too Many Requests
Ability to change
- Avoid breaking changes, dont remove fields/methods/add additional validations
References
- https://swagger.io/resources/articles/adopting-an-api-first-approach/
- https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api
- http://spec.openapis.org/oas/v3.0.3
- https://tools.ietf.org/html/rfc3339
- https://tools.ietf.org/html/rfc7807
- https://www.elastic.io/6-characteristics-of-great-api/