Skip to content
GitHub

@effectify/node-auth-app

The @effectify/node-auth-app package provides a complete, production-ready authentication server application built with Effect, better-auth, and modern Node.js patterns. It’s designed to be deployed as a standalone service or integrated into existing applications.

npm install @effectify/node-auth-app

Dependencies:

npm install express cors helmet dotenv better-auth better-sqlite3

Create a .env file:

# Server Configuration
PORT=3001
NODE_ENV=development
CORS_ORIGIN=http://localhost:3000

# Database
DATABASE_URL=file:./auth.db

# Authentication
BETTER_AUTH_SECRET=your-super-secret-key-here
BETTER_AUTH_URL=http://localhost:3001
JWT_SECRET=your-jwt-secret-here

# Email (optional)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password

# Social Auth (optional)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
import { createAuthApp } from "@effectify/node-auth-app"
import { Effect } from "effect"

// Create and start the auth server
const startAuthServer = Effect.gen(function*() {
  const app = yield* createAuthApp({
    port: 3001,
    corsOrigin: "http://localhost:3000",
    database: {
      provider: "sqlite",
      url: "file:./auth.db",
    },
  })

  console.log("Auth server running on port 3001")
  return app
})

Effect.runPromise(startAuthServer)
import { AuthAppConfig, createAuthApp } from "@effectify/node-auth-app"

const config: AuthAppConfig = {
  port: 3001,
  corsOrigin: ["http://localhost:3000", "https://myapp.com"],
  database: {
    provider: "postgresql",
    url: process.env.DATABASE_URL!,
  },
  auth: {
    session: {
      expiresIn: 60 * 60 * 24 * 7, // 7 days
      updateAge: 60 * 60 * 24, // 1 day
    },
    emailVerification: {
      enabled: true,
      expiresIn: 60 * 60 * 24, // 24 hours
    },
    passwordReset: {
      enabled: true,
      expiresIn: 60 * 60, // 1 hour
    },
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
  email: {
    provider: "smtp",
    config: {
      host: process.env.SMTP_HOST!,
      port: Number(process.env.SMTP_PORT!),
      auth: {
        user: process.env.SMTP_USER!,
        pass: process.env.SMTP_PASS!,
      },
    },
  },
}

const app = await Effect.runPromise(createAuthApp(config))

The auth app provides the following endpoints:

Register a new user with email and password.

// Request
{
  "email": "user@example.com",
  "password": "securepassword123",
  "name": "John Doe"
}

// Response
{
  "user": {
    "id": "user-id",
    "email": "user@example.com",
    "name": "John Doe",
    "emailVerified": false,
    "createdAt": "2024-01-01T00:00:00.000Z"
  },
  "session": {
    "id": "session-id",
    "token": "jwt-token",
    "expiresAt": "2024-01-08T00:00:00.000Z"
  }
}

Sign in with email and password.

// Request
{
  "email": "user@example.com",
  "password": "securepassword123"
}

// Response
{
  "user": { /* user object */ },
  "session": { /* session object */ }
}

Sign out the current user.

// Headers
Authorization: Bearer < session - token >
  // Response
  {
    "success": true,
  }

Get current session information.

// Headers
Authorization: Bearer < session - token >
  // Response
  {
    "user": {/* user object */},
    "session": {/* session object */},
  }

Get current user profile.

// Headers
Authorization: Bearer < session - token >
  // Response
  {
    "id": "user-id",
    "email": "user@example.com",
    "name": "John Doe",
    "emailVerified": true,
    "createdAt": "2024-01-01T00:00:00.000Z",
    "updatedAt": "2024-01-01T00:00:00.000Z",
  }

Update user profile.

// Headers
Authorization: Bearer <session-token>

// Request
{
  "name": "John Smith",
  "email": "john.smith@example.com"
}

// Response
{
  "user": { /* updated user object */ }
}

Change user password.

// Headers
Authorization: Bearer <session-token>

// Request
{
  "currentPassword": "oldpassword123",
  "newPassword": "newpassword123"
}

// Response
{
  "success": true
}

Send email verification.

// Headers
Authorization: Bearer < session - token >
  // Response
  {
    "success": true,
    "message": "Verification email sent",
  }

Verify email with token.

// Request
{
  "token": "verification-token"
}

// Response
{
  "success": true,
  "user": { /* updated user object */ }
}

Request password reset.

// Request
{
  "email": "user@example.com"
}

// Response
{
  "success": true,
  "message": "Password reset email sent"
}

Reset password with token.

// Request
{
  "token": "reset-token",
  "password": "newpassword123"
}

// Response
{
  "success": true,
  "message": "Password reset successfully"
}

Initiate GitHub OAuth flow.

GitHub OAuth callback.

Initiate Google OAuth flow.

Google OAuth callback.

// auth.ts
const API_BASE = "http://localhost:3001/api/auth"

export const authAPI = {
  signUp: async (email: string, password: string, name: string) => {
    const response = await fetch(`${API_BASE}/sign-up`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password, name }),
    })
    return response.json()
  },

  signIn: async (email: string, password: string) => {
    const response = await fetch(`${API_BASE}/sign-in`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    })
    return response.json()
  },

  getSession: async (token: string) => {
    const response = await fetch(`${API_BASE}/session`, {
      headers: { Authorization: `Bearer ${token}` },
    })
    return response.json()
  },

  signOut: async (token: string) => {
    const response = await fetch(`${API_BASE}/sign-out`, {
      method: "POST",
      headers: { Authorization: `Bearer ${token}` },
    })
    return response.json()
  },
}

