DEV Community

Cover image for πŸ”„ Understanding ETag and If-None-Match in Web Development
Abhinav
Abhinav

Posted on

πŸ”„ Understanding ETag and If-None-Match in Web Development

πŸ”„ Understanding ETag and If-None-Match in Web Development

In the world of web development, optimizing resource delivery isn’t just about CDN and lazy loading. Headers like ETag and If-None-Match are low-key heroes πŸ¦Έβ€β™‚οΈ when it comes to reducing bandwidth and improving load speed πŸš€.

In this post, I’ll walk through:

  • 🧠 What ETags and If-None-Match actually are
  • 🀝 Why you should care
  • πŸ” How the browser and server interact with these headers
  • πŸ› οΈ Real-world usage in a NestJS backend

Let’s dive in πŸŠβ€β™‚οΈ


🧠 What is an ETag?

An ETag (short for Entity Tag) is like a fingerprint 🧬 for a resource. Every time your backend returns something (a JSON response, image, HTML, etc.), it can attach an ETag header β€” a unique string that represents the current version of that resource.

Example server response:

200 OK
ETag: "abc123"
Enter fullscreen mode Exit fullscreen mode

This value is cached by the client πŸ“¦. On future requests, the client can ask:

β€œHey server, is this still version abc123?”

If yes, the server replies:

304 Not Modified
Enter fullscreen mode Exit fullscreen mode

…with no response body β€” saving bandwidth πŸ’‘.


πŸ“¨ What is If-None-Match?

If-None-Match is the request header the client sends to say:

β€œOnly send the data if it’s different from the version I already have.”

Example:

GET /api/user/123
If-None-Match: "abc123"
Enter fullscreen mode Exit fullscreen mode

If the server's version is still the same, it responds with:

304 Not Modified
Enter fullscreen mode Exit fullscreen mode

Otherwise, it sends the updated content with a new ETag πŸ†•.


πŸ” How They Work Together

Here’s the flow:

  1. πŸ§‘β€πŸ’» Client requests a resource
  2. 🧾 Server responds with data + ETag: "xyz789"
  3. πŸ’½ Client saves the response + ETag
  4. πŸ”„ Next time, client sends If-None-Match: "xyz789"
  5. βœ… If match β†’ server replies with 304 Not Modified
  6. ❌ If not β†’ updated data is sent with a new ETag

It’s like conditional rendering πŸ”ƒ, but for APIs.


⚑ Why You Should Care (The Benefits)

  • πŸͺ„ Save Bandwidth – Don’t send the same thing twice
  • πŸš€ Faster UX – Cached responses mean quicker loads
  • πŸ” Safe Concurrency – Prevent stale updates with version checks

πŸ› οΈ Implementing ETags in NestJS (with Code)

Let’s say you have a user API and want to skip re-sending the same user data when it hasn’t changed.

1. Generate an ETag from your response

// etag.util.ts
import * as crypto from 'crypto';

export function generateEtag(data: any): string {
  const str = JSON.stringify(data);
  return crypto.createHash('sha1').update(str).digest('hex');
}
Enter fullscreen mode Exit fullscreen mode

2. Use ETag and If-None-Match in your controller

import { Controller, Get, Param, Req, Res, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';
import { generateEtag } from './etag.util';

@Controller('user')
export class UserController {
  @Get(':id')
  async getUser(@Param('id') id: string, @Req() req: Request, @Res() res: Response) {
    const user = { id, name: 'Alice' }; // Dummy user data

    const etag = generateEtag(user);
    const clientEtag = req.headers['if-none-match'];

    if (clientEtag === etag) {
      return res.status(HttpStatus.NOT_MODIFIED).setHeader('ETag', etag).end();
    }

    res.setHeader('ETag', etag);
    return res.status(HttpStatus.OK).json(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you’re only sending data when necessary. Simple and effective πŸ’―.


πŸ§ͺ Practical Example

Let’s say your site has a giant CSS file 🎨:

  1. πŸ‘€ First visit β†’ browser downloads it, gets ETag: "v1"
  2. πŸ‘‹ Second visit β†’ browser sends If-None-Match: "v1"
  3. πŸ” If unchanged β†’ server responds with 304, no download

Result? Less waiting. Better UX. Happy user 😊.


⚠️ Considerations

  • βš™οΈ ETag Generation: Needs to be deterministic. Use stable hashing.
  • 🧡 Weak ETags: W/"etag" means β€œclose enough” (used for minor changes).
  • 🧩 Middleware/CDNs: Some proxies or CDNs may strip or override ETags.
  • ⏱️ Overhead: For dynamic content, ETag hashing might add slight load.

🧷 Bonus: Updating Resources Safely with If-Match

Imagine two users editing the same record πŸ§‘β€πŸ€β€πŸ§‘. You can use If-Match to prevent overwriting someone else’s changes.

PUT /user/123
If-Match: "v1"
Enter fullscreen mode Exit fullscreen mode

NestJS:

@Put(':id')
async updateUser(@Param('id') id: string, @Req() req: Request, @Res() res: Response) {
  const currentUser = { id, name: 'Alice' };
  const currentEtag = generateEtag(currentUser);
  const clientEtag = req.headers['if-match'];

  if (clientEtag !== currentEtag) {
    return res.status(HttpStatus.PRECONDITION_FAILED).send('ETag mismatch');
  }

  // Perform the update
  return res.json({ message: 'Updated successfully' });
}
Enter fullscreen mode Exit fullscreen mode

It’s like optimistic locking, but way easier πŸ”.


πŸ“₯ Recap of Request & Response Headers

πŸ“€ Request Headers:

If-None-Match: "etag-value"  // For GET
If-Match: "etag-value"       // For PUT/UPDATE
Enter fullscreen mode Exit fullscreen mode

πŸ“₯ Response Headers:

ETag: "etag-value"
Enter fullscreen mode Exit fullscreen mode

βœ… TL;DR

Header Direction Who Sends It Used For
ETag Server ➑️ Client Server Identifying resource version
If-None-Match Client ➑️ Server Client Conditional GET (cache check)
If-Match Client ➑️ Server Client Safe updates
304 Not Modified Server ➑️ Client Server Says: β€œYou already have it”
412 Precondition Failed Server ➑️ Client Server Update blocked due to version mismatch

🧠 Final Thoughts

ETag and If-None-Match are underrated performance boosts πŸš€. They save bandwidth, speed up your app, and help avoid update collisions β€” all with just a few headers.

And with NestJS, it’s super easy to implement πŸ’ͺ.

Top comments (0)