5 ms·
When doing my last bigger API i read every recommendation. Still learned a lot. Everyone has different recommendations - dont get discouraged by this. Make s
by andreasklinger 10y ago
When doing my last bigger API i read every recommendation. Still learned a lot.
Everyone has different recommendations - dont get discouraged by this.
Make sure also to look into newer standards like JsonAPI if they are suitable - last time i tried to use it the tooling around it was still not strong enough and i decided to go w/ simpler custom api.
Assuming it has to be a restful api (vs graphql) and assuming you want to create an api for multiple kinds of clients (that's the the harder part) - Here my personal TL;DR:
- Autogenerate your docs with your tests
- Do versioning in URL (easier to route/cache/etc)
- Worry about caching (a lot)
- Personalized info only in isolated namespace, rest is fully cacheable
- Never embed personalized information (eg not `{ post: { user_has_commented: true } }`
- Never nest data (not `post: { author: { … } }` but reference only `post: {author_id: …}`)
- Embed referenced objects only by whitelist
- Never nest routes (not `/posts/343/comments` but `/comments?post_id=232`) filtering tends to become more complex
- Use public feedback tools (eg github issues) for your user questions/complains - so it can become searchable for people with similar problems
hth - happy to answer some of those in detail if useful
As said - highly subjective opinions - i am sure others might disagree w/ some of the points
- pbreit 10y agoWouldn't most people disagree with: "not `/posts/343/comments` but `/comments?post_id=232`"?
- misterbwong 10y agoThis is one issue I struggled with in my last API design. I generally like the /comments?post_id=232 format but in many cases it's not intuitive. Especially when the sub-resource (i.e. comments) is only ever referenced in with a parent resource (i.e. posts). This is a bit of a contrived example but with a structure like /comments?post_id=232, a developer might mistakenly assume comments are independent resources and not explicitly tied to posts when, in reality, there are can be no comments without posts. In this case, /posts/343/comments is much more intuitive. The way we ended up handling it was forcing ourselves to limit sub-resources to a maximum of 1 level deep. So /posts/343/comments was allowable but something like /posts/343/comments/23/author was not allowed.
- friendzis 10y agoIn my view, arbitrary limits are also not too intuitive :) Problem with nesting is that very rarely your resource classes will make an acyclic graph. Even in your example '/posts/343/comments/23/author' resource class AUTHOR may be a child of either comment or the post itself. And if a user wants to view all posts by particular author? Intuitive use might as well be '/posts/343/comments/23/author/posts/123/comments' ad infinitum :) Such problems can be solved by providing an endpoint for each distinct resource class and making it search provider. In your example case 3 endpoints are needed: /post, /comment, /author, all accepting other two as search parameters, e.g. /author?comment=123456 or /author?comments|id=123456 (inspired by FHIR).
- andreasklinger 10y agomy reasoning: you end up w/ a lot of filters very quickly and related post id will be just one of them also linking it to another resource usually involved expecting defaults (default ordering, default display, pagination etc) if you stay "flat" this tends to be less of an issue when usecases become more complex
- friendzis 10y agoProbably yes, the former not only "looks better", but is possibly easier to use in a view. Just spawn two delegates with `$post_path` and `pathcat($post_path, 'comments')` and be done with it. However, the argument being made here is that you may not want to expose post comments as sub-resource of a particular post, but rather as completely separate resource. And there is a point to that. Suppose you serve the post content as a static content, and comments from some application (octopress + disqus style). The latter format allows you to route anything matching `^/posts/(.*)` directly at web server level without touching application server at all. If you decided to use the former, then your infrastructure becomes dependent on your API structure. Not very nice :)
- emilsedgh 10y agoNever nest data (not `post: { author: { … } }` but reference only `post: {author_id: …}`) I'm creating a huge API on my dayjob and we have nested A LOT. So many levels of nesting, the responses have become too big. Yet, the clients refuse to call additional endpoints and always insist on this. And it _does_ make sense for them to make 1 call and retrieve all information they need. How does everyone handle this on REST? (I know GraphQL is a solution.I'm wondering how people use REST API's)
- yeukhon 10y agoI am not familiar with GraphQL and my APIs are serving only a handful of people. I do have one suggestion: write a really really good client library for your user. This eventually can become the basis of your test harness. In fact, every time I write my tests, I end up writing a client library...
- biot 10y agoIf you must, serve up the authors as an array that is a sibling of the posts array. So if you have 100 posts written by only 3 authors, you have: { posts: [ /* 100 records */ ], authors: [ /* 3 records */ ] } Each post references the author by ID only and all required data is sent in one API call.
- _asummers 10y agoEven better: make authors be a map of ID to author so you can get fast lookup by ID when you need it.
- webjunkie 10y ago- Never embed personalized information (eg not `{ post: { user_has_commented: true } }` Until you hear from an iOS developer that it is a huge pain trying to figure out if the user has left a comment on an item or not and including this little flag would save tons of time.
- andreasklinger 10y agoclient lib code is never the issue. usually scaling or breaking contracts is you can just add it as separate key (similar how you embed eg user objects) ``` posts: {…} interactions: { commented_on_posts: [1212,12,1212,121] } ``` btw most people over estimate this problem the _total_ amount of user interactions is usually very small in almost all cases you could download it once at boot for the user.
- webjunkie 10y agoGood ideas, thanks!