Nuxt Guide

TypeScript

TypeScript conventions for frontend types — declaration style, naming, and patterns.

Type Files

Types live in app/types/ and are globally available via Nuxt's auto-import (declare types require no explicit import).

app/types/
  index.ts      ← barrel export for exported types
  auth.ts       ← TUser, TUserType, TUserStatus
  resource.ts   ← IResource, TResourceStatus
  form.ts       ← TValidationRule, TValidationRules, TApiActionOptions
  api.ts        ← TPaginatedResponse<T>

Declaration Style

Use declare type and declare interface in .d.ts files — this makes types globally available without any import statement. Use export type / export interface only in regular .ts files consumed via barrel exports.

// types/auth.d.ts  — globally available, no import needed
declare type TLogin = {
  email:    string
  password: string
}

declare type TRegister = {
  id?:       string
  firstName: string
  lastName:  string
  phone:     string
  email:     string
  password:  string
}

declare interface IUser {
  _id:       string
  firstName: string
  lastName:  string
  email:     string
  phone?:    string
  role:      TUserRole
  status:    TUserStatus
}

declare type TUserRole   = 'ADMIN' | 'OWNER' | 'CUSTOMER'
declare type TUserStatus = 'ACTIVE' | 'INACTIVE' | 'PENDING'

For nested / complex request shapes, keep nesting inline:

declare type TRegisterOwner = {
  personalDetails: {
    firstName:  string
    middleName: string
    lastName:   string
    phone:      string
  }
  businessDetails: {
    name:                 string
    businessType:         string
    businessAddress: {
      region:         string
      province:       string
      city:           string
      barangay:       string
      street:         string
      zip:            string
    }
  }
  credentials: {
    email:    string
    password: string
  }
  verifiedOTP: string
}

Naming Conventions

PatternWhen to useExample
I prefix (interface)Entity/model shapesIUser, IResource
T prefix (type alias)Unions, derived types, request/response shapesTUserRole, TRegister
No prefixLocal component props/emitsProps, Emits

Common Entity Type

// types/resource.d.ts
declare interface IResource {
  _id:             string
  name:            string
  description?:    string
  status?:         TResourceStatus
  organizationId?: string
  createdAt?:      string
  updatedAt?:      string
}

declare type TResourceStatus = 'ACTIVE' | 'INACTIVE'
declare type TResourceCreate = Pick<IResource, 'name' | 'description' | 'organizationId'>

Form Types

// types/form.d.ts
declare type TValidationRule  = (value: any) => string
declare type TValidationRules = Record<string, TValidationRule>

declare type TApiActionOptions = {
  successMessage?: string
  errorMessage?:   string
  skipAlert?:      boolean
  onSuccess?:      (result: any) => void | Promise<void>
  onError?:        (error: any)  => void
}

Paginated Response

// types/api.d.ts
declare interface TPaginatedResponse<T> {
  items: T[]
  pages: number
  total: number
  page:  number
  limit: number
}

defineProps and defineEmits Typing

Use the runtime object form for defineProps and the array form for defineEmits. Always provide a concrete PropType<T> — avoid PropType<any>.

// ✅ Correct
const props = defineProps({
  modelValue: { type: Object as PropType<TAddress>, required: true },
  disabled:   { type: Boolean, default: false },
  required:   { type: Boolean, default: true },
})

const emit = defineEmits(['update:modelValue'])
// ❌ Avoid — PropType<any> defeats type safety
const props = defineProps({
  modelValue: { type: Object as PropType<any>, required: true },
})
// ❌ Avoid — Options API / PropType object without a real type
const props = defineProps({
  item: { type: Object, required: true },
})

PropType reference

Prop shapePropType to use
Object / interfaceObject as PropType<IResource>
Array of objectsArray as PropType<IResource[]>
Union stringString as PropType<'ACTIVE' | 'INACTIVE'>
FunctionFunction as PropType<() => void>

API Response Typing

Type all $api calls at the call site:

const response = await useNuxtApp().$api<TPaginatedResponse<IResource>>(
  '/api/resources',
  { method: 'GET', query: { page: 1 } }
)

Strict Null Checks

tsconfig.json has strict: true. Always handle possible null/undefined:

// ✅
const name = currentUser.value?.firstName ?? 'Guest'

// ❌
const name = currentUser.value.firstName

tsconfig.json Baseline

{
  "extends": "./.nuxt/tsconfig.json",
  "compilerOptions": {
    "strict": true
  }
}