Error Handling
Understand the error classes thrown by better-env and how to handle them.
better-env uses three distinct error types to signal different failure modes. All error classes are exported from the main package.
import { EnvValidationError, ClientAccessError } from "@ayronforge/better-env"
EnvValidationError
Thrown when one or more environment variables fail schema validation.
| Name | Type | Default | Description |
|---|---|---|---|
| _tag | "EnvValidationError" | — | Discriminant tag for pattern matching. |
| errors | ReadonlyArray<string> | — | Array of human-readable validation error messages. |
| message | string | — | Formatted error message with all failures. |
Example
import { createEnv, requiredString, port, EnvValidationError } from "@ayronforge/better-env"
try {
const env = createEnv({
server: {
DATABASE_URL: requiredString,
PORT: port,
},
})
} catch (e) {
if (e instanceof EnvValidationError) {
console.error("Validation failed:")
for (const error of e.errors) {
console.error(` - ${error}`)
}
// Output:
// - DATABASE_URL: Expected a string with a length of at least 1, but got undefined
// - PORT: Expected Port (1-65535), but got "abc"
}
}
EnvValidationError collects all validation failures, not just the first one. This lets you fix all issues in a single pass rather than playing whack-a-mole.
onValidationError callback
You can hook into validation errors before the exception is thrown:
createEnv({
onValidationError: (errors) => {
// errors is string[] — same as EnvValidationError.errors
logger.error("Environment validation failed", { errors })
},
server: {
DATABASE_URL: requiredString,
},
})
The callback fires before the error is thrown. The EnvValidationError is still thrown after the callback completes.
ClientAccessError
Thrown when client-side code attempts to access a server-only environment variable.
| Name | Type | Default | Description |
|---|---|---|---|
| _tag | "ClientAccessError" | — | Discriminant tag for pattern matching. |
| variableName | string | — | The name of the server variable that was accessed. |
| message | string | — | Descriptive error message. |
Example
import { createEnv, requiredString, ClientAccessError } from "@ayronforge/better-env"
const env = createEnv({
server: {
DATABASE_URL: requiredString,
},
client: {
NEXT_PUBLIC_API_URL: requiredString,
},
isServer: false, // simulate client-side
})
try {
env.DATABASE_URL // throws on client
} catch (e) {
if (e instanceof ClientAccessError) {
console.error(e.variableName) // "DATABASE_URL"
// "Attempted to access server-side env var "DATABASE_URL" on client"
}
}
ResolverError
Thrown when a resolver fails to initialize or fetch secrets. This is an Effect tagged error, used in the Effect error channel.
| Name | Type | Default | Description |
|---|---|---|---|
| _tag | "ResolverError" | — | Discriminant tag for Effect error handling. |
| resolver | string | — | Name of the resolver that failed (e.g., "aws", "gcp"). |
| message | string | — | Human-readable error message. |
| cause | unknown | — | The underlying error, if any. |
ResolverError is a Data.TaggedError from Effect, meaning you can use it with Effect’s error handling:
import { Effect } from "effect"
import { createEnv, requiredString } from "@ayronforge/better-env"
import { fromAwsSecrets, ResolverError } from "@ayronforge/better-env/aws"
const envEffect = createEnv({
server: {
DATABASE_URL: requiredString,
},
resolvers: [
fromAwsSecrets({
secrets: { DATABASE_URL: "prod/db-url" },
}),
],
})
// Handle resolver errors specifically
const program = envEffect.pipe(
Effect.catchTag("ResolverError", (err) => {
console.error(`Resolver "${err.resolver}" failed: ${err.message}`)
return Effect.fail(err)
}),
)
When using resolvers, createEnv returns an Effect.Effect instead of a plain object. You must run it with Effect.runPromise or Effect.runSync (or use it within an Effect pipeline).