Live data from Hacker News

How to Design Better APIs

r.bluethl.net

101–110 of 238 posts

Re: How to Design Better APIs

#101
post #67

a) Use standardized error codes, not standardized error messages. Clients are responsible for internationalization, which includes presenting error messages in the user's language. If you document a set of error codes as an enum, the client can present a user-friendly error message in the user's language based on the error code. If there are dynamic parts of the error message, i.e. "404: There is no user with ID 1234…

b -> What happens if the "list of IDs" is 10k or 100k or 1M+?

There's typically business-logic ways to require a range to be specified, and then you specify a maximum range. For example, if the resource in question is audit events, you require the user to specify a time range, up to a maximum of a day, or seven days, or whatever performance / budget constraints allow for.

Re: How to Design Better APIs

#102
post #67

a) Use standardized error codes, not standardized error messages. Clients are responsible for internationalization, which includes presenting error messages in the user's language. If you document a set of error codes as an enum, the client can present a user-friendly error message in the user's language based on the error code. If there are dynamic parts of the error message, i.e. "404: There is no user with ID 1234…

> Pagination is the devil. The state of the server can change while the user is paginating, leading to fragile clients. Don't paginate your API. The problem is not pagination, and the solution is not to avoid pagination. The problem is offset-based pagination, and the solution is to use cursor-based pagination. > If you think you have a need to paginate, have one API call return a list of IDs, and have a separate API…

Cursor-based pagination doesn't solve your state issue, it forces the server to create a copy of state for the cursor request. This is complex to implement correctly - for example, if the user does not actually paginate through all the entries, when do you dump the unused cursor? If the user issues the same request over and over, do you return a cached cursor or re-copy the state into a new cursor? If you re-copy the state, are you defended from a malicious actor who sends 1,000 new requests? Of course, all these concerns can be mitigated, but it's easier to just design without pagination in the first place if you can.

> The state of the server can change between fetching the list of IDs and operating on them.

Right, for example, a returned ID may have been deleted before the user can issue a query for that resource. But this is usually far more comprehensible to clients, particularly if the resource requires authorization such that only the client is permitted to delete that resource.

Re: How to Design Better APIs

#103

Earlier quoted context omitted.

> Pagination is the devil. The state of the server can change while the user is paginating, leading to fragile clients. Don't paginate your API. The problem is not pagination, and the solution is not to avoid pagination. The problem is offset-based pagination, and the solution is to use cursor-based pagination. > If you think you have a need to paginate, have one API call return a list of IDs, and have a separate API…

Cursor-based pagination doesn't solve your state issue, it forces the server to create a copy of state for the cursor request. This is complex to implement correctly - for example, if the user does not actually paginate through all the entries, when do you dump the unused cursor? If the user issues the same request over and over, do you return a cached cursor or re-copy the state into a new cursor? If you re-copy the…

Cursor-based pagination doesn’t require anything at all like you describe. Are you sure you aren’t mistaking it for something else?

Re: How to Design Better APIs

#104

My recommendation: Use PUT everywhere instead of POST. PUT has to be idempotent, so if the request fails, the client can simply post it again. This solves issue like worrying about creating a second copy of an item if a POST timed out. Using PUT to create elements means that the client has to supply the ID, but this is easily solved by using GUIDs as IDs. Most languages have a package for create a unique GUIDs.

I don't think we should strive to remove non-idempotent cases. If something is not idempotent does not mean it is bad. It just means that request should be handled differently.

In your example (and I ask this as I remained confused after also reading SO):

Let's say that you need the client to provide the ID in the request body.

In this case, how is using PUT when creating a new resource idempotent if the ID should be unique and you have a constraint on the DB level for example?

What happens when the second call will be made with the same ID?

If I execute the following sequence:

Call 1: PUT resource + request.body {id: 1} => a new record is created so the state of the system changes

Call 2: PUT resource + request.body {id: 1} => no record is created, maybe the existing one is updated

