Nhost Backend Service
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
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
- Apple
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
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:
- User enters email, password
- Form validation (Zod schema)
- Call Nhost
signUp()method - Email verification sent
- 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:
- User enters credentials
- Form validation
- Call Nhost
signInEmailPassword()method - Session token created
- Redirect to dashboard
Component: SignInEmailPassword.tsx
Magic Link Authentication
Routes:
/sign-up/magic-link- Request magic link/sign-in/magic-link- Verify magic link
Process:
- User enters email
- Backend sends email with magic link
- User clicks link in email
- Automatic sign-in
- 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:
- User initiates WebAuthn flow
- Browser prompts for security key
- User authenticates (fingerprint, face, hardware key)
- Public key cryptography validates
- Session created
OAuth Providers
Component: OAuthLinks.tsx
Supported Providers:
- GitHub - Developer authentication
- Google - Consumer authentication
- Apple - iOS users
- LinkedIn - Professional network
OAuth Flow:
- User clicks provider button
- Redirect to provider’s OAuth page
- User authorizes Phenom app
- Provider redirects back with code
- Nhost exchanges code for token
- 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:
- User enters email address
- Backend sends reset email
- User clicks link in email
- User enters new password
- 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 accountsauth_provider_requests- OAuth stateauth_providers- Configured providersauth_refresh_tokens- Session tokensauth_user_roles- Role assignmentsauth_user_security_keys- WebAuthn keys
User Profile Management
Profile Page
Route: /profile
Features:
User Info Display
- Email address
- Display name
- Avatar
- User ID
- Account creation date
Change Email
- Enter new email
- Verification email sent
- Confirm email change
Change Password
- Current password required
- New password validation
- Confirmation step
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.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.