Skip to content

Configuration ​

Effect SQL clients can be configured using direct options or Effect's Config system for environment-based configuration.

Direct Configuration ​

Pass configuration options directly when creating a Layer:

typescript
import { PgClient } from "@effect/sql-pg"

const DatabaseLive = PgClient.layer({
  host: "localhost",
  port: 5432,
  database: "myapp",
  username: "postgres",
  password: Redacted.make("secret")
})

Environment-Based Configuration ​

Use Effect's Config system for production deployments:

typescript
import { Config, Redacted } from "effect"
import { PgClient } from "@effect/sql-pg"

const DatabaseConfig = Config.all({
  host: Config.string("DATABASE_HOST").pipe(Config.withDefault("localhost")),
  port: Config.integer("DATABASE_PORT").pipe(Config.withDefault(5432)),
  database: Config.string("DATABASE_NAME"),
  username: Config.string("DATABASE_USER"),
  password: Config.redacted("DATABASE_PASSWORD")
})

const DatabaseLive = PgClient.layerConfig(DatabaseConfig)

Now your app reads from environment variables:

bash
DATABASE_HOST=db.example.com \
DATABASE_PORT=5432 \
DATABASE_NAME=myapp \
DATABASE_USER=admin \
DATABASE_PASSWORD=secret \
node dist/index.js

Common Configuration Options ​

These options are available across all database adapters:

Connection Settings ​

OptionTypeDescription
hoststringDatabase server hostname
portnumberDatabase server port
databasestringDatabase name
usernamestringAuthentication username
passwordRedactedAuthentication password
urlRedactedConnection URL (alternative to individual settings)

Connection Pool ​

OptionTypeDescription
minConnectionsnumberMinimum pool connections
maxConnectionsnumberMaximum pool connections
connectionTTLDurationInputMax connection lifetime
idleTimeoutDurationInputIdle connection timeout
connectTimeoutDurationInputConnection attempt timeout

Transformations ​

OptionTypeDescription
transformQueryNames(s: string) => stringTransform identifiers in queries
transformResultNames(s: string) => stringTransform column names in results

Observability ​

OptionTypeDescription
spanAttributesRecord<string, unknown>Custom span attributes

Database-Specific Configuration ​

PostgreSQL ​

typescript
import { PgClient } from "@effect/sql-pg"

const DatabaseLive = PgClient.layer({
  // Connection
  host: "localhost",
  port: 5432,
  database: "myapp",
  username: "postgres",
  password: Redacted.make("secret"),
  
  // Or use a connection URL
  // url: Redacted.make("postgresql://user:pass@localhost:5432/myapp"),
  
  // SSL
  ssl: true, // or { rejectUnauthorized: false }
  
  // Pool settings
  maxConnections: 20,
  minConnections: 5,
  connectionTTL: Duration.minutes(30),
  idleTimeout: Duration.minutes(10),
  connectTimeout: Duration.seconds(10),
  
  // Name transformations
  transformQueryNames: String.camelToSnake,
  transformResultNames: String.snakeToCamel,
  transformJson: true, // Also transform JSON fields
  
  // Application name (visible in pg_stat_activity)
  applicationName: "myapp-api",
  
  // Custom type parsing
  types: {
    // Custom type configuration for node-postgres
  }
})

SQLite (Node.js) ​

typescript
import { SqliteClient } from "@effect/sql-sqlite-node"

const DatabaseLive = SqliteClient.layer({
  // Database file path (":memory:" for in-memory)
  filename: "./data.db",
  
  // Open as read-only
  readonly: false,
  
  // Disable WAL mode (enabled by default)
  disableWAL: false,
  
  // Prepared statement cache
  prepareCacheSize: 200,
  prepareCacheTTL: Duration.minutes(10),
  
  // Name transformations
  transformQueryNames: String.camelToSnake,
  transformResultNames: String.snakeToCamel
})

MySQL ​

typescript
import { MysqlClient } from "@effect/sql-mysql2"

const DatabaseLive = MysqlClient.layer({
  host: "localhost",
  port: 3306,
  database: "myapp",
  username: "root",
  password: Redacted.make("secret"),
  
  // Pool settings
  maxConnections: 10,
  minConnections: 2,
  
  // Name transformations
  transformQueryNames: String.camelToSnake,
  transformResultNames: String.snakeToCamel
})

SQL Server ​

typescript
import { MssqlClient } from "@effect/sql-mssql"