IMO this is not idempotent nor should it be. Creating a new resource is not an idempotent operation.

I also don't like that depending on the state of the DB two requests with the same attributes will have different impacts on the system.

In my mind as a consumer of an API it is simpler: POST it creates a new record so I know what to expect, PUT updates an existing one.

Re: How to Design Better APIs

#105

Earlier quoted context omitted.

b -> What happens if the "list of IDs" is 10k or 100k or 1M+?

There's typically business-logic ways to require a range to be specified, and then you specify a maximum range. For example, if the resource in question is audit events, you require the user to specify a time range, up to a maximum of a day, or seven days, or whatever performance / budget constraints allow for.

That's just like paging by day or whatever.

Re: How to Design Better APIs

#106
post #67

a) Use standardized error codes, not standardized error messages. Clients are responsible for internationalization, which includes presenting error messages in the user's language. If you document a set of error codes as an enum, the client can present a user-friendly error message in the user's language based on the error code. If there are dynamic parts of the error message, i.e. "404: There is no user with ID 1234…

> Pagination is the devil. The state of the server can change while the user is paginating, leading to fragile clients. Don't paginate your API. The problem is not pagination, and the solution is not to avoid pagination. The problem is offset-based pagination, and the solution is to use cursor-based pagination. > If you think you have a need to paginate, have one API call return a list of IDs, and have a separate API…

You should use stable page boundaries instead of cursors. If you are returning results by timestamps, the page boundary should be the timestamp and secondary ordering keys of the last row returned.

Cursors take too many server resources and require client affinity.

Re: How to Design Better APIs

#107

Earlier quoted context omitted.

> Pagination is the devil. The state of the server can change while the user is paginating, leading to fragile clients. Don't paginate your API. The problem is not pagination, and the solution is not to avoid pagination. The problem is offset-based pagination, and the solution is to use cursor-based pagination. > If you think you have a need to paginate, have one API call return a list of IDs, and have a separate API…

You should use stable page boundaries instead of cursors. If you are returning results by timestamps, the page boundary should be the timestamp and secondary ordering keys of the last row returned. Cursors take too many server resources and require client affinity.

You’re describing cursor-based pagination. It’s got nothing to do with the SQL concept of cursors.

Re: How to Design Better APIs

#108
post #21
post #5

Why would one prefer ISO 8601 dates over POSIX timestamps?

8601 has a large pattern space - RFC 3339 is a narrower subset of ISO. Somewhere I saw a diagram that convinced me. I link if I can find again. Edit: a relevant link https://news.ycombinator.com/item?id=28976526

https://ijmacd.github.io/rfc3339-iso8601/

A ven diagram very cool

Re: How to Design Better APIs

#109
post #70

Can someone share how they handle versioning in their API when it comes to data model changes? For example `POST /users` now takes a required field `avatar_url` but it was not part of `v1`. Since this field is validated in the DB, merely having `v1` `v2` distinction at the API layer is not sufficient. So I was thinking we will have to either 1) disable DB validations and rely on app validations or 2) run two separate…

I really like this way of versioning https://medium.com/@XenoSnowFox/youre-thinking-about-api-ver...

It uses Accept and Content-Type to version resources: application/vnd.company.article-v1+json

Re: How to Design Better APIs

#110
post #4

Some nice tips in here. However, tip 15, I strongly disagree with: > 15. Allow expanding resources I would suggest the opposite. A REST API should not return nested resources at all. Instead, and to stay with the example provided on the website, to obtain the "orders", the /users/:id/orders endpoint should be called. It might be tempting to return nested resources, because clients would only have to make a single cal…

Once your api query parameters reach a certain level of complexity, like such nested lookups, one should just consider giving direct proxied read access to the DB for clients that need it. Why reinvent your own query language. I Don’t know enough about graphql to compare it. Databases have access controls also.
Post reply on HN