| id |
schema-web-standards-url |
| title |
URL Validation |
| category |
web-standards-validation |
| skillLevel |
beginner |
| tags |
schema |
validation |
web-standards |
url |
branded-types |
http |
|
| lessonOrder |
30 |
| rule |
| description |
URL Validation using Schema. |
|
| summary |
You accept URLs from users—as links, webhooks, or API endpoints. A string type doesn't validate that it's actually a valid URL. Users submit malformed URLs like "htp://example" or "not a url". You... |
You accept URLs from users—as links, webhooks, or API endpoints. A string type doesn't validate that it's actually a valid URL. Users submit malformed URLs like "htp://example" or "not a url". You need to validate URLs at runtime, ensure they use allowed protocols, and create a distinct type so URLs can't be confused with plain strings.
import { Schema, Effect } from "effect"
// 1. Define branded URL type
const HttpUrl = Schema.String.pipe(
Schema.trimmed(),
Schema.filter((s) => {
try {
const url = new URL(s)
// Only allow http/https
return (
url.protocol === "http:" || url.protocol === "https:"
)
} catch {
return false
}
}).pipe(
Schema.annotations({
description: "Valid HTTP/HTTPS URL",
})
),
Schema.brand("HttpUrl")
)
type HttpUrl = typeof HttpUrl.Type
// 2. Alternative: Allow multiple protocols
const WebUrl = Schema.String.pipe(
Schema.trimmed(),
Schema.filter((s) => {
try {
new URL(s)
return true
} catch {
return false
}
}).pipe(
Schema.annotations({
description: "Valid URL (any protocol)",
})
),
Schema.brand("WebUrl")
)
type WebUrl = typeof WebUrl.Type
// 3. Use in webhook configuration
const WebhookConfig = Schema.Struct({
name: Schema.String,
url: HttpUrl,
events: Schema.Array(
Schema.Literal("user.created", "user.deleted")
),
retryLimit: Schema.Number.pipe(
Schema.between(0, 10)
).pipe(Schema.withDefault(3)),
})
type WebhookConfig = typeof WebhookConfig.Type
// 4. Validate and extract URL parts
const configureWebhook = (input: unknown) =>
Effect.gen(function* () {
const config = yield* Schema.decodeUnknown(
WebhookConfig
)(input).pipe(
Effect.mapError((error) => ({
_tag: "ConfigError" as const,
message: `Invalid webhook config: ${error.message}`,
}))
)
// Extract hostname from validated URL
const urlObj = new URL(config.url)
console.log(
`Webhook for ${urlObj.hostname} registered`
)
return config
})
// 5. Usage
const webhookInput = {
name: "User Events",
url: "https://webhook.example.com/users",
events: ["user.created"],
}
Effect.runPromise(configureWebhook(webhookInput))
.then((config) => {
console.log(`✅ Webhook: ${config.name}`)
})
.catch((error) =>
console.error(`Error: ${error.message}`)
)
| Concept |
Explanation |
new URL(s) |
Native browser/Node.js API—validates RFC 3986 |
Schema.filter |
Custom validation logic beyond regex |
| Protocol check |
Only allow safe protocols (http/https) |
Schema.brand |
Creates nominal type—HttpUrl ≠ string |
| URL extraction |
Can safely call new URL() again since already validated |
| Type safety |
Typed callbacks receive validated URLs |
- Webhook registration
- API endpoint configuration
- Link collection and storage
- Redirect targets
- External resource URLs
- API client endpoints