Skip to main content
Properly defining API parameters provides type safety and clear API documentation.

Parameters Overview

Type Safety

Compile-time validationAutocomplete support

Clear Contract

Auto-generated API specClient guide

Validation

Automatic type validationEarly error detection

Documentation

Add descriptions with commentsEasy maintenance

Basic Types

Primitive Types

class UserModel extends BaseModelClass {
  // Number
  @api({ httpMethod: "GET" })
  async getUser(id: number): Promise<User> {
    // GET /api/user/getUser?id=1
    // ...
  }
  
  // String
  @api({ httpMethod: "GET" })
  async findByEmail(email: string): Promise<User | null> {
    // GET /api/user/findByEmail?email=test@example.com
    // ...
  }
  
  // Boolean
  @api({ httpMethod: "GET" })
  async listActive(active: boolean): Promise<User[]> {
    // GET /api/user/listActive?active=true
    // ...
  }
  
  // Date
  @api({ httpMethod: "GET" })
  async listByDate(fromDate: Date, toDate: Date): Promise<User[]> {
    // GET /api/user/listByDate?fromDate=2025-01-01&toDate=2025-12-31
    // ...
  }
}

Array Types

class UserModel extends BaseModelClass {
  // Number array
  @api({ httpMethod: "GET" })
  async getMultiple(ids: number[]): Promise<User[]> {
    // GET /api/user/getMultiple?ids=1,2,3
    const rdb = this.getPuri("r");
    return rdb.table("users").whereIn("id", ids).select("*");
  }
  
  // String array
  @api({ httpMethod: "GET" })
  async findByRoles(roles: UserRole[]): Promise<User[]> {
    // GET /api/user/findByRoles?roles=admin,manager
    const rdb = this.getPuri("r");
    return rdb.table("users").whereIn("role", roles).select("*");
  }
}

Interface Definitions

Simple Interface

interface CreateUserParams {
  email: string;
  username: string;
  password: string;
  role: "admin" | "manager" | "normal";
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async create(params: CreateUserParams): Promise<{ userId: number }> {
    const wdb = this.getPuri("w");
    
    // Autocomplete available for params.email, params.username, etc.
    const [user] = await wdb
      .table("users")
      .insert({
        email: params.email,
        username: params.username,
        password: params.password,
        role: params.role,
      })
      .returning({ id: "id" });
    
    return { userId: user.id };
  }
}

// Call: POST /api/user/create
// Body: {
//   "email": "test@example.com",
//   "username": "testuser",
//   "password": "hashedpass",
//   "role": "normal"
// }

Optional Fields

interface UserListParams {
  // Required parameters
  page: number;
  pageSize: number;
  
  // Optional parameters
  search?: string;
  role?: UserRole;
  isActive?: boolean;
  sortBy?: "created_at" | "username" | "email";
  sortOrder?: "asc" | "desc";
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async list(params: UserListParams): Promise<{
    users: User[];
    total: number;
  }> {
    const rdb = this.getPuri("r");
    
    let query = rdb.table("users");
    
    // Handle optional parameters
    if (params.search) {
      query = query.where("username", "like", `%${params.search}%`);
    }
    
    if (params.role) {
      query = query.where("role", params.role);
    }
    
    if (params.isActive !== undefined) {
      query = query.where("is_active", params.isActive);
    }
    
    // Sorting (provide defaults)
    const sortBy = params.sortBy || "created_at";
    const sortOrder = params.sortOrder || "desc";
    query = query.orderBy(sortBy, sortOrder);
    
    // Pagination
    const users = await query
      .limit(params.pageSize)
      .offset((params.page - 1) * params.pageSize)
      .select("*");
    
    const [{ count }] = await rdb.table("users").count({ count: "*" });
    
    return { users, total: count };
  }
}

Nested Objects

interface RegisterParams {
  // User info
  email: string;
  username: string;
  password: string;
  
  // Profile info (nested)
  profile: {
    bio: string;
    avatarUrl?: string;
    socialLinks?: {
      twitter?: string;
      github?: string;
      linkedin?: string;
    };
  };
  