const DatabaseLive = MssqlClient.layer({
  server: "localhost",
  port: 1433,
  database: "myapp",
  username: "sa",
  password: Redacted.make("secret"),
  
  // Trust server certificate
  trustServerCertificate: true,
  
  // Pool settings
  maxConnections: 10,
  minConnections: 2
})

Name Transformations ​

Configure automatic name transformations between TypeScript (camelCase) and database (snake_case):

typescript
import { String } from "effect"

const DatabaseLive = PgClient.layer({
  // ... connection options
  
  // TypeScript → Database (for queries)
  transformQueryNames: String.camelToSnake,
  // Database → TypeScript (for results)
  transformResultNames: String.snakeToCamel
})

Now your code can use camelCase while your database uses snake_case:

typescript
// TypeScript code
const user = yield* sql<{ firstName: string }>`
  SELECT ${sql("firstName")} FROM users WHERE ${sql("userId")} = ${1}
`
// Generated SQL: SELECT "first_name" FROM users WHERE "user_id" = $1
// Result: { firstName: "Alice" } (not { first_name: "Alice" })

Custom Transformations ​

Create custom transformation functions:

typescript
const customTransform = (str: string) => {
  // Your transformation logic
  return str.toUpperCase()
}

const DatabaseLive = PgClient.layer({
  transformQueryNames: customTransform
})

JSON Field Transformation ​

PostgreSQL adapter can also transform JSON fields:

typescript
const DatabaseLive = PgClient.layer({
  transformResultNames: String.snakeToCamel,
  transformJson: true // Also transform keys in JSON columns
})

Connection URLs ​

Most adapters support connection URLs as an alternative to individual settings:

typescript
// PostgreSQL
const DatabaseLive = PgClient.layer({
  url: Redacted.make("postgresql://user:pass@localhost:5432/myapp?ssl=true")
})

// MySQL
const DatabaseLive = MysqlClient.layer({
  url: Redacted.make("mysql://user:pass@localhost:3306/myapp")
})

Using Config:

typescript
const DatabaseConfig = Config.all({
  url: Config.redacted("DATABASE_URL")
})

const DatabaseLive = PgClient.layerConfig(DatabaseConfig)

Multiple Databases ​

Use different tags for multiple database connections:

typescript
import { Context, Layer } from "effect"
import { PgClient } from "@effect/sql-pg"

// Define custom tags
class MainDatabase extends Context.Tag("MainDatabase")<
  MainDatabase,
  PgClient.PgClient
>() {}

class AnalyticsDatabase extends Context.Tag("AnalyticsDatabase")<
  AnalyticsDatabase,
  PgClient.PgClient
>() {}

// Create layers for each database
const MainDbLive = Layer.effect(
  MainDatabase,
  PgClient.make({ database: "main" })
)

const AnalyticsDbLive = Layer.effect(
  AnalyticsDatabase,
  PgClient.make({ database: "analytics" })
)

// Use them in your program
const program = Effect.gen(function* () {
  const mainDb = yield* MainDatabase
  const analyticsDb = yield* AnalyticsDatabase
  
  const users = yield* mainDb`SELECT * FROM users`
  const metrics = yield* analyticsDb`SELECT * FROM page_views`
})

Testing Configuration ​

For tests, use in-memory databases or test-specific configurations:

typescript
// SQLite in-memory for testing
const TestDatabase = SqliteClient.layer({
  filename: ":memory:"
})

// Or a test-specific PostgreSQL database
const TestDatabase = PgClient.layer({
  database: "myapp_test"
})

Production Recommendations ​

Connection Pool Sizing ​

typescript
// For a typical web application
const DatabaseLive = PgClient.layer({
  // (CPU cores * 2) + 1 is a common starting point
  minConnections: 5,
  maxConnections: 20,
  
  // Recycle connections periodically
  connectionTTL: Duration.minutes(30),
  
  // Don't hold idle connections too long
  idleTimeout: Duration.minutes(10)
})

SSL in Production ​

typescript
const DatabaseLive = PgClient.layer({
  ssl: process.env.NODE_ENV === "production"
    ? true  // Verify certificates in production
    : false // No SSL in development
})

Connection Timeouts ​

typescript
const DatabaseLive = PgClient.layer({
  // Don't wait too long for connections
  connectTimeout: Duration.seconds(10),
  
  // Query timeout (via statement_timeout in PostgreSQL)
  // Set in the database or per-query
})

Next Steps ​

Released under the MIT License.