Installation
Installation
Section titled “Installation”This guide covers how to install and configure Effectify backend packages in your Node.js project.
Package Manager
Section titled “Package Manager”We recommend using your preferred package manager. All examples use npm, but you can substitute with yarn, pnpm, or bun.
Core Packages
Section titled “Core Packages”@effectify/node-better-auth
Section titled “@effectify/node-better-auth”Effect integration with better-auth for Node.js:
npm install @effectify/node-better-authPeer Dependencies:
npm install better-auth effect @effect/platform @effect/platform-node@effectify/node-auth-app
Section titled “@effectify/node-auth-app”Complete authentication server application:
npm install @effectify/node-auth-appPeer Dependencies:
npm install express cors helmet dotenv better-sqlite3Framework-Specific Setup
Section titled “Framework-Specific Setup”Express.js
Section titled “Express.js”- Create a new Express project:
mkdir my-backend && cd my-backend
npm init -y
npm install express cors helmet dotenv
npm install -D @types/express @types/cors @types/node typescript ts-node nodemon- Initialize TypeScript:
npx tsc --init- Configure
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}- Add scripts to
package.json:
{
"scripts": {
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}Fastify
Section titled “Fastify”- Create a new Fastify project:
mkdir my-fastify-backend && cd my-fastify-backend
npm init -y
npm install fastify @fastify/cors @fastify/helmet
npm install -D @types/node typescript ts-node nodemon- Basic Fastify setup with Effect:
// src/index.ts
import Fastify from "fastify"
import { Effect } from "effect"
const fastify = Fastify({ logger: true })
// Register plugins
fastify.register(require("@fastify/cors"))
fastify.register(require("@fastify/helmet"))
// Effect-based route
fastify.get("/health", async (request, reply) => {
const healthCheck = Effect.succeed({
status: "ok",
timestamp: new Date().toISOString(),
})
const result = await Effect.runPromise(healthCheck)
return result
})
const start = async () => {
try {
await fastify.listen({ port: 3000 })
console.log("Server running on port 3000")
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
}
start()NestJS
Section titled “NestJS”- Create a new NestJS project:
npm i -g @nestjs/cli
nest new my-nest-backend
cd my-nest-backend
npm install @effectify/node-better-auth effect- Create an Effect service:
// src/effect/effect.service.ts
import { Injectable } from "@nestjs/common"
import { Effect } from "effect"
@Injectable()
export class EffectService {
runEffect<A, E>(effect: Effect.Effect<A, E, never>): Promise<A> {
return Effect.runPromise(effect)
}
}- Use in controllers:
// src/users/users.controller.ts
import { Controller, Get, Param } from "@nestjs/common"
import { EffectService } from "../effect/effect.service"
import { UserService } from "./user.service"
@Controller("users")
export class UsersController {
constructor(private readonly effectService: EffectService) {}
@Get(":id")
async getUser(@Param("id") id: string) {
return this.effectService.runEffect(UserService.getById(id))
}
}Database Setup
Section titled “Database Setup”PostgreSQL with pg
Section titled “PostgreSQL with pg”npm install pg @types/pgCreate database service:
// src/services/database.ts
import { Pool } from "pg"
import { Context, Effect, Layer } from "effect"
export class DatabaseService extends Context.Tag("DatabaseService")<
DatabaseService,
{
readonly query: <T>(
sql: string,
params?: any[],
) => Effect.Effect<T[], DatabaseError>
}
>() {}
const makeDatabaseService = Effect.gen(function*() {
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
})
return {
query: <T>(sql: string, params: any[] = []) =>
Effect.tryPromise({
try: () => pool.query(sql, params).then((result) => result.rows as T[]),
catch: (error) => new DatabaseError("Query failed", { cause: error }),
}),
}
})
export const DatabaseServiceLive = Layer.effect(
DatabaseService,
makeDatabaseService,
)SQLite with better-sqlite3
Section titled “SQLite with better-sqlite3”npm install better-sqlite3 @types/better-sqlite3Create SQLite service:
// src/services/sqlite.ts
import Database from "better-sqlite3"
import { Context, Effect, Layer } from "effect"
export class SqliteService extends Context.Tag("SqliteService")<
SqliteService,
{
readonly query: <T>(
sql: string,
params?: any[],
) => Effect.Effect<T[], DatabaseError>
readonly run: (
sql: string,
params?: any[],
) => Effect.Effect<void, DatabaseError>
}
>() {}
const makeSqliteService = Effect.gen(function*() {
const db = new Database(process.env.DATABASE_PATH || "app.db")
return {
query: <T>(sql: string, params: any[] = []) =>
Effect.try({
try: () => db.prepare(sql).all(...params) as T[],
catch: (error) => new DatabaseError("Query failed", { cause: error }),
}),
run: (sql: string, params: any[] = []) =>
Effect.try({
try: () => {
db.prepare(sql).run(...params)
},
catch: (error) => new DatabaseError("Query failed", { cause: error }),
}),
}
})
export const SqliteServiceLive = Layer.effect(SqliteService, makeSqliteService)Prisma Integration
Section titled “Prisma Integration”npm install prisma @prisma/client
npx prisma initCreate Prisma service:
// src/services/prisma.ts
import { PrismaClient } from "@prisma/client"
import { Context, Effect, Layer } from "effect"
export class PrismaService extends Context.Tag("PrismaService")<
PrismaService,
PrismaClient
>() {}
const makePrismaService = Effect.gen(function*() {
const prisma = new PrismaClient()
// Connect to database
yield* Effect.tryPromise({
try: () => prisma.$connect(),
catch: (error) => new DatabaseError("Failed to connect to database", { cause: error }),
})
return prisma
})
export const PrismaServiceLive = Layer.effect(PrismaService, makePrismaService)Authentication Setup
Section titled “Authentication Setup”Basic Better Auth Setup
Section titled “Basic Better Auth Setup”npm install @effectify/node-better-auth better-authCreate auth configuration:
// src/config/auth.ts
import { betterAuth } from "better-auth"
import { Effect } from "effect"
export const authConfig = betterAuth({
database: {
provider: "sqlite",
url: process.env.DATABASE_URL || "app.db",
},
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day
},
secret: process.env.BETTER_AUTH_SECRET!,
baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
})
// Effect wrapper for auth operations
export const AuthService = {
signUp: (email: string, password: string, name: string) =>
Effect.tryPromise({
try: () =>
authConfig.api.signUpEmail({
body: { email, password, name },
}),
catch: (error) => new AuthenticationError("Sign up failed", { cause: error }),
}),
signIn: (email: string, password: string) =>
Effect.tryPromise({
try: () =>
authConfig.api.signInEmail({
body: { email, password },
}),
catch: (error) => new AuthenticationError("Sign in failed", { cause: error }),
}),
getSession: (sessionToken: string) =>
Effect.tryPromise({
try: () =>
authConfig.api.getSession({
headers: { authorization: `Bearer ${sessionToken}` },
}),
catch: (error) => new AuthenticationError("Invalid session", { cause: error }),
}),
}Environment Configuration
Section titled “Environment Configuration”Create .env file:
# Server
PORT=3000
NODE_ENV=development
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# or for SQLite
# DATABASE_URL=file:./app.db
# Authentication
BETTER_AUTH_SECRET=your-super-secret-key-here
BETTER_AUTH_URL=http://localhost:3000
JWT_SECRET=your-jwt-secret-here
# CORS
CORS_ORIGIN=http://localhost:3000
# Logging
LOG_LEVEL=infoLoad environment variables:
// src/config/env.ts
import { config } from "dotenv"
import { Context, Effect } from "effect"
config() // Load .env file
export interface EnvConfig {
readonly port: number
readonly nodeEnv: string
readonly databaseUrl: string
readonly betterAuthSecret: string
readonly betterAuthUrl: string
readonly jwtSecret: string
readonly corsOrigin: string
readonly logLevel: string
}
export class EnvService extends Context.Tag("EnvService")<
EnvService,
EnvConfig
>() {}
export const loadEnvConfig = Effect.gen(function*() {
const requiredEnvVars = ["DATABASE_URL", "BETTER_AUTH_SECRET", "JWT_SECRET"]
// Validate required environment variables
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
yield* Effect.fail(
new Error(`Missing required environment variable: ${envVar}`),
)
}
}
return {
port: Number(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || "development",
databaseUrl: process.env.DATABASE_URL!,
betterAuthSecret: process.env.BETTER_AUTH_SECRET!,
betterAuthUrl: process.env.BETTER_AUTH_URL || "http://localhost:3000",
jwtSecret: process.env.JWT_SECRET!,
corsOrigin: process.env.CORS_ORIGIN || "http://localhost:3000",
logLevel: process.env.LOG_LEVEL || "info",
} satisfies EnvConfig
})
export const EnvServiceLive = Layer.effect(EnvService, loadEnvConfig)Logging Setup
Section titled “Logging Setup”npm install winstonCreate logging service:
// src/services/logger.ts
import winston from "winston"
import { Context, Effect, Layer } from "effect"
export class LoggerService extends Context.Tag("LoggerService")<
LoggerService,
{
readonly info: (message: string, meta?: any) => Effect.Effect<void>
readonly error: (message: string, error?: any) => Effect.Effect<void>
readonly warn: (message: string, meta?: any) => Effect.Effect<void>
readonly debug: (message: string, meta?: any) => Effect.Effect<void>
}
>() {}
const makeLoggerService = Effect.gen(function*() {
const env = yield* EnvService
const logger = winston.createLogger({
level: env.logLevel,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json(),
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
),
}),
],
})
return {
info: (message: string, meta?: any) => Effect.sync(() => logger.info(message, meta)),
error: (message: string, error?: any) => Effect.sync(() => logger.error(message, { error })),
warn: (message: string, meta?: any) => Effect.sync(() => logger.warn(message, meta)),
debug: (message: string, meta?: any) => Effect.sync(() => logger.debug(message, meta)),
}
})
export const LoggerServiceLive = Layer.effect(LoggerService, makeLoggerService)Verification
Section titled “Verification”Create a test endpoint to verify everything is working:
// src/routes/test.ts
import { Router } from "express"
import { Effect } from "effect"
import { DatabaseService } from "../services/database"
import { LoggerService } from "../services/logger"
export const testRoutes = Router()
testRoutes.get("/health", (req, res) => {
const healthCheck = Effect.gen(function*() {
const logger = yield* LoggerService
yield* logger.info("Health check requested")
return {
status: "ok",
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV,
}
})
Effect.runPromise(
healthCheck.pipe(
Effect.provide(LoggerServiceLive),
Effect.map((result) => res.json(result)),
Effect.catchAll((error) => Effect.sync(() => res.status(500).json({ error: "Health check failed" }))),
),
)
})
testRoutes.get("/db-test", (req, res) => {
const dbTest = Effect.gen(function*() {
const db = yield* DatabaseService
const logger = yield* LoggerService
yield* logger.info("Database test requested")
// Simple query to test database connection
const result = yield* db.query("SELECT 1 as test")
return { database: "connected", result }
})
Effect.runPromise(
dbTest.pipe(
Effect.provide(Layer.merge(DatabaseServiceLive, LoggerServiceLive)),
Effect.map((result) => res.json(result)),
Effect.catchAll((error) => Effect.sync(() => res.status(500).json({ error: "Database test failed" }))),
),
)
})Next Steps
Section titled “Next Steps”- Getting Started Guide - Learn the basics
- Node Better Auth - Explore authentication
- Node Auth App - Complete auth service
- Backend Reference - API documentation
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”- TypeScript compilation errors: Check your
tsconfig.jsonconfiguration - Environment variable errors: Ensure all required variables are set
- Database connection issues: Verify your database URL and credentials
- Effect context errors: Make sure all services are properly provided with layers