Skip to content

DEVELOPMENT

GitHub Actions Bot edited this page May 18, 2026 · 5 revisions

Development Guide

This guide covers local development setup for contributing to nself-admin.

Table of Contents

  1. Getting Started
  2. Project Structure
  3. Development Workflow
  4. Code Style Guide
  5. Testing
  6. Building
  7. Common Development Tasks
  8. Debugging
  9. Common Issues
  10. Contributing

Getting Started

Prerequisites

  • Node.js: 18.x or later
  • pnpm: 8.x or later
  • Docker: 20.10+ (for testing)
  • Git: Any recent version
  • Code Editor: VS Code recommended

Initial Setup

# Clone the repository
git clone https://github.com/nself-org/admin.git
cd nself-admin

# Install dependencies
pnpm install

# Copy environment file (optional)
cp .env.example .env.local

# Start development server
PORT=3021 pnpm dev

Open http://localhost:3021

VS Code Setup

Recommended extensions:

{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "bradlc.vscode-tailwindcss",
    "yoavbls.pretty-ts-errors",
    "streetsidesoftware.code-spell-checker"
  ]
}

Settings (.vscode/settings.json):

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "tailwindCSS.experimental.classRegex": [["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]]
}

Project Structure

nself-admin/
├── .claude/                 # Project instructions for Claude
│   └── CLAUDE.md           # Development guidelines
├── docs/                   # Documentation
│   ├── ARCHITECTURE.md     # Architecture details
│   ├── API.md              # API documentation
│   ├── DEPLOYMENT.md       # Deployment guide
│   ├── DEVELOPMENT.md      # This file
│   ├── MIGRATION.md        # Migration guides
│   └── CHANGELOG.md        # Release notes
├── public/                 # Static assets
│   ├── sw.js              # Service worker
│   └── offline.html       # Offline page
├── src/
│   ├── app/               # Next.js App Router
│   │   ├── api/          # API routes
│   │   ├── login/        # Login page
│   │   ├── build/        # Build wizard
│   │   ├── config/       # Configuration pages
│   │   ├── database/     # Database pages
│   │   ├── services/     # Service management pages
│   │   ├── deployment/   # Deployment pages
│   │   ├── cloud/        # Cloud provider pages
│   │   ├── plugins/      # Plugin pages
│   │   ├── monitor/      # Monitoring pages
│   │   ├── settings/     # Settings pages
│   │   ├── layout.tsx    # Root layout
│   │   └── page.tsx      # Dashboard
│   ├── components/        # React components
│   │   ├── ui/           # UI primitives
│   │   ├── build/        # Build wizard components
│   │   ├── config/       # Config components
│   │   ├── database/     # Database components
│   │   ├── services/     # Service components
│   │   ├── skeletons/    # Loading skeletons
│   │   ├── Header.tsx    # Top navigation
│   │   ├── Layout.tsx    # Main layout
│   │   └── Navigation.tsx # Sidebar navigation
│   ├── contexts/          # React contexts
│   │   └── AuthContext.tsx # Auth state
│   ├── hooks/             # Custom hooks
│   │   ├── useDashboardData.ts
│   │   ├── useServiceStatus.ts
│   │   ├── useSession.ts
│   │   └── useWebSocket.ts
│   ├── lib/               # Utilities and libraries
│   │   ├── api-client.ts  # API client
│   │   ├── auth-db.ts     # Auth database
│   │   ├── database.ts    # LokiJS database
│   │   ├── nselfCLI.ts    # nself CLI wrapper
│   │   ├── validation.ts  # Zod schemas
│   │   └── utils.ts       # Utility functions
│   └── services/          # Background services
│       └── SimplifiedPolling.ts # Polling service
├── tests/                 # Test files
│   ├── e2e/              # Playwright tests
│   └── accessibility/    # Accessibility tests
├── .env.example          # Example environment file
├── .eslintrc.json        # ESLint configuration
├── .prettierrc           # Prettier configuration
├── jest.config.js        # Jest configuration
├── next.config.mjs       # Next.js configuration
├── package.json          # Dependencies
├── pnpm-lock.yaml        # Lock file
├── tailwind.config.ts    # Tailwind configuration
└── tsconfig.json         # TypeScript configuration

Key Directories

src/app/ - Next.js App Router pages and API routes

  • Every folder is a route
  • page.tsx = page component
  • route.ts = API endpoint

src/components/ - Reusable React components

  • ui/ = Base components (buttons, inputs, etc.)
  • Feature-specific folders for complex components

src/lib/ - Utilities, helpers, and services

  • Business logic, database access, API clients

src/hooks/ - Custom React hooks

  • Data fetching, WebSocket, state management

Development Workflow

Starting Development Server

# Start with default port (3021)
pnpm dev

# Start with custom port
PORT=4000 pnpm dev

# Start with turbo mode (faster builds)
pnpm dev --turbo

Making Changes

  1. Create a branch

    git checkout -b feature/my-feature
  2. Make your changes

  • Edit files
  • Add tests
  • Update documentation
  1. Run quality checks

    pnpm run lint          # Check for errors
    pnpm run format        # Auto-format code
    pnpm run type-check    # TypeScript validation
    pnpm test              # Run unit tests
  2. Test manually

  • Start dev server
  • Test in browser
  • Check console for errors
  1. Commit changes

    git add .
    git commit -m "feat: add new feature"
  2. Push and create PR

    git push origin feature/my-feature
    # Open PR on GitHub

Hot Reload

Next.js supports hot module replacement:

  • Changes to components → instant reload
  • Changes to API routes → automatic restart
  • Changes to config → manual restart needed

Code Style Guide

TypeScript

Use strict typing:

// Good
interface ServiceStatus {
  name: string
  status: 'running' | 'stopped' | 'error'
  uptime: number
}

function getServiceStatus(name: string): ServiceStatus {
  // ...
}

// Avoid
function getServiceStatus(name: any): any {
  // ...
}

Prefer interfaces over types:

// Good
interface User {
  id: string
  name: string
}

// OK for unions
type Status = 'active' | 'inactive'

Use proper error handling:

// Good
try {
  await someOperation()
} catch (error) {
  const err = error as Error
  console.error('Operation failed:', err.message)
  return { success: false, error: err.message }
}

// Avoid
try {
  await someOperation()
} catch (error) {
  console.error(error) // Unknown type
}

React Components

Functional components with TypeScript:

// Good
interface ButtonProps {
  label: string
  onClick: () => void
  variant?: 'primary' | 'secondary'
}

export function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  )
}

Use proper hooks:

// Good
function MyComponent() {
  const [count, setCount] = useState(0)
  const [data, setData] = useState<Data | null>(null)

  useEffect(() => {
    fetchData().then(setData)
  }, [])

  return <div>{count}</div>
}

API Routes

Standard response format:

// Good
export async function GET(request: Request) {
  try {
    const data = await fetchData()
    return NextResponse.json({ success: true, data })
  } catch (error) {
    return NextResponse.json(
      {
        success: false,
        error: 'Failed to fetch data',
        details: error instanceof Error ? error.message : 'Unknown error',
      },
      { status: 500 }
    )
  }
}

Naming Conventions

Files:

  • Components: PascalCase.tsx (e.g., ServiceCard.tsx)
  • Utilities: camelCase.ts (e.g., apiClient.ts)
  • API routes: route.ts
  • Pages: page.tsx

Variables:

  • Constants: UPPER_SNAKE_CASE
  • Variables: camelCase
  • Components: PascalCase
  • Types/Interfaces: PascalCase

Functions:

  • Regular functions: camelCase
  • React components: PascalCase
  • Custom hooks: useCamelCase

Imports

Order:

  1. External libraries
  2. Internal utilities
  3. Components
  4. Types
  5. Styles
// Good
import { useState, useEffect } from 'react'
import { NextResponse } from 'next/server'

import { apiClient } from '@/lib/api-client'
import { Button } from '@/components/ui/button'

import type { ServiceStatus } from '@/types'

Use path aliases:

// Good
import { Button } from '@/components/ui/button'
import { apiClient } from '@/lib/api-client'

// Avoid
import { Button } from '../../../components/ui/button'

Testing

Unit Tests (Jest)

Run tests:

pnpm test              # Run all tests
pnpm test:watch        # Watch mode
pnpm test:coverage     # With coverage report

Example test:

// src/lib/__tests__/utils.test.ts
import { formatUptime } from '../utils'

describe('formatUptime', () => {
  it('formats seconds correctly', () => {
    expect(formatUptime(30)).toBe('30s')
  })

  it('formats minutes correctly', () => {
    expect(formatUptime(90)).toBe('1m 30s')
  })

  it('formats hours correctly', () => {
    expect(formatUptime(3665)).toBe('1h 1m 5s')
  })
})

E2E Tests (Playwright)

Run E2E tests:

pnpm test:e2e           # Run all E2E tests
pnpm test:e2e:headed    # Run with browser visible
pnpm test:e2e:ui        # Interactive mode
pnpm test:e2e:debug     # Debug mode

Example E2E test:

// tests/e2e/login.spec.ts
import { test, expect } from '@playwright/test'

test('should login successfully', async ({ page }) => {
  await page.goto('http://localhost:3021/login')

  await page.fill('input[name="password"]', 'testpassword123')
  await page.click('button[type="submit"]')

  await expect(page).toHaveURL('http://localhost:3021/')
  await expect(page.locator('h1')).toContainText('Dashboard')
})

Accessibility Tests

Run accessibility audit:

pnpm test:a11y

Uses Pa11y to check WCAG 2.1 AA compliance.


Building

Development Build

pnpm build

Outputs to .next/ directory.

Production Build

NODE_ENV=production pnpm build

Optimizes for production:

  • Minification
  • Code splitting
  • Tree shaking
  • Image optimization

Docker Build

# Build image
pnpm docker:build

# Push to registry
pnpm docker:push

# Or use script
./scripts/docker-release.sh

Build Analysis

Analyze bundle size:

ANALYZE=true pnpm build

Opens interactive bundle analyzer.


Common Development Tasks

Adding a New Page

  1. Create page file:

    mkdir -p src/app/my-feature
    touch src/app/my-feature/page.tsx
  2. Create page component:

    export default function MyFeaturePage() {
      return <div>My Feature</div>
    }
  3. Add to navigation:

    // src/components/Navigation.tsx
    {
      name: 'My Feature',
      href: '/my-feature',
      icon: SparklesIcon
    }

Adding an API Route

  1. Create route file:

    mkdir -p src/app/api/my-feature
    touch src/app/api/my-feature/route.ts
  2. Implement handlers:

    import { NextResponse } from 'next/server'
    
    export async function GET(request: Request) {
      try {
        const data = await fetchData()
        return NextResponse.json({ success: true, data })
      } catch (error) {
        return NextResponse.json({ success: false, error: 'Failed' }, { status: 500 })
      }
    }
    
    export async function POST(request: Request) {
      // ...
    }

Adding a Component

  1. Create component file:

    touch src/components/MyComponent.tsx
  2. Implement component:

    interface MyComponentProps {
      title: string
    }
    
    export function MyComponent({ title }: MyComponentProps) {
      return <div>{title}</div>
    }
  3. Export from index (if in ui/):

    // src/components/ui/index.ts
    export { MyComponent } from './MyComponent'

Adding a Custom Hook

  1. Create hook file:

    touch src/hooks/useMyHook.ts
  2. Implement hook:

    import { useState, useEffect } from 'react'
    
    export function useMyHook(param: string) {
      const [data, setData] = useState(null)
    
      useEffect(() => {
        // Fetch data
      }, [param])
    
      return { data }
    }

Adding Database Fields

LokiJS is used for nAdmin's internal database.

  1. Update schema:

    // src/lib/database.ts
    interface MyCollection {
      key: string
      value: any
      timestamp: number
    }
  2. Add collection:

    const collection =
      db.getCollection<MyCollection>('my_collection') ||
      db.addCollection('my_collection', {
        unique: ['key'],
        ttl: 3600000, // 1 hour
      })

Debugging

Browser DevTools

React DevTools:

  • Install extension
  • Inspect component tree
  • View props and state

Network Tab:

  • Monitor API requests
  • Check response times
  • Debug failed requests

Console:

  • View logs and errors
  • Test JavaScript

VS Code Debugging

Create .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node-terminal",
      "request": "launch",
      "command": "pnpm dev"
    },
    {
      "name": "Next.js: debug client-side",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3021"
    }
  ]
}

Debugging API Routes

Add breakpoints or console.log:

export async function GET(request: Request) {
  console.log('Request received:', request.url)
  debugger // Pauses execution
  // ...
}

Debugging nself CLI Calls

Enable CLI debug output:

// src/lib/nselfCLI.ts
const result = await execFile('nself', ['status'], {
  cwd: projectPath,
  env: { ...process.env, DEBUG: '1' },
})
console.log('CLI output:', result.stdout)

Common Issues

Port Already in Use

Error: EADDRINUSE: address already in use :::3021

Solution:

# Find process using port
lsof -i :3021

# Kill process
kill -9 <PID>

# Or use different port
PORT=4000 pnpm dev

Module Not Found

Error: Cannot find module '@/components/...'

Solution:

# Reinstall dependencies
rm -rf node_modules pnpm-lock.yaml
pnpm install

TypeScript Errors

Error: Type errors during development

Solution:

# Run type check
pnpm run type-check

# Restart TypeScript server in VS Code
Cmd+Shift+P → "TypeScript: Restart TS Server"

Hot Reload Not Working

Solution:

# Restart dev server
Ctrl+C
pnpm dev

# Clear Next.js cache
rm -rf .next
pnpm dev

Docker Socket Permission Denied

Error: permission denied while trying to connect to the Docker daemon socket

Solution:

# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker

# Verify
docker ps

Contributing

Before Submitting PR

Checklist:

  • Code follows style guide
  • TypeScript compiles (pnpm run type-check)
  • Linting passes (pnpm run lint)
  • Code is formatted (pnpm run format)
  • Tests pass (pnpm test)
  • E2E tests pass (if applicable)
  • Documentation updated
  • Commit messages follow convention

Commit Message Convention

Format: <type>(<scope>): <subject>

Types:

  • feat - New feature
  • fix - Bug fix
  • docs - Documentation
  • style - Formatting
  • refactor - Code restructuring
  • test - Adding tests
  • chore - Maintenance

Examples:

feat(database): add backup scheduling
fix(auth): resolve session timeout issue
docs(api): update API documentation
refactor(services): simplify service status logic
test(auth): add login flow tests

Code Review Process

  1. Submit PR with description
  2. Automated checks run (CI)
  3. Reviewer provides feedback
  4. Make requested changes
  5. Approved and merged

Additional Resources


Questions? Open an issue on GitHub.

Clone this wiki locally