Skip to content

Code Style Guide

Follow these coding standards when contributing to Nuxt Users.

TypeScript

General Guidelines

  • Use TypeScript: All new code should be written in TypeScript
  • Strict typing: Avoid any type when possible
  • Arrow functions: Use arrow functions consistently
  • No semicolons: Don't use semicolons in JavaScript/TypeScript files
  • Use semicolons: Use semicolons in PHP files

Type Definitions

ts
// Good: Proper type definitions
interface User {
  id: number
  email: string
  name: string
  password: string
  role: string
  created_at: string
  updated_at: string
}

// Good: Function with proper types
const createUser = async (userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> => {
  // Implementation
}

// Avoid: Using any type
const badFunction = (data: any) => {
  // Implementation
}

Import/Export

ts
// Good: Named imports
import { createDatabase } from 'db0'
import type { ModuleOptions } from '../../../types'

// Good: Default exports for main modules
export default defineNuxtModule<ModuleOptions>({
  // Configuration
})

// Good: Named exports for utilities
export const createUser = async () => {
  // Implementation
}

Vue Components

Composition API

Use Composition API with <script setup>:

vue
<script setup lang="ts">
import { ref } from 'vue'
import type { User } from '~/src/types'

interface Props {
  apiEndpoint?: string
  redirectTo?: string
}

interface Emits {
  (e: 'success', user: User): void
  (e: 'error', error: string): void
}

const props = withDefaults(defineProps<Props>(), {
  apiEndpoint: '/api/nuxt-users/session',
  redirectTo: '/'
})

const emit = defineEmits<Emits>()

const isLoading = ref(false)
const error = ref('')

const handleSubmit = async (formData: LoginFormData) => {
  // Implementation
}
</script>

Template Structure

vue
<template>
  <div class="component-container">
    <!-- Header -->
    <slot name="header">
      <div class="default-header">
        <h2>Default Title</h2>
      </div>
    </slot>

    <!-- Main content -->
    <div class="main-content">
      <slot />
    </div>

    <!-- Footer -->
    <slot name="footer" />
  </div>
</template>

Styling

vue
<style scoped>
/* Use CSS custom properties for theming */
.component-container {
  --border-color: #d1d5db;
  --border-color-focus: #3b82f6;
  --bg-color: #f9fafb;
}

/* Use :deep() for targeting child components */
:deep(.formkit-input) {
  border-color: var(--border-color);
}

:deep(.formkit-input:focus) {
  border-color: var(--border-color-focus);
}
</style>

Database Code

SQL Queries

ts
// Good: Parameterized queries
const user = await db.sql`
  SELECT * FROM ${tableName} 
  WHERE email = ${email}
` as { rows: User[] }

// Good: Table name interpolation
await db.sql`
  CREATE TABLE IF NOT EXISTS ${tableName} (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT NOT NULL UNIQUE
  )
`

// Avoid: String concatenation
const badQuery = `SELECT * FROM users WHERE email = '${email}'`

Error Handling

ts
// Good: Proper error handling
try {
  const result = await db.sql`SELECT * FROM users`
  return result.rows
} catch (error) {
  console.error('[Nuxt Users] Database query failed:', error)
  throw new Error('Failed to fetch users')
}

// Good: Database-specific handling
if (connectorName === 'sqlite') {
  await db.sql`CREATE TABLE users (...)`
} else if (connectorName === 'mysql') {
  await db.sql`CREATE TABLE users (...)`
} else if (connectorName === 'postgresql') {
  await db.sql`CREATE TABLE users (...)`
}

API Routes

Request Handling

ts
// Good: Proper request validation
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const { email, password } = body

  if (!email || !password) {
    throw createError({
      statusCode: 400,
      statusMessage: 'Email and password are required'
    })
  }

  // Implementation
})

Response Format

ts
// Good: Consistent response format
return {
  user: {
    id: user.id,
    email: user.email,
    name: user.name,
    created_at: user.created_at,
    updated_at: user.updated_at
  }
}

// Good: Error responses
throw createError({
  statusCode: 401,
  statusMessage: 'Invalid email or password'
})

File Organization

Directory Structure

src/
├── module.ts                    # Main module file
├── types.ts                     # TypeScript types
└── runtime/
    ├── components/              # Vue components
    ├── plugin.ts               # Nuxt plugin
    └── server/
        ├── api/                # API endpoints
        ├── services/           # Business logic
        └── utils/              # Database utilities

File Naming

  • kebab-case: For files and directories
  • PascalCase: For Vue components
  • camelCase: For functions and variables
  • UPPER_CASE: For constants
