π 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"
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
β¦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"
If the server's version is still the same, it responds with:
304 Not Modified
Otherwise, it sends the updated content with a new ETag π.
π How They Work Together
Hereβs the flow:
- π§βπ» Client requests a resource
- π§Ύ Server responds with data +
ETag: "xyz789"
- π½ Client saves the response + ETag
- π Next time, client sends
If-None-Match: "xyz789"
- β
If match β server replies with
304 Not Modified
- β 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');
}
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);
}
}
Now youβre only sending data when necessary. Simple and effective π―.
π§ͺ Practical Example
Letβs say your site has a giant CSS file π¨:
- π First visit β browser downloads it, gets
ETag: "v1"
- π Second visit β browser sends
If-None-Match: "v1"
- π 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"
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' });
}
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
π₯ Response Headers:
ETag: "etag-value"
β 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)