  // Settings (nested)
  preferences: {
    theme: "light" | "dark";
    language: "ko" | "en";
    notifications: {
      email: boolean;
      push: boolean;
    };
  };
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async register(params: RegisterParams): Promise<{ userId: number }> {
    const wdb = this.getPuri("w");
    
    // Create User
    const userRef = wdb.ubRegister("users", {
      email: params.email,
      username: params.username,
      password: params.password,
      role: "normal",
    });
    
    // Create Profile
    wdb.ubRegister("profiles", {
      user_id: userRef,
      bio: params.profile.bio,
      avatar_url: params.profile.avatarUrl || null,
      social_links: JSON.stringify(params.profile.socialLinks || {}),
    });
    
    // Create Preferences
    wdb.ubRegister("user_preferences", {
      user_id: userRef,
      theme: params.preferences.theme,
      language: params.preferences.language,
      email_notifications: params.preferences.notifications.email,
      push_notifications: params.preferences.notifications.push,
    });
    
    const [userId] = await wdb.ubUpsert("users");
    await wdb.ubUpsert("profiles");
    await wdb.ubUpsert("user_preferences");
    
    return { userId };
  }
}

Union Types and Enums

Using Enums

// Enum definitions
enum UserRole {
  Admin = "admin",
  Manager = "manager",
  Normal = "normal",
}

enum UserStatus {
  Active = "active",
  Inactive = "inactive",
  Suspended = "suspended",
}

interface UpdateUserParams {
  role?: UserRole;
  status?: UserStatus;
  bio?: string;
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "PUT" })
  @transactional()
  async update(
    id: number,
    params: UpdateUserParams
  ): Promise<void> {
    const wdb = this.getPuri("w");
    
    const updateData: any = {};
    
    if (params.role) {
      // Enum value validation is automatic
      updateData.role = params.role;
    }
    
    if (params.status) {
      updateData.status = params.status;
    }
    
    if (params.bio !== undefined) {
      updateData.bio = params.bio;
    }
    
    await wdb.table("users").where("id", id).update(updateData);
  }
}

Literal Union Types

interface SearchParams {
  query: string;
  // Literal union type
  searchIn: "username" | "email" | "bio" | "all";
  sortBy: "relevance" | "created_at" | "username";
  order: "asc" | "desc";
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async search(params: SearchParams): Promise<User[]> {
    const rdb = this.getPuri("r");
    
    let query = rdb.table("users");
    
    // Determine search field based on searchIn
    switch (params.searchIn) {
      case "username":
        query = query.where("username", "like", `%${params.query}%`);
        break;
      case "email":
        query = query.where("email", "like", `%${params.query}%`);
        break;
      case "bio":
        query = query.where("bio", "like", `%${params.query}%`);
        break;
      case "all":
        query = query.where((qb) => {
          qb.where("username", "like", `%${params.query}%`)
            .orWhere("email", "like", `%${params.query}%`)
            .orWhere("bio", "like", `%${params.query}%`);
        });
        break;
    }
    
    return query.orderBy(params.sortBy, params.order).select("*");
  }
}

Generic Types

Common List Parameters

// Reusable generic interface
interface ListParams<T> {
  page: number;
  pageSize: number;
  sortBy?: keyof T;
  sortOrder?: "asc" | "desc";
}

// Extended for User
interface UserListParams extends ListParams<User> {
  search?: string;
  role?: UserRole;
}

// Extended for Product
interface ProductListParams extends ListParams<Product> {
  category?: string;
  minPrice?: number;
  maxPrice?: number;
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async list(params: UserListParams): Promise<User[]> {
    // sortBy can only be User keys (type safe)
    // ...
  }
}

Validation Logic

Custom Validation

interface CreateUserParams {
  email: string;
  username: string;
  password: string;
  age?: number;
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async create(params: CreateUserParams): Promise<{ userId: number }> {
    // Validate parameters
    this.validateCreateParams(params);
    
    const wdb = this.getPuri("w");
    
    // ...
  }
  
