Skip to main content
The setup API handles the initial configuration of a fresh Nanahoshi instance, creating the first admin user and organization.

Complete setup

Initialize a new Nanahoshi instance by creating the first admin user and workspace.
const result = await orpc.setup.complete.mutate({
  workspaceName: "My Library",
  workspaceSlug: "my-library",
  username: "admin",
  email: "[email protected]",
  password: "securepassword123"
});

Parameters

workspaceName
string
required
Display name for the initial organization/workspace
workspaceSlug
string
required
URL-friendly slug for the organization (alphanumeric and hyphens only)
username
string
required
Username for the admin account
email
string
required
Email address for the admin account (must be valid email format)
password
string
required
Password for the admin account (minimum 8 characters)

Response

success
boolean
Whether the setup completed successfully

Setup flow

The setup process performs the following actions:
  1. Validates setup state - Ensures the app hasn’t already been configured
  2. Creates admin user - Registers the user via better-auth with the provided credentials
  3. Creates organization - Creates the initial workspace/organization
  4. Assigns owner role - Makes the user the organization owner
  5. Grants admin privileges - Sets the user’s global role to “admin”
  6. Marks as configured - Persists the setup completion state in the database

Setup guard

The setup endpoint is a public procedure (no authentication required) but can only be called once. After successful completion, all subsequent calls will return a FORBIDDEN error.
// First call - succeeds
await orpc.setup.complete.mutate({ /* ... */ });

// Second call - fails
await orpc.setup.complete.mutate({ /* ... */ });
// Error: Application is already configured

UI implementation

Example setup wizard component:
import { useState } from 'react';
import { orpc } from '@/utils/orpc';
import { useNavigate } from '@tanstack/react-router';

function SetupWizard() {
  const navigate = useNavigate();
  const [formData, setFormData] = useState({
    workspaceName: '',
    workspaceSlug: '',
    username: '',
    email: '',
    password: ''
  });
  
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    try {
      await orpc.setup.complete.mutate(formData);
      // Redirect to login after successful setup
      navigate({ to: '/login' });
    } catch (error) {
      console.error('Setup failed:', error);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <h1>Welcome to Nanahoshi</h1>
      <p>Set up your digital library</p>
      
      <input
        placeholder="Workspace Name"
        value={formData.workspaceName}
        onChange={(e) => setFormData({ 
          ...formData, 
          workspaceName: e.target.value 
        })}
        required
      />
      
      <input
        placeholder="Workspace Slug"
        value={formData.workspaceSlug}
        onChange={(e) => setFormData({ 
          ...formData, 
          workspaceSlug: e.target.value 
        })}
        required
      />
      
      <input
        placeholder="Username"
        value={formData.username}
        onChange={(e) => setFormData({ 
          ...formData, 
          username: e.target.value 
        })}
        required
      />
      
      <input
        type="email"
        placeholder="Email"
        value={formData.email}
        onChange={(e) => setFormData({ 
          ...formData, 
          email: e.target.value 
        })}
        required
      />
      
      <input
        type="password"
        placeholder="Password (min 8 characters)"
        value={formData.password}
        onChange={(e) => setFormData({ 
          ...formData, 
          password: e.target.value 
        })}
        required
        minLength={8}
      />
      
      <button type="submit">Complete Setup</button>
    </form>
  );
}

Setup detection

To determine if setup is required, you can check the app configuration status:
// This is typically done server-side in a route guard
import { isAppConfigured } from '@nanahoshi-v2/api/modules/settings.service';

const needsSetup = !(await isAppConfigured());

if (needsSetup) {
  // Redirect to /setup
} else {
  // Normal login flow
}
The first user created during setup receives admin privileges with full system access. Ensure you use a strong password and keep these credentials secure.
After setup completion, users should be directed to the login page. The initial admin can then sign in and configure libraries, invite other users, and set up organizations.

Build docs developers (and LLMs) love