Skip to content

Commit 0743744

Browse files
committed
docs: document type-safe router labels and per-label userData typing
Add a "Type-safe router labels and `userData`" section to the TypeScript projects guide explaining how to declare a route map and pass it as the second type argument to a `createXRouter` factory for per-label `userData` typing. Mirrored into the latest (3.17) docs snapshot for the patch release.
1 parent 5d6a817 commit 0743744

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

docs/guides/typescript_project.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ title: TypeScript Projects
44
description: Stricter, safer, and better development experience
55
---
66

7+
import ApiLink from '@site/src/components/ApiLink';
8+
79
Crawlee is built with TypeScript, which means it provides the type definition directly in the package. This allows writing code with auto-completion for TypeScript and JavaScript code alike. Besides that, projects written in TypeScript can take advantage of compile-time type-checking and avoid many coding mistakes, while providing documentation for functions, parameters and return values. It will also help with refactoring a lot, and ensuring the least amount of bugs will sneak through.
810

911
## Setting up a TypeScript project
@@ -152,3 +154,36 @@ Let's wrap it up to. In addition to the scripts we described above, we also need
152154
}
153155
}
154156
```
157+
158+
## Type-safe router labels and `userData`
159+
160+
When you structure a crawler with a <ApiLink to="core/class/Router">`Router`</ApiLink>, each route handler reads `request.userData`. By default `userData` is loosely typed, so a typo in a label or a wrong `userData` property is only caught at runtime.
161+
162+
You can instead declare a **route map** — an `interface` (or `type`) that maps each label to the shape of `userData` expected for it — and pass it as the second type argument to a `createXRouter` factory (or <ApiLink to="core/class/Router#create">`Router.create`</ApiLink>). Handlers then get `request.userData` typed per label, and unknown labels become a compile-time error:
163+
164+
```ts
165+
import { createCheerioRouter, type CheerioCrawlingContext } from 'crawlee';
166+
167+
interface Routes {
168+
PRODUCT: { sku: string; price: number };
169+
CATEGORY: { categoryId: string };
170+
}
171+
172+
const router = createCheerioRouter<CheerioCrawlingContext, Routes>();
173+
174+
router.addHandler('PRODUCT', async ({ request }) => {
175+
request.userData.sku; // string
176+
request.userData.price; // number
177+
});
178+
179+
router.addHandler('CATEGORY', async ({ request }) => {
180+
request.userData.categoryId; // string
181+
});
182+
183+
// ❌ compile error: 'TYPO' is not a label declared in `Routes`
184+
router.addHandler('TYPO', async () => {});
185+
```
186+
187+
The default handler registered via `addDefaultHandler` receives the union of all declared `userData` shapes.
188+
189+
This is a compile-time-only feature with **no runtime cost**, and it is fully backwards compatible — omitting the route map keeps the original loose typing, and passing a plain `userData` shape (e.g. `createCheerioRouter<CheerioCrawlingContext, { token: string }>()`) still types every handler with that shape, exactly as before.

website/versioned_docs/version-3.17/guides/typescript_project.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ title: TypeScript Projects
44
description: Stricter, safer, and better development experience
55
---
66

7+
import ApiLink from '@site/src/components/ApiLink';
8+
79
Crawlee is built with TypeScript, which means it provides the type definition directly in the package. This allows writing code with auto-completion for TypeScript and JavaScript code alike. Besides that, projects written in TypeScript can take advantage of compile-time type-checking and avoid many coding mistakes, while providing documentation for functions, parameters and return values. It will also help with refactoring a lot, and ensuring the least amount of bugs will sneak through.
810

911
## Setting up a TypeScript project
@@ -152,3 +154,36 @@ Let's wrap it up to. In addition to the scripts we described above, we also need
152154
}
153155
}
154156
```
157+
158+
## Type-safe router labels and `userData`
159+
160+
When you structure a crawler with a <ApiLink to="core/class/Router">`Router`</ApiLink>, each route handler reads `request.userData`. By default `userData` is loosely typed, so a typo in a label or a wrong `userData` property is only caught at runtime.
161+
162+
You can instead declare a **route map** — an `interface` (or `type`) that maps each label to the shape of `userData` expected for it — and pass it as the second type argument to a `createXRouter` factory (or <ApiLink to="core/class/Router#create">`Router.create`</ApiLink>). Handlers then get `request.userData` typed per label, and unknown labels become a compile-time error:
163+
164+
```ts
165+
import { createCheerioRouter, type CheerioCrawlingContext } from 'crawlee';
166+
167+
interface Routes {
168+
PRODUCT: { sku: string; price: number };
169+
CATEGORY: { categoryId: string };
170+
}
171+
172+
const router = createCheerioRouter<CheerioCrawlingContext, Routes>();
173+
174+
router.addHandler('PRODUCT', async ({ request }) => {
175+
request.userData.sku; // string
176+
request.userData.price; // number
177+
});
178+
179+
router.addHandler('CATEGORY', async ({ request }) => {
180+
request.userData.categoryId; // string
181+
});
182+
183+
// ❌ compile error: 'TYPO' is not a label declared in `Routes`
184+
router.addHandler('TYPO', async () => {});
185+
```
186+
187+
The default handler registered via `addDefaultHandler` receives the union of all declared `userData` shapes.
188+
189+
This is a compile-time-only feature with **no runtime cost**, and it is fully backwards compatible — omitting the route map keeps the original loose typing, and passing a plain `userData` shape (e.g. `createCheerioRouter<CheerioCrawlingContext, { token: string }>()`) still types every handler with that shape, exactly as before.

0 commit comments

Comments
 (0)