Skip to content

Restful Caching

DodaTech 2 min read

title: "RESTful Caching — Cache-Control, ETag, and Conditional Requests" description: "RESTful caching uses Cache-Control headers, ETags, and conditional requests to reduce server load and improve response times for cacheable GET requests." date: 2026-06-28 lastmod: 2026-06-28 weight: 22 tags: [apis, restful] }

RESTful caching leverages HTTP caching semantics with Cache-Control directives, ETag validation, and conditional requests to serve cached responses efficiently.

What You'll Learn

  • HTTP caching for REST APIs
  • ETag generation
  • Conditional request handling

Why It Matters

Caching reduces API response times from 200ms to under 10ms and cuts server load by 90% for cacheable endpoints.

Code Examples

# Cache-Control headers
@app.route('/users')
def list_users():
    users = db.get_users()
    response = jsonify([u.to_dict() for u in users])
    response.headers['Cache-Control'] = 'public, max-age=60'
    response.headers['ETag'] = compute_etag(users)
    response.headers['Vary'] = 'Accept-Encoding'
    return response

# Conditional request handling
@app.route('/users/<int:id>')
def get_user(id):
    user = db.get_user(id)
    if not user:
        return jsonify({"error": "Not found"}), 404

    etag = f'"{user.version}"'

    if request.headers.get('If-None-Match') == etag:
        return '', 304

    response = jsonify(user.to_dict())
    response.headers['ETag'] = etag
    response.headers['Cache-Control'] = 'private, max-age=60'
    response.headers['Last-Modified'] = user.updated_at.strftime(
        '%a, %d %b %Y %H:%M:%S GMT'
    )
    return response

# Cache header helper
def cache_response(response, public=True, max_age=60, etag=None):
    cache_type = 'public' if public else 'private'
    response.headers['Cache-Control'] = f'{cache_type}, max-age={max_age}'
    if etag:
        response.headers['ETag'] = etag
    return response
// Express ETag middleware
const etag = require('etag');

app.get('/api/users', (req, res) => {
  const users = db.getUsers();
  const hash = etag(JSON.stringify(users));

  if (req.headers['if-none-match'] === hash) {
    return res.status(304).end();
  }

  res.set({
    'ETag': hash,
    'Cache-Control': 'public, max-age=60'
  });
  res.json(users);
});

Common Mistakes

1. No Cache Headers on GET

Every GET response should have explicit Cache-Control.

2. Caching User-Specific Data as Public

User profile data should use private cache.

3. No ETag on Cacheable Responses

ETags enable efficient conditional revalidation.

4. No Vary Header

Cached responses may be served to wrong clients.

5. Long TTLs Without Invalidation Strategy

Stale data persists when you forget to invalidate.

Practice Questions

  1. What header makes a response cacheable?
  2. How does ETag enable conditional requests?
  3. What is the difference between public and private cache?
  4. Why use Vary: Accept-Encoding?
  5. What HTTP status indicates the cached version is still valid?

Answers:

  1. Cache-Control header.
  2. Client sends If-None-Match with the ETag; server returns 304 if unchanged.
  3. Public allows proxies and CDNs to cache; private restricts to browser.
  4. So cached gzipped responses aren't served to clients without gzip support.
  5. 304 Not Modified.

Challenge: Add proper caching headers to all GET endpoints. Implement ETag-based conditional responses and test with curl.

FAQ

Should all GET endpoints be cacheable?

: No. Responses with user-specific data should use private or no-store.

How do I invalidate cached responses?

: Change the ETag (content changed) or wait for TTL expiry.

What is the ideal max-age for API responses?

: 30-300 seconds for dynamic data, hours for static reference data.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro