| id |
schema-api-response-basic |
| title |
Basic API Response Decoding |
| category |
validating-api-responses |
| skillLevel |
beginner |
| tags |
schema |
api |
decode |
runtime-validation |
http |
|
| lessonOrder |
5 |
| rule |
| description |
Basic API Response Decoding using Schema. |
|
| summary |
You're fetching data from an external API. The response is `unknown` at runtime. TypeScript's type assertions (`as User`) are lies—they don't validate anything. A malformed API response will silently... |
You're fetching data from an external API. The response is unknown at runtime. TypeScript's type assertions (as User) are lies—they don't validate anything. A malformed API response will silently corrupt your application state.
You need runtime validation that:
- Fails fast on invalid data
- Gives you typed errors in the Effect channel, not thrown exceptions
- Integrates cleanly with Effect pipelines
- Provides clear error messages for debugging
import { Effect, Schema } from "effect"
// 1. Define the expected shape
const User = Schema.Struct({
id: Schema.Number,
name: Schema.String,
email: Schema.String,
})
// 2. Derive the TypeScript type
type User = typeof User.Type
// 3. Create a decoder Effect
const parseUser = Schema.decodeUnknown(User)
// 4. Use in an Effect pipeline
const fetchUser = (id: number) =>
Effect.gen(function* () {
// Fetch from API
const response = yield* Effect.tryPromise(() =>
fetch(`https://api.example.com/users/${id}`).then((r) => r.json())
)
// Validate response against schema
const user = yield* parseUser(response)
// user is typed as User, and validated at runtime
console.log(`Fetched user: ${user.name} <${user.email}>`)
return user
})
// Handle the result
const main = Effect.gen(function* () {
const user = yield* fetchUser(123)
yield* Effect.log(`User email: ${user.email}`)
})
await Effect.runPromise(main)
| Concept |
Explanation |
| Schema.Struct |
Defines expected object shape with runtime + compile-time types—single source of truth |
| Schema.decodeUnknown |
Returns Effect<A, ParseError> — validation errors flow through Effect's error channel, never thrown |
| typeof User.Type |
Extracts the TypeScript type from the schema—keeps type and validation in sync |
| Effect.gen |
Sequences async operations cleanly, errors propagate automatically through the pipeline |
| ParseError |
Contains detailed error info: what field failed, what was expected, what was received |
- Fetching data from external REST APIs
- Parsing webhook payloads
- Processing data crossing trust boundaries
- Validating responses before passing to domain logic
- Any
unknown data that needs type safety