Environment Configuration
The Environment Configuration Abstraction Layer provides a unified, type-safe way to access environment variables throughout your application. This guide shows you how to use it effectively.
Quick Start
Section titled “Quick Start”Basic Usage
Section titled “Basic Usage”import { getEnvironmentConfig } from '#utils/env-config'
// Get configuration instance (cached singleton)const envConfig = getEnvironmentConfig()
// Access environment variablesconst isDevelopment = envConfig.isDevelopment()const clerkKey = envConfig.getClerkPublishableKey()const supabaseUrl = envConfig.getSupabaseUrl()Why Use the Abstraction?
Section titled “Why Use the Abstraction?”// Untyped, unvalidated, error-proneconst clerkKey = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEYconst supabaseUrl = import.meta.env.SUPABASE_URL
// Manual validation requiredif (!clerkKey || clerkKey === 'YOUR_CLERK_KEY') { throw new Error('Invalid configuration')}Problems:
- No type safety
- No placeholder detection
- Repeated lookups (slower)
- Hard to test
import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()
// Type-safe, validated, cachedconst clerkKey = envConfig.getClerkPublishableKey()const supabaseUrl = envConfig.getSupabaseUrl()
// Built-in validationif (!envConfig.isClerkConfigured()) { throw new Error('Clerk not configured')}Benefits:
- ✅ Type safety
- ✅ Automatic validation
- ✅ Performance (cached)
- ✅ Easy testing
Environment Detection
Section titled “Environment Detection”Check the current runtime environment:
const envConfig = getEnvironmentConfig()
// Boolean checksif (envConfig.isDevelopment()) { console.log('🚧 Running in development mode')}
if (envConfig.isProduction()) { console.log('🚀 Running in production')}
if (envConfig.isTest()) { console.log('🧪 Running tests')}
// Get environment stringconst env = envConfig.getEnvironment() // 'development' | 'production' | 'test'Use Cases
Section titled “Use Cases”Development-only features:
---import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()const showDebug = envConfig.isDevelopment()---
{showDebug && ( <div class="debug-panel"> <h3>Debug Info</h3> <pre>{JSON.stringify(Astro.props, null, 2)}</pre> </div>)}Clerk Authentication
Section titled “Clerk Authentication”Validation
Section titled “Validation”Always validate Clerk configuration before use:
import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()
if (!envConfig.isClerkConfigured()) { throw new Error( 'Clerk not configured. Set PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY' )}Accessing Keys
Section titled “Accessing Keys”// Get individual keys (returns null if not configured)const publishableKey = envConfig.getClerkPublishableKey()const secretKey = envConfig.getClerkSecretKey()const webhookSecret = envConfig.getClerkWebhookSecret()
// Safe pattern with validationif (envConfig.isClerkConfigured()) { const publishableKey = envConfig.getClerkPublishableKey()! const secretKey = envConfig.getClerkSecretKey()! // Non-null assertions safe after validation}Example: Middleware
Section titled “Example: Middleware”import { clerkMiddleware, createRouteMatcher } from '@clerk/astro/server'import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/forum(.*)'])
export const onRequest = clerkMiddleware((auth, context) => { if (!envConfig.isClerkConfigured()) { throw new Error('Clerk authentication not configured') }
if (isProtectedRoute(context.request) && !auth().userId) { return auth().redirectToSignIn() }})Database Configuration
Section titled “Database Configuration”Supabase
Section titled “Supabase”import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()
// Validationif (!envConfig.isSupabaseConfigured()) { throw new Error('Supabase not configured')}
// Access credentialsconst url = envConfig.getSupabaseUrl()!const anonKey = envConfig.getSupabaseAnonKey()!const serviceRoleKey = envConfig.getSupabaseServiceRoleKey() // Optional, may be nullimport { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()
// Validationif (!envConfig.isTursoConfigured()) { throw new Error('Turso not configured')}
// Access credentialsconst url = envConfig.getTursoDatabaseUrl()!const token = envConfig.getTursoAuthToken()!Provider Selection
Section titled “Provider Selection”// Check which database is configuredconst provider = envConfig.getDatabaseProvider()// Returns: 'turso' | 'supabase' | 'auto' | null
// Multi-provider supportif (envConfig.isSupabaseConfigured()) { // Use Supabase} else if (envConfig.isTursoConfigured()) { // Use Turso} else { throw new Error('No database configured')}Logging Configuration
Section titled “Logging Configuration”Axiom Logging
Section titled “Axiom Logging”import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()
// Check if Axiom is configuredif (envConfig.isAxiomConfigured()) { const token = envConfig.getAxiomToken()! const dataset = envConfig.getAxiomDataset()! const orgId = envConfig.getAxiomOrgId() // Optional
// Initialize Axiom client const axiom = new Axiom({ token, ...(orgId ? { orgId } : {}) })}Configuration Health Monitoring
Section titled “Configuration Health Monitoring”Get Complete Status
Section titled “Get Complete Status”import { getEnvironmentStatus } from '#utils/env-config'
const status = getEnvironmentStatus()
console.log(`Environment: ${status.environment}`)console.log(`Mode: ${status.mode}`)console.log(`Fully Configured: ${status.isFullyConfigured}`)
// Check specific servicesconsole.log('Services:')console.log(` Clerk: ${status.services.clerk.configured}`)console.log(` Database: ${status.services.database.configured}`)console.log(` Logging: ${status.services.logging.configured}`)
// List missing configurationif (!status.isFullyConfigured) { console.warn('Missing configuration:') status.missingConfiguration.forEach(item => { console.warn(` - ${item}`) })}Example Output
Section titled “Example Output”{ "environment": "development", "mode": "development", "isFullyConfigured": false, "services": { "clerk": { "configured": true, "hasWebhook": false }, "database": { "provider": "supabase", "configured": true, "availableProviders": ["supabase"] }, "logging": { "configured": false, "provider": null } }, "missingConfiguration": [ "Axiom Logging (AXIOM_TOKEN, AXIOM_DATASET)" ]}Create Health Check Endpoint
Section titled “Create Health Check Endpoint”import type { APIRoute } from 'astro'import { getEnvironmentStatus } from '#utils/env-config'
export const GET: APIRoute = async () => { const status = getEnvironmentStatus()
return new Response(JSON.stringify(status, null, 2), { status: 200, headers: { 'Content-Type': 'application/json', }, })}Usage Patterns
Section titled “Usage Patterns”Pattern 1: API Endpoints
Section titled “Pattern 1: API Endpoints”import type { APIRoute } from 'astro'import { Webhook } from 'svix'import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()
export const POST: APIRoute = async ({ request }) => { const webhookSecret = envConfig.getClerkWebhookSecret()
if (!webhookSecret) { return new Response( 'Webhook secret not configured', { status: 500 } ) }
const webhook = new Webhook(webhookSecret)
// Verify and process webhook...
return new Response('OK', { status: 200 })}Pattern 2: Database Clients
Section titled “Pattern 2: Database Clients”import { createClient } from '@supabase/supabase-js'import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()
if (!envConfig.isSupabaseConfigured()) { throw new Error( 'Supabase not configured. Required: SUPABASE_URL, SUPABASE_ANON_KEY' )}
const url = envConfig.getSupabaseUrl()!const key = envConfig.getSupabaseAnonKey()!
export const supabaseAuth = createClient(url, key, { auth: { autoRefreshToken: true, persistSession: true, },})Pattern 3: Astro Components
Section titled “Pattern 3: Astro Components”---import { getEnvironmentConfig } from '#utils/env-config'
const envConfig = getEnvironmentConfig()const isDev = envConfig.isDevelopment()const env = envConfig.getEnvironment()---
{isDev && ( <div class="dev-badge"> 🚧 Development Mode ({env}) </div>)}
<style> .dev-badge { position: fixed; bottom: 1rem; right: 1rem; background: #ff6b6b; color: white; padding: 0.5rem 1rem; border-radius: 0.5rem; font-size: 0.875rem; z-index: 9999; }</style>Pattern 4: React Hooks
Section titled “Pattern 4: React Hooks”import { createClient } from '@supabase/supabase-js'import { useEffect, useState } from 'react'import { getEnvironmentConfig } from '#utils/env-config'
export function useSupabase() { const [client, setClient] = useState<any>(null)
useEffect(() => { const envConfig = getEnvironmentConfig()
if (envConfig.isSupabaseConfigured()) { const url = envConfig.getSupabaseUrl()! const key = envConfig.getSupabaseAnonKey()!
const supabase = createClient(url, key) setClient(supabase) } else { console.warn('Supabase not configured') } }, [])
return client}Testing
Section titled “Testing”Mock Configuration in Tests
Section titled “Mock Configuration in Tests”import { describe, it, expect, vi } from 'vitest'import { getEnvironmentConfig } from '#utils/env-config'
// Mock the factory functionvi.mock('#utils/env-config', () => ({ getEnvironmentConfig: vi.fn(() => ({ isClerkConfigured: () => true, getClerkPublishableKey: () => 'pk_test_mock_key', getClerkSecretKey: () => 'sk_test_mock_secret', isDevelopment: () => true, isProduction: () => false, })),}))
describe('Middleware', () => { it('should authenticate with valid Clerk keys', () => { const config = getEnvironmentConfig()
expect(config.isClerkConfigured()).toBe(true) expect(config.getClerkPublishableKey()).toBe('pk_test_mock_key') })})Integration Tests
Section titled “Integration Tests”import { getEnvironmentConfig } from '#utils/env-config'
describe('Clerk Integration', () => { it('should work with real Clerk credentials', () => { const envConfig = getEnvironmentConfig()
// Skip test if not configured if (!envConfig.isClerkConfigured()) { console.warn('Skipping: Clerk not configured') return }
// Run integration test with real credentials const publishableKey = envConfig.getClerkPublishableKey() expect(publishableKey).toMatch(/^pk_(test|live)_/) })})Environment Variables Reference
Section titled “Environment Variables Reference”Required Variables
Section titled “Required Variables”# Clerk Authentication (Required)PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_key_hereCLERK_SECRET_KEY=sk_test_your_secret_here# Supabase (Required)SUPABASE_URL=https://your-project.supabase.coSUPABASE_ANON_KEY=your_anon_key_here
# Supabase (Optional - for server operations)SUPABASE_SERVICE_ROLE_KEY=your_service_role_key_here# Turso (Required)TURSO_DATABASE_URL=libsql://your-db.turso.ioTURSO_AUTH_TOKEN=your_auth_token_hereOptional Variables
Section titled “Optional Variables”# Clerk (Optional)CLERK_WEBHOOK_SECRET=whsec_your_webhook_secret
# Database Provider Selection (Optional)DATABASE_PROVIDER=supabase # 'turso' | 'supabase' | 'auto'
# Axiom Logging (Optional)AXIOM_TOKEN=your_axiom_tokenAXIOM_DATASET=your_dataset_nameAXIOM_ORG_ID=your_org_id # Optional
# Astro (Optional)ASTRO_ADAPTER=netlify # 'netlify' | 'node' | 'vercel'PUBLIC_SITE_URL=https://example.comObtaining Credentials
Section titled “Obtaining Credentials”Getting Supabase Keys
Section titled “Getting Supabase Keys”To obtain your Supabase credentials, follow these steps:
1. Open Supabase Dashboard
- Navigate to: https://supabase.com/dashboard
- Select your project from the list
2. Access API Settings
- Click Settings (gear icon) in the left sidebar
- Navigate to the API section
3. Copy Your Credentials
| Credential | Location | Variable Name |
|---|---|---|
| Project URL | Top of API page | SUPABASE_URL |
| anon (public) key | Project API Keys section | SUPABASE_ANON_KEY |
| service_role (secret) key | Project API Keys section | SUPABASE_SERVICE_ROLE_KEY |
For detailed setup instructions with screenshots, see the Complete Setup Guide.
Getting Clerk Keys
Section titled “Getting Clerk Keys”To obtain your Clerk credentials:
1. Open Clerk Dashboard
- Navigate to: https://dashboard.clerk.com
- Select your application
2. Access API Keys
- Click API Keys in the left sidebar
3. Copy Your Credentials
| Credential | Format | Variable Name |
|---|---|---|
| Publishable Key | Starts with pk_test_ or pk_live_ |
PUBLIC_CLERK_PUBLISHABLE_KEY |
| Secret Key | Starts with sk_test_ or sk_live_ |
CLERK_SECRET_KEY |
4. Optional: Webhook Secret
- Navigate to Webhooks → Select your endpoint
- Copy the Signing Secret →
CLERK_WEBHOOK_SECRET
Getting Turso Credentials
Section titled “Getting Turso Credentials”To obtain Turso credentials using the CLI:
1. Install Turso CLI
curl -sSfL https://get.tur.so/install.sh | bash2. Authenticate
turso auth login3. Create or Select Database
# Create new databaseturso db create my-database
# Or list existing databasesturso db list4. Get Connection Details
# Get database URL → TURSO_DATABASE_URLturso db show my-database --url
# Create auth token → TURSO_AUTH_TOKENturso db tokens create my-databaseBest Practices
Section titled “Best Practices”1. Use validation helpers before accessing values:
if (envConfig.isClerkConfigured()) { const key = envConfig.getClerkPublishableKey()! // Safe to use non-null assertion}2. Create instance at module level for performance:
// Top of fileimport { getEnvironmentConfig } from '#utils/env-config'const envConfig = getEnvironmentConfig()
// Use throughout moduleexport function myFunction() { const isDev = envConfig.isDevelopment()}3. Provide descriptive error messages:
if (!envConfig.isSupabaseConfigured()) { throw new Error( 'Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY in .env' )}❌ Don’t
Section titled “❌ Don’t”1. Don’t access import.meta.env directly:
❌ const key = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY✅ const key = envConfig.getClerkPublishableKey()2. Don’t skip validation checks:
❌ const key = envConfig.getClerkPublishableKey()! // Might be null!✅ if (envConfig.isClerkConfigured()) { ... }3. Don’t create multiple instances:
❌ function doSomething() { const config = getEnvironmentConfig() // New instance each call }
✅ const envConfig = getEnvironmentConfig() // Module level, reused function doSomething() { const isDev = envConfig.isDevelopment() }Troubleshooting
Section titled “Troubleshooting”Configuration Not Found
Section titled “Configuration Not Found”Error:
Error: Clerk not configuredSolution:
- Check your
.envfile exists and has the required variables - Verify values are not placeholders (
YOUR_CLERK_KEY) - Check environment status:
import { getEnvironmentStatus } from '#utils/env-config'console.log(getEnvironmentStatus())Placeholder Values Not Detected
Section titled “Placeholder Values Not Detected”Error:
Error: Invalid Clerk key formatCause: Using placeholder like YOUR_CLERK_PUBLISHABLE_KEY
Solution: The abstraction automatically detects these. Check validation:
const envConfig = getEnvironmentConfig()console.log(envConfig.isClerkConfigured()) // Should be false with placeholdersUpdate your .env file with real credentials:
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_real_key_hereCLERK_SECRET_KEY=sk_test_real_secret_hereTests Failing After Migration
Section titled “Tests Failing After Migration”Error:
TypeError: Cannot read property 'getClerkPublishableKey' of undefinedCause: Mock not configured properly
Solution:
vi.mock('#utils/env-config', () => ({ getEnvironmentConfig: vi.fn(() => ({ isClerkConfigured: () => true, getClerkPublishableKey: () => 'test_key', getClerkSecretKey: () => 'test_secret', // Add all methods your tests need })),}))Related Guides
Section titled “Related Guides”- Getting Started - Setup - Initial environment configuration
- Database Switching - Switch between Supabase and Turso
- Clerk Authentication - Clerk integration guide
- Logging System - Axiom logging configuration
★ Insight ─────────────────────────────────────
Performance Through Caching: The environment configuration abstraction uses a singleton pattern with lazy loading. The first call loads and caches all environment variables (~0.2ms), but subsequent calls simply return the cached instance (~0.05ms). This makes it faster than repeated direct access to import.meta.env.
Type Safety Benefits: TypeScript’s type system prevents you from accessing non-existent environment variables at compile time. This catches configuration typos during development rather than at runtime in production.
Testing Simplicity: Mocking getEnvironmentConfig() is dramatically simpler than mocking import.meta.env globally. You can create different configurations for different tests easily, improving test isolation and reliability.
─────────────────────────────────────────────────