  private validateCreateParams(params: CreateUserParams): void {
    // Email format validation
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(params.email)) {
      throw new Error("Invalid email format");
    }
    
    // Username length validation
    if (params.username.length < 3 || params.username.length > 20) {
      throw new Error("Username must be between 3 and 20 characters");
    }
    
    // Password strength validation
    if (params.password.length < 8) {
      throw new Error("Password must be at least 8 characters");
    }
    
    // Age validation
    if (params.age !== undefined && (params.age < 18 || params.age > 120)) {
      throw new Error("Age must be between 18 and 120");
    }
  }
}

Validation with Zod

import { z } from "zod";

// Zod schema definition
const CreateUserSchema = z.object({
  email: z.string().email("Invalid email format"),
  username: z.string().min(3).max(20),
  password: z.string().min(8),
  age: z.number().min(18).max(120).optional(),
  role: z.enum(["admin", "manager", "normal"]),
});

type CreateUserParams = z.infer<typeof CreateUserSchema>;

class UserModel extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async create(params: CreateUserParams): Promise<{ userId: number }> {
    // Validate with Zod
    const validated = CreateUserSchema.parse(params);
    
    const wdb = this.getPuri("w");
    
    // validated has guaranteed types
    const [user] = await wdb
      .table("users")
      .insert({
        email: validated.email,
        username: validated.username,
        password: validated.password,
        age: validated.age,
        role: validated.role,
      })
      .returning({ id: "id" });
    
    return { userId: user.id };
  }
}

Parameter Documentation

JSDoc Comments

/**
 * Retrieves a list of users.
 */
interface UserListParams {
  /**
   * Page number (starts from 1)
   * @example 1
   */
  page: number;
  
  /**
   * Number of items per page
   * @example 20
   * @min 1
   * @max 100
   */
  pageSize: number;
  
  /**
   * Search keyword (searches in username, email)
   * @example "john"
   */
  search?: string;
  
  /**
   * User role filter
   * @example "admin"
   */
  role?: "admin" | "manager" | "normal";
  
  /**
   * Sort field
   * @default "created_at"
   */
  sortBy?: "created_at" | "username" | "email";
  
  /**
   * Sort order
   * @default "desc"
   */
  sortOrder?: "asc" | "desc";
}

Practical Patterns

Pagination Parameters

interface PaginationParams {
  page: number;
  pageSize: number;
}

interface UserListParams extends PaginationParams {
  search?: string;
  role?: UserRole;
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async list(params: UserListParams): Promise<{
    data: User[];
    pagination: {
      page: number;
      pageSize: number;
      total: number;
      totalPages: number;
    };
  }> {
    const rdb = this.getPuri("r");
    
    // Default value handling
    const page = Math.max(1, params.page);
    const pageSize = Math.min(100, Math.max(1, params.pageSize));
    
    // ...
  }
}

Filter Parameters

interface ProductFilterParams {
  // Category
  category?: string;
  
  // Price range
  minPrice?: number;
  maxPrice?: number;
  
  // Stock status
  inStock?: boolean;
  
  // Brands
  brands?: string[];
  
  // Tags
  tags?: string[];
  
  // Search
  search?: string;
}

class ProductModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async search(
    params: ProductFilterParams & PaginationParams
  ): Promise<Product[]> {
    const rdb = this.getPuri("r");
    
    let query = rdb.table("products");
    
    if (params.category) {
      query = query.where("category", params.category);
    }
    
    if (params.minPrice !== undefined) {
      query = query.where("price", ">=", params.minPrice);
    }
    
    if (params.maxPrice !== undefined) {
      query = query.where("price", "<=", params.maxPrice);
    }
    
    if (params.inStock !== undefined) {
      if (params.inStock) {
        query = query.where("stock", ">", 0);
      } else {
        query = query.where("stock", "=", 0);
      }
    }
    
    if (params.brands && params.brands.length > 0) {
      query = query.whereIn("brand", params.brands);
    }
    
    // ...
  }
}

Next Steps