Monitoring version usage
Know your clients
You’ve set a sunset date. You’ve added deprecation headers. But before you actually pull the plug on v1, you need to answer some questions. Who is still using it? How many requests per day? Is usage declining? Are there holdouts who haven’t started migrating?
Without data, you’re sunsetting blind.
Tracking version usage
We already have version detection in onRequest from the Version Routers lesson. Let’s extend that to store usage counts in the database:
// src/shared/version-tracking.ts
import db from "./db.js";
db.exec(`
CREATE TABLE IF NOT EXISTS api_version_usage (
version TEXT NOT NULL,
date TEXT NOT NULL,
count INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (version, date)
);
`);
export function trackVersionUsage(version: string): void {
const date = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
db.prepare(
`
INSERT INTO api_version_usage (version, date, count)
VALUES (?, ?, 1)
ON CONFLICT (version, date) DO UPDATE SET count = count + 1
`,
).run(version, date);
} Let’s walk through what this does. The api_version_usage table has three columns: version (v1 or v2), date (today’s date), and count (how many requests). The primary key is the combination of version and date, so there’s one row per version per day.
The trackVersionUsage function runs an upsert. If there’s no row for today’s version yet, it creates one with a count of 1. If the row already exists, it increments the count. Simple and efficient.
Now we wire it into onRequest:
onRequest: ({ request }) => {
const url = new URL(request.url);
const version = url.pathname.startsWith("/v2") ? "v2" : "v1";
trackVersionUsage(version);
return { apiVersion: version };
}, Every request now gets counted. No changes to any route handlers.
Querying the data
Usage data is only useful if you can look at it. Let’s add an admin endpoint:
route.get("/admin/version-usage", {
resolve: () => {
const usage = db
.prepare(
`
SELECT version, date, count
FROM api_version_usage
ORDER BY date DESC, version
LIMIT 60
`,
)
.all();
const summary = db
.prepare(
`
SELECT version, SUM(count) AS total_requests
FROM api_version_usage
WHERE date >= date('now', '-30 days')
GROUP BY version
`,
)
.all();
return Response.json({ last30Days: summary, daily: usage });
},
}); Restart the server and make a few requests to generate some data:
curl http://localhost:3000/v1/books
curl http://localhost:3000/v1/books
curl http://localhost:3000/v2/books
curl http://localhost:3000/v2/books
curl http://localhost:3000/v2/books Now check the usage data:
curl http://localhost:3000/admin/version-usage This gives you two things. The last30Days summary tells you how many v1 vs v2 requests happened in the last 30 days. The daily breakdown shows the day-by-day trend.
If v1 usage is steadily declining toward zero, sunset is safe. If v1 still gets 40% of traffic, clients need more time or you need to reach out to them directly.
Identifying specific clients
If clients authenticate with API keys, you can track per-client version usage. This tells you exactly who is still on v1:
export function trackClientVersionUsage(version: string, clientId: string): void {
const date = new Date().toISOString().split("T")[0];
db.prepare(
`
INSERT INTO client_version_usage (client_id, version, date, count)
VALUES (?, ?, ?, 1)
ON CONFLICT (client_id, version, date) DO UPDATE SET count = count + 1
`,
).run(clientId, version, date);
} Now instead of “v1 got 500 requests yesterday,” you know “Client X made 500 v1 requests yesterday.” You can contact them directly: “We noticed you’re still on v1. v1 sunsets on June 1. Here’s the migration guide. Can we help?”
That kind of proactive outreach is how production APIs handle sunset transitions smoothly.
When to sunset
Sunset when:
v1 usage is near zero. Less than 1% of total traffic. The remaining requests are likely abandoned integrations that nobody maintains.
The sunset date has passed. You committed to a date. Honor it. Clients had time to migrate.
Maintenance cost exceeds value. Every bug fix and feature has to be applied to v1 and v2. If v1 has 10 users and v2 has 10,000, maintaining v1 is disproportionately expensive.
Do NOT sunset when:
A major client hasn’t migrated. If your biggest partner is still on v1, work with them directly before sunsetting. Breaking your biggest customer is never worth it.
Usage is increasing. If v1 usage is growing, something is wrong. New clients are using the deprecated version. Fix the onboarding docs, update the getting-started guide, make sure new API keys default to v2.
We’ve covered every piece of the versioning puzzle. In the final lesson, we’ll pull it all together into a complete, working example with a checklist you can take to your own projects.
Exercises
Exercise 1: Add version tracking to onRequest. Make 10 v1 requests and 20 v2 requests. Query the usage table.
Exercise 2: Build the admin endpoint. Verify it shows daily and summary data.
Exercise 3: Identify a scenario where v1 usage data would change your sunset decision.
Why track version usage before sunsetting?