Nhost Backend Service

Technical documentation for the Nhost backend service providing authentication, GraphQL API, and storage capabilities for the Phenom platform.

Overview

The Nhost Backend Service is a comprehensive authentication and API layer built on top of Nhost - an open-source Firebase alternative. Located at server/phenom/ in the phenom-backend repository, this React application provides the core backend services for user authentication, data management, and file storage.

What is Nhost?

Nhost is an open-source Backend-as-a-Service (BaaS) that provides:

  • Authentication - User management with multiple auth methods
  • Database - PostgreSQL with Hasura GraphQL engine
  • Storage - File upload and management
  • Serverless Functions - Custom business logic

Official Website: https://nhost.io Documentation: https://docs.nhost.io

Technology Stack

Core Technologies

  • Nhost SDK (@nhost/react, @nhost/react-apollo)
  • React 18 - UI framework
  • Apollo Client - GraphQL client
  • React Router v6 - Client-side routing
  • TypeScript - Type-safe development

Backend Services

  • Hasura GraphQL Engine - Auto-generated GraphQL API
  • PostgreSQL - Relational database
  • Hasura Auth - Authentication service (Git submodule)
  • Nhost Storage - S3-compatible file storage

UI Components

  • Radix UI - Accessible component primitives
  • Tailwind CSS - Utility-first CSS
  • Shadcn UI - Component library
  • Lucide React - Icon set

Service Architecture