// React hook
import { useEffect, useState } from "react"

export function useAuth() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const token = localStorage.getItem("auth-token")
    if (token) {
      authAPI.getSession(token)
        .then((data) => {
          if (data.user) {
            setUser(data.user)
          } else {
            localStorage.removeItem("auth-token")
          }
        })
        .catch(() => {
          localStorage.removeItem("auth-token")
        })
        .finally(() => setLoading(false))
    } else {
      setLoading(false)
    }
  }, [])

  const signIn = async (email: string, password: string) => {
    const data = await authAPI.signIn(email, password)
    if (data.session) {
      localStorage.setItem("auth-token", data.session.token)
      setUser(data.user)
    }
    return data
  }

  const signOut = async () => {
    const token = localStorage.getItem("auth-token")
    if (token) {
      await authAPI.signOut(token)
      localStorage.removeItem("auth-token")
      setUser(null)
    }
  }

  return { user, loading, signIn, signOut }
}
// auth.ts
import { createEffect, createSignal } from "solid-js"

export function createAuth() {
  const [user, setUser] = createSignal(null)
  const [loading, setLoading] = createSignal(true)

  createEffect(() => {
    const token = localStorage.getItem("auth-token")
    if (token) {
      authAPI.getSession(token)
        .then((data) => {
          if (data.user) {
            setUser(data.user)
          } else {
            localStorage.removeItem("auth-token")
          }
        })
        .catch(() => {
          localStorage.removeItem("auth-token")
        })
        .finally(() => setLoading(false))
    } else {
      setLoading(false)
    }
  })

  const signIn = async (email: string, password: string) => {
    const data = await authAPI.signIn(email, password)
    if (data.session) {
      localStorage.setItem("auth-token", data.session.token)
      setUser(data.user)
    }
    return data
  }

  const signOut = async () => {
    const token = localStorage.getItem("auth-token")
    if (token) {
      await authAPI.signOut(token)
      localStorage.removeItem("auth-token")
      setUser(null)
    }
  }

  return { user, loading, signIn, signOut }
}
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./
RUN npm ci --only=production

# Copy application code
COPY dist ./dist

# Create data directory for SQLite
RUN mkdir -p /app/data

# Expose port
EXPOSE 3001

# Set environment
ENV NODE_ENV=production
ENV DATABASE_URL=file:/app/data/auth.db

# Start application
CMD ["node", "dist/index.js"]
version: '3.8'

services:
  auth-server:
    build: .
    ports:
      - "3001:3001"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=file:/app/data/auth.db
      - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
      - BETTER_AUTH_URL=https://auth.yourdomain.com
      - CORS_ORIGIN=https://yourdomain.com
    volumes:
      - auth_data:/app/data
    restart: unless-stopped

volumes:
  auth_data:
{
  "build": {
    "builder": "NIXPACKS"
  },
  "deploy": {
    "startCommand": "npm start",
    "healthcheckPath": "/health"
  }
}
{
  "version": 2,
  "builds": [
    {
      "src": "dist/index.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "dist/index.js"
    }
  ]
}
interface AuthAppConfig {
  port?: number
  corsOrigin?: string | string[]
  database: {
    provider: "sqlite" | "postgresql" | "mysql"
    url: string
  }
  auth?: {
    session?: {
      expiresIn?: number
      updateAge?: number
    }
    emailVerification?: {
      enabled?: boolean
      expiresIn?: number
    }
    passwordReset?: {
      enabled?: boolean
      expiresIn?: number
    }
  }
  socialProviders?: {
    github?: {
      clientId: string
      clientSecret: string
    }
    google?: {
      clientId: string
      clientSecret: string
    }
  }
  email?: {
    provider: "smtp" | "sendgrid" | "mailgun"
    config: any
  }
  rateLimit?: {
    windowMs?: number
    max?: number
  }
  logging?: {
    level?: "error" | "warn" | "info" | "debug"
  }
}
  • Password Hashing: Uses bcrypt for secure password hashing
  • JWT Tokens: Secure session management with JWT
  • Rate Limiting: Built-in rate limiting for authentication endpoints
  • CORS Protection: Configurable CORS settings
  • Helmet Security: Security headers with Helmet.js
  • Input Validation: Comprehensive input validation
  • SQL Injection Protection: Parameterized queries
  • Session Management: Secure session handling with expiration

The auth app includes built-in logging and monitoring:

// Health check endpoint
GET /health

// Response
{
  "status": "ok",
  "timestamp": "2024-01-01T00:00:00.000Z",
  "uptime": 12345,
  "database": "connected"
}

// Metrics endpoint (if enabled)
GET /metrics

Check out the complete implementation:

  1. Database connection errors: Check your DATABASE_URL
  2. CORS errors: Verify CORS_ORIGIN matches your frontend URL
  3. Email not sending: Check SMTP configuration
  4. Social auth not working: Verify OAuth app configuration