ts
// File names
create-users-table.ts
create-user.ts
database-utils.ts

// Component names
NUsersLoginForm.vue

// Function names
createUser()
checkUsersTableExists()
getConnector()

Comments and Documentation

Code Comments

ts
/**
 * Creates a new user in the database
 * @param userData - User data to create
 * @param options - Module options
 * @returns Promise<User> - Created user
 */
export const createUser = async (
  userData: CreateUserData,
  options: ModuleOptions
): Promise<User> => {
  // Hash password before storing
  const hashedPassword = await bcrypt.hash(userData.password, 10)
  
  // Insert user into database
  const result = await db.sql`
    INSERT INTO ${options.tables.users} (email, name, password)
    VALUES (${userData.email}, ${userData.name}, ${hashedPassword})
  `
  
  return result
}

JSDoc Comments

ts
/**
 * Module options interface
 */
export interface ModuleOptions {
  /** Database connector configuration */
  connector?: {
    /** Database type (sqlite, mysql) */
    name: DatabaseType
    /** Database connection options */
    options: DatabaseConfig
  }
  /** Table name configuration */
  tables: {
    /** Users table name */
    users: string
    /** Personal access tokens table name */
    personalAccessTokens: string
    /** Password reset tokens table name */
    passwordResetTokens: string
  }
  /** Mailer configuration for password resets */
  mailer?: MailerOptions
  /** Base URL for password reset links */
  passwordResetBaseUrl?: string
}

Testing

Test Structure

ts
describe('Feature Name', () => {
  let db: Database
  let testOptions: ModuleOptions

  beforeEach(async () => {
    // Setup test environment
    const settings = await createTestSetup({
      dbType: 'sqlite',
      dbConfig: { path: './_test-db' }
    })
    
    db = settings.db
    testOptions = settings.testOptions
  })

  afterEach(async () => {
    // Cleanup test data
  })

  it('should do something', async () => {
    // Test implementation
    expect(result).toBe(expected)
  })

  it('should handle errors gracefully', async () => {
    // Error test
    await expect(asyncFunction()).rejects.toThrow('Error message')
  })
})

Security Considerations

Input Validation

ts
// Good: Validate all inputs
const validateEmail = (email: string): boolean => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  return emailRegex.test(email)
}

const validatePassword = (password: string): boolean => {
  return password.length >= 6
}

Password Handling

ts
// Good: Hash passwords with bcrypt
const hashedPassword = await bcrypt.hash(password, 10)

// Good: Compare passwords securely
const isValid = await bcrypt.compare(password, hashedPassword)

SQL Injection Prevention

ts
// Good: Use parameterized queries
await db.sql`SELECT * FROM users WHERE email = ${email}`

// Avoid: String concatenation
const badQuery = `SELECT * FROM users WHERE email = '${email}'`

Performance

Database Queries

ts
// Good: Use indexes for performance
await db.sql`CREATE INDEX IF NOT EXISTS idx_users_email ON users (email)`

// Good: Limit query results
await db.sql`SELECT * FROM users LIMIT 10`

// Good: Use specific columns
await db.sql`SELECT id, email, name FROM users`

Memory Management

ts
// Good: Close database connections
const db = createDatabase(connector(options))
try {
  // Use database
} finally {
  await db.close()
}

Common Patterns

Configuration Pattern

ts
export const defaultOptions: ModuleOptions = {
  connector: {
    name: 'sqlite',
    options: {
      path: './data/users.sqlite3'
    }
  },
  tables: {
    users: 'users',
    personalAccessTokens: 'personal_access_tokens',
    passwordResetTokens: 'password_reset_tokens'
  }
}

Error Handling Pattern

ts
const safeOperation = async () => {
  try {
    const result = await riskyOperation()
    return result
  } catch (error) {
    console.error('[Nuxt Users] Operation failed:', error)
    throw new Error('Operation failed')
  }
}

Validation Pattern

ts
const validateUserData = (data: unknown): UserData => {
  if (!data || typeof data !== 'object') {
    throw new Error('Invalid user data')
  }
  
  const { email, name, password } = data as any
  
  if (!email || typeof email !== 'string') {
    throw new Error('Email is required')
  }
  
  if (!name || typeof name !== 'string') {
    throw new Error('Name is required')
  }
  
  if (!password || typeof password !== 'string') {
    throw new Error('Password is required')
  }
  
  return { email, name, password }
}

Next Steps

Released under the MIT License.