graph TB subgraph "Client Layer" ReactApp["React Application
Vite Build
Apollo Client"] end subgraph "Nhost Backend Services" Auth["Authentication Service
Port 9000
Email, OAuth, MFA"] GraphQL["GraphQL API
Port 8080
Hasura Engine"] Storage["Storage Service
Port 9500
File Upload/Download"] Functions["Functions Service
Port 9999
Serverless Logic"] end subgraph "Data Layer" DB["PostgreSQL Database
User tables
Auth schemas
Application data"] FileStore["S3-Compatible Storage
Files & Metadata"] end subgraph "Supporting Services" JWT["JWT Token Management
Session handling
Token refresh"] RBAC["Role-Based Access Control
Hasura Permissions
Row-level security"] Subscriptions["Real-time Subscriptions
WebSocket connections
Live updates"] end ReactApp -->|Auth requests| Auth ReactApp -->|GraphQL queries
& mutations| GraphQL ReactApp -->|File operations| Storage ReactApp -->|Function calls| Functions Auth -->|User mgmt| DB Auth -->|Issue/refresh| JWT GraphQL -->|Schema queries| DB GraphQL -->|Apply| RBAC GraphQL -->|Subscribe| Subscriptions Storage -->|File metadata| DB Storage -->|File storage| FileStore Functions -->|Data access| GraphQL style Auth fill:#e1f5ff style GraphQL fill:#f3e5f5 style Storage fill:#e8f5e9 style Functions fill:#fff3e0

Reference URLs:

Backend Services Configuration

// Current configuration (localhost for development)
const nhost = new NhostClient({
  authUrl: 'http://localhost:9000/v1/auth',
  graphqlUrl: 'http://localhost:8080/v1/graphql',
  storageUrl: 'http://localhost:9500/v1/storage',
  functionsUrl: 'http://localhost:9999/v1/functions'
})

// Production configuration (when hosted on Nhost cloud)
const nhost = new NhostClient({
  subdomain: 'your-subdomain',
  region: 'us-west-2'
})

Service Endpoints

1. Authentication Service (Port 9000)

URL: http://localhost:9000/v1/auth

Capabilities:

  • User registration (sign-up)
  • User login (sign-in)
  • Password reset
  • Email verification
  • Session management
  • Token refresh
  • OAuth provider integration

Auth Methods:

  • Email/Password - Traditional authentication
  • Magic Link - Passwordless email links
  • Security Key - WebAuthn/FIDO2
  • OAuth Providers:
    • GitHub
    • Google
    • Apple
    • LinkedIn

2. GraphQL API (Port 8080)

URL: http://localhost:8080/v1/graphql

Features:

  • Auto-generated API: Based on database schema
  • Real-time Subscriptions: Live data updates
  • Role-based Access: Hasura permission system
  • Custom Queries: Defined in database schema
  • Mutations: Create, update, delete operations

Example Queries:

# Fetch current user
query GetCurrentUser {
  users_by_pk(id: $userId) {
    id
    email
    displayName
    metadata
  }
}

# Subscribe to real-time updates
subscription OnUserUpdated($userId: uuid!) {
  users_by_pk(id: $userId) {
    id
    email
    displayName
  }
}

3. Storage Service (Port 9500)

URL: http://localhost:9500/v1/storage

Capabilities:

  • File Upload: Multi-part uploads
  • Image Processing: Automatic resizing, format conversion
  • Access Control: Public/private files
  • CDN Integration: Fast global delivery
  • Metadata Management: Custom file metadata

Supported Operations:

// Upload file
await nhost.storage.upload({ file })

// Get file URL
const url = nhost.storage.getPublicUrl({ fileId })

// Delete file
await nhost.storage.delete({ fileId })

4. Serverless Functions (Port 9999)

URL: http://localhost:9999/v1/functions

Purpose:

  • Custom business logic
  • Third-party API integrations
  • Background jobs
  • Webhooks

Authentication Flows

graph TB Entry["User Access Point"] Entry -->|Email/Password| EmailFlow["Sign-Up/Sign-In
Email & Password
Form validation
Email verification"] Entry -->|Passwordless| MagicLink["Magic Link Flow
Email only
No password
Auto sign-in on click"] Entry -->|Biometric| WebAuthn["Security Key/WebAuthn
YubiKey, Touch ID
Face ID, Windows Hello
Public key crypto"] Entry -->|Social| OAuth["OAuth Providers
GitHub, Google
Apple, LinkedIn
Redirect + exchange"] Entry -->|Forgotten| ForgotFlow["Password Reset
Email verification
Set new password
Session created"] EmailFlow -->|Success| Session["Session Token Created
JWT issued
Stored in browser"] MagicLink -->|Success| Session WebAuthn -->|Success| Session OAuth -->|Success| Session ForgotFlow -->|Success| Session Session -->|Protected| Dashboard["Access Dashboard
Authenticated routes
GraphQL queries
User data"] subgraph "Auth Methods" EmailFlow MagicLink WebAuthn OAuth ForgotFlow end style EmailFlow fill:#e1f5ff style MagicLink fill:#f3e5f5 style WebAuthn fill:#fff3e0 style OAuth fill:#ffd700 style ForgotFlow fill:#ffccbc style Session fill:#c8e6c9 style Dashboard fill:#a5d6a7

Reference URLs:

Email/Password Sign-Up

Route: /sign-up/email-password

Process:

  1. User enters email, password
  2. Form validation (Zod schema)
  3. Call Nhost signUp() method
  4. Email verification sent
  5. Redirect to dashboard on success

Component: SignUpEmailPassword.tsx

const { signUpEmailPassword } = useSignUpEmailPassword()

const handleSignUp = async (email: string, password: string) => {
  const result = await signUpEmailPassword(email, password)
  if (result.isSuccess) {
    // Redirect to dashboard
    navigate('/')
  }
}

Email/Password Sign-In

Route: /sign-in/email-password

Process:

  1. User enters credentials
  2. Form validation
  3. Call Nhost signInEmailPassword() method
  4. Session token created
  5. Redirect to dashboard

Component: SignInEmailPassword.tsx

Routes:

  • /sign-up/magic-link - Request magic link
  • /sign-in/magic-link - Verify magic link

Process:

  1. User enters email
  2. Backend sends email with magic link
  3. User clicks link in email
  4. Automatic sign-in
  5. Redirect to dashboard

Benefits:

  • Passwordless
  • More secure (no password to steal)
  • Better UX for quick access

Security Key (WebAuthn)

Routes:

  • /sign-up/security-key - Register security key
  • /sign-in/security-key - Authenticate with key

Supported Devices:

  • YubiKey
  • Touch ID / Face ID
  • Windows Hello
  • Android biometrics

Process:

  1. User initiates WebAuthn flow
  2. Browser prompts for security key
  3. User authenticates (fingerprint, face, hardware key)
  4. Public key cryptography validates
  5. Session created

OAuth Providers

Component: OAuthLinks.tsx

Supported Providers:

  • GitHub - Developer authentication
  • Google - Consumer authentication
  • Apple - iOS users
  • LinkedIn - Professional network

OAuth Flow:

  1. User clicks provider button
  2. Redirect to provider’s OAuth page
  3. User authorizes Phenom app
  4. Provider redirects back with code
  5. Nhost exchanges code for token
  6. User session created

Implementation:

const { signInWithProvider } = useSignInWithProvider()

const handleGitHubSignIn = async () => {
  await signInWithProvider({
    provider: 'github',
    options: {
      redirectTo: window.location.origin
    }
  })
}

Password Reset

Route: /sign-in/forgot-password

Process:

  1. User enters email address
  2. Backend sends reset email
  3. User clicks link in email
  4. User enters new password
  5. Password updated in database

Component: ForgotPassword.tsx

Application Structure

Route Configuration

/                            # Dashboard home (protected)
├── /profile                # User profile management
└── /storage                # File storage interface

/sign-in                     # Authentication hub
├── /sign-in/               # Sign-in options
├── /sign-in/email-password # Email/password login
├── /sign-in/magic-link     # Passwordless login
├── /sign-in/security-key   # WebAuthn login
└── /sign-in/forgot-password # Password reset

/sign-up                     # Registration hub
├── /sign-up/               # Sign-up options
├── /sign-up/email-password # Email/password registration
├── /sign-up/magic-link     # Passwordless registration
└── /sign-up/security-key   # WebAuthn registration

Component Architecture

src/
├── components/
│   ├── auth/
│   │   ├── auth-gate.tsx          # Protected route wrapper
│   │   ├── oauth-links.tsx        # OAuth provider buttons
│   │   ├── sign-in-footer.tsx     # Sign-in page footer
│   │   └── sign-up-footer.tsx     # Sign-up page footer
│   ├── profile/
│   │   ├── user-info.tsx          # Display user data
│   │   ├── change-email.tsx       # Email update form
│   │   ├── change-password.tsx    # Password change form
│   │   └── jwt-claims.tsx         # Display JWT token info
│   ├── storage/
│   │   ├── upload-single-file.tsx # Single file upload
│   │   └── upload-multiple-files.tsx # Batch upload
│   ├── routes/
│   │   ├── app/
│   │   │   ├── home.tsx           # Dashboard home
│   │   │   ├── profile.tsx        # Profile page
│   │   │   ├── storage.tsx        # Storage page
│   │   │   └── layout.tsx         # App layout wrapper
│   │   └── auth/
│   │       ├── sign-in/           # Sign-in flows
│   │       └── sign-up/           # Sign-up flows
│   └── ui/                        # Shadcn UI components
└── App.tsx                        # Root component with routing

Authentication Gate

Component: AuthGate.tsx

Purpose: Protect routes from unauthenticated access

// Wraps protected routes
<AuthGate>
  <Layout>
    <ProtectedContent />
  </Layout>
</AuthGate>

Behavior:

  • If authenticated → Render children
  • If not authenticated → Redirect to /sign-in
  • While checking → Show loading spinner

Database Schema

User Table

Located in: db/schema/auth/

Fields:

users (
  id uuid PRIMARY KEY,
  email varchar UNIQUE NOT NULL,
  passwordHash varchar,
  emailVerified boolean DEFAULT false,
  displayName varchar,
  avatarUrl varchar,
  defaultRole varchar DEFAULT 'user',
  roles jsonb,
  metadata jsonb,
  createdAt timestamp DEFAULT now(),
  updatedAt timestamp DEFAULT now(),
  lastSeen timestamp
)

Auth Schema

Hasura Auth Submodule: db/hasura-auth/

Tables:

  • users - User accounts
  • auth_provider_requests - OAuth state
  • auth_providers - Configured providers
  • auth_refresh_tokens - Session tokens
  • auth_user_roles - Role assignments
  • auth_user_security_keys - WebAuthn keys

User Profile Management

Profile Page

Route: /profile

Features:

  1. User Info Display

    • Email address
    • Display name
    • Avatar
    • User ID
    • Account creation date
  2. Change Email

    • Enter new email
    • Verification email sent
    • Confirm email change
  3. Change Password

    • Current password required
    • New password validation
    • Confirmation step
  4. JWT Token Info

    • Display current JWT claims
    • Token expiration time
    • Role information
    • Custom claims

Profile Components

UserInfo.tsx:

const { user } = useUserData()

return (
  <div>
    <p>Email: {user?.email}</p>
    <p>Name: {user?.displayName}</p>
    <Avatar src={user?.avatarUrl} />
  </div>
)

ChangeEmail.tsx:

const { changeEmail } = useChangeEmail()

const handleEmailChange = async (newEmail: string) => {
  await changeEmail(newEmail)
  // Verification email sent
}

ChangePassword.tsx:

const { changePassword } = useChangePassword()

const handlePasswordChange = async (newPassword: string) => {
  await changePassword(newPassword)
  // Password updated
}

File Storage

Storage Page

Route: /storage

Features:

  • Single file upload
  • Multiple file upload (batch)
  • File list display
  • File deletion
  • Download links

Upload Components

Single File Upload:

const { upload } = useFileUpload()

const handleUpload = async (file: File) => {
  const result = await upload({ file })
  if (result.fileMetadata) {
    const url = nhost.storage.getPublicUrl({
      fileId: result.fileMetadata.id
    })
  }
}

Multiple File Upload:

const { upload } = useMultipleFilesUpload()

const handleBatchUpload = async (files: FileList) => {
  for (const file of files) {
    await upload({ file })
  }
}

GraphQL Integration

Apollo Client Setup

import { NhostApolloProvider } from '@nhost/react-apollo'

<NhostApolloProvider nhost={nhost}>
  <App />
</NhostApolloProvider>

Custom Queries

Example: Fetch user profile data

import { useQuery } from '@apollo/client'
import { gql } from '@apollo/client'

const GET_USER_PROFILE = gql`
  query GetUserProfile($userId: uuid!) {
    users_by_pk(id: $userId) {
      id
      email
      displayName
      avatarUrl
      metadata
    }
  }
`

const { data, loading } = useQuery(GET_USER_PROFILE, {
  variables: { userId: user.id }
})

Real-time Subscriptions

Example: Live user updates

import { useSubscription } from '@apollo/client'

const USER_SUBSCRIPTION = gql`
  subscription OnUserChanged($userId: uuid!) {
    users_by_pk(id: $userId) {
      email
      displayName
      lastSeen
    }
  }
`

const { data } = useSubscription(USER_SUBSCRIPTION, {
  variables: { userId: user.id }
})

Development Setup

Prerequisites

Node.js 18+
npm or yarn
Docker (for local Nhost services)
PostgreSQL 14+

Installation

cd server/phenom
npm install

Environment Configuration

The application is currently configured for local development. To switch to hosted Nhost:

Edit src/index.tsx:

// Change from:
const nhost = new NhostClient({
  authUrl: 'http://localhost:9000/v1/auth',
  // ...
})

// To:
const nhost = new NhostClient({
  subdomain: 'your-nhost-subdomain',
  region: 'us-west-2' // or your region
})

Start Development Server

# Start Nhost local services (Docker required)
nhost up

# In another terminal, start React app
npm start
# Opens at http://localhost:3000

Build for Production

npm run build
# Output: build/

Deployment

Hosting Options

Option 1: Nhost Cloud + Netlify/Vercel

  • Host backend on Nhost cloud
  • Deploy React app to Netlify/Vercel
  • Update subdomain/region in code

Option 2: Self-hosted

  • Deploy Nhost services to AWS/GCP/Azure
  • Use Docker Compose for backend
  • Deploy React app to any static host

Option 3: Full Nhost Hosting

  • Use Nhost’s hosting for both backend and frontend
  • Simplest deployment option

Current Deployment

The Nest platform uses:

  • Backend: Self-hosted Nhost services
  • Frontend: Cloudflare Pages / Netlify
  • Access: Cloudflare Zero Trust

Security Best Practices

Authentication

  • Never store passwords: Use Nhost’s secure hashing
  • Token rotation: Refresh tokens automatically
  • Session timeout: Configure reasonable expiry
  • MFA available: WebAuthn for sensitive accounts

Authorization

  • Role-based: Define roles in Hasura
  • Row-level security: Hasura permissions per table
  • JWT claims: Include necessary user data
  • API keys: Never expose in client code

Data Protection

  • HTTPS only: TLS/SSL for all requests
  • CORS configured: Restrict origins
  • Rate limiting: Prevent abuse
  • Input validation: Zod schemas on forms

Troubleshooting

Common Issues

“Failed to connect to Nhost”

  • Ensure Docker services are running (nhost up)
  • Check ports 8080, 9000, 9500, 9999 are available
  • Verify firewall settings

“Authentication failed”

  • Clear browser cookies/storage
  • Check email verification status
  • Verify OAuth provider settings
  • Inspect browser console for errors

“GraphQL errors”

  • Check Hasura console: http://localhost:8080/console
  • Verify database schema matches queries
  • Review Hasura permissions
  • Check network tab in DevTools

“File upload fails”

  • Verify file size limits
  • Check storage service is running
  • Ensure proper permissions
  • Review browser console errors

Future Enhancements

Planned Features:

  • Enhanced OAuth scopes
  • Multi-tenant support
  • Advanced role hierarchy
  • Audit logging
  • 2FA/MFA enforcement
  • WebSocket fallback
  • Offline support

Resources

Official Documentation:

Repository:

Support: For technical issues, contact the internal development team or file an issue in the repository.