Most tutorials and articles I've read about REST only touch on the subject, and the few that elaborate basically recommend adding a "last modified" column to my database table that the API can check. However, is that really the best way to go about it? I don't like the idea of adding columns to my database purely for the sake of the client - that seems to go against REST's goal of a client-server relationship where neither entity has to know the needs of the other.
Is there a better way?
in general the best solution is in the single point of truth so the last modified column is a good solution.
you can ofc add a cache layer that is used for the reads as long as you can be sure that the writes to the database invalidate the specific cached-row / set.
you could use refresh cycles or a socket that pushes all changes automatically to the client by comparing md5 sums of the "current state" or something equivalent.
if you want to have a more controlled system than hashing strings you could think of versioning and the client sends something like "i'm on version X and i haven't modified the state" and the server response "no changes" or "new version ready" and you can think of a merge.
I guess the client will be stateful and the API should be stateless so this would be one option :)
there is http://stateless.co/hal_specification.html this as well for a more complex communication structure without keeping the state onto the server.
but those are just thoughts, maybe someone with a more opinionated view would be of better help here.
Yes. Caching.
While it is true that your application should be stateless, no one will kill you for adding a cache layer (which does not represent state, but rather the data). Such a cache layer highly depends on what kind of data you are handling and how "fresh" it has to be. It also is vital that you always update the data in your data store (for example database)
What I did for one of my applications was adding in-memory caching (just a variable in Node.JS). Basically the server will fetch all data and store it in order to fetch it faster for serving it to clients. At the same time, I added something like an ETag to the data. The client requests the data and transmit the ETag if available. The server will compare the ETags and either just send a "not changed" response or send over whatever it has in its cache. The cache is never invalidated, but changed on certain API requests (together with the database). That kind of thing works well for my application, but depending on what you need you might have to invalidate your data based on a timeout (for example every hour). Do the invalidation in the background. Always serve fast!
btw, the described cache boosted performance from a 2 minutes wait-time to a some 50ms wait-time. I admit, the data store is very slow, but this caching technique was a speed-up of an unexpected multitude.