๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
HTTP ๋ฉ”์„œ๋“œ๋Š” API์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ํ‘œํ˜„ํ•˜๊ณ  RESTful ์›์น™์„ ๋”ฐ๋ฅด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

HTTP ๋ฉ”์„œ๋“œ ๊ฐœ์š”

GET

๋ฐ์ดํ„ฐ ์กฐํšŒ์•ˆ์ „ํ•˜๊ณ  ๋ฉฑ๋“ฑ์„ฑ

POST

๋ฐ์ดํ„ฐ ์ƒ์„ฑ๋น„๋ฉฑ๋“ฑ์„ฑ

PUT

๋ฐ์ดํ„ฐ ์ˆ˜์ •๋ฉฑ๋“ฑ์„ฑ

DELETE

๋ฐ์ดํ„ฐ ์‚ญ์ œ๋ฉฑ๋“ฑ์„ฑ

GET - ์กฐํšŒ

๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋‹จ์ผ ๋ฆฌ์†Œ์Šค ์กฐํšŒ

class UserModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async get(id: number): Promise<User> {
    const rdb = this.getPuri("r");
    
    const user = await rdb
      .table("users")
      .where("id", id)
      .first();
    
    if (!user) {
      throw new Error("User not found");
    }
    
    return user;
  }
}

// ํ˜ธ์ถœ: GET /api/user/get?id=1

๋ชฉ๋ก ์กฐํšŒ

interface UserListParams {
  page?: number;
  pageSize?: number;
  search?: string;
  role?: UserRole;
  sortBy?: "created_at" | "username";
  sortOrder?: "asc" | "desc";
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async list(params: UserListParams): Promise<{
    users: User[];
    total: number;
    page: number;
    pageSize: number;
  }> {
    const rdb = this.getPuri("r");
    
    // ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
    const page = params.page || 1;
    const pageSize = params.pageSize || 20;
    const sortBy = params.sortBy || "created_at";
    const sortOrder = params.sortOrder || "desc";
    
    // ์ฟผ๋ฆฌ ๋นŒ๋“œ
    let query = rdb.table("users");
    
    // ๊ฒ€์ƒ‰ ์กฐ๊ฑด
    if (params.search) {
      query = query.where((qb) => {
        qb.where("username", "like", `%${params.search}%`)
          .orWhere("email", "like", `%${params.search}%`);
      });
    }
    
    // ์—ญํ•  ํ•„ํ„ฐ
    if (params.role) {
      query = query.where("role", params.role);
    }
    
    // ์ •๋ ฌ
    query = query.orderBy(sortBy, sortOrder);
    
    // ํŽ˜์ด์ง€๋„ค์ด์…˜
    const users = await query
      .limit(pageSize)
      .offset((page - 1) * pageSize)
      .select("*");
    
    // ์ „์ฒด ๊ฐœ์ˆ˜
    const [{ count }] = await rdb
      .table("users")
      .count({ count: "*" });
    
    return {
      users,
      total: count,
      page,
      pageSize,
    };
  }
}

// ํ˜ธ์ถœ: GET /api/user/list?page=1&pageSize=20&search=john&role=admin&sortBy=username&sortOrder=asc

๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ์กฐํšŒ

class UserModel extends BaseModelClass {
  @api({ httpMethod: "GET" })
  async getWithProfile(id: number): Promise<{
    user: User;
    profile: Profile | null;
    posts: Post[];
  }> {
    const rdb = this.getPuri("r");
    
    // User ์กฐํšŒ
    const user = await rdb
      .table("users")
      .where("id", id)
      .first();
    
    if (!user) {
      throw new Error("User not found");
    }
    
    // Profile ์กฐํšŒ (1:1)
    const profile = await rdb
      .table("profiles")
      .where("user_id", id)
      .first();
    
    // Posts ์กฐํšŒ (1:N)
    const posts = await rdb
      .table("posts")
      .where("user_id", id)
      .orderBy("created_at", "desc")
      .select("*");
    
    return {
      user,
      profile: profile || null,
      posts,
    };
  }
}
GET ๋ฉ”์„œ๋“œ ํŠน์ง•:
  • ์•ˆ์ „ํ•จ (Safe): ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Œ
  • ๋ฉฑ๋“ฑ์„ฑ (Idempotent): ๊ฐ™์€ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•ด๋„ ๊ฒฐ๊ณผ ๋™์ผ
  • ์บ์‹œ ๊ฐ€๋Šฅ (Cacheable): ๋ธŒ๋ผ์šฐ์ €/ํ”„๋ก์‹œ์—์„œ ์บ์‹œ ๊ฐ€๋Šฅ
  • ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” URL ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์œผ๋กœ ์ „๋‹ฌ

POST - ์ƒ์„ฑ

์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋‹จ์ˆœ ์ƒ์„ฑ

interface CreateUserParams {
  email: string;
  username: string;
  password: string;
  role: UserRole;
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async create(params: CreateUserParams): Promise<{
    userId: number;
  }> {
    const wdb = this.getPuri("w");
    
    // ์ค‘๋ณต ์ฒดํฌ
    const existing = await wdb
      .table("users")
      .where("email", params.email)
      .first();
    
    if (existing) {
      throw new Error("Email already exists");
    }
    
    // User ์ƒ์„ฑ
    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 };
  }
}

// ํ˜ธ์ถœ: POST /api/user/create
// Body: { "email": "...", "username": "...", "password": "...", "role": "normal" }

๋ณต์žกํ•œ ์ƒ์„ฑ (๊ด€๊ณ„ ๋ฐ์ดํ„ฐ ํฌํ•จ)

interface RegisterParams {
  email: string;
  username: string;
  password: string;
  profile: {
    bio: string;
    avatarUrl?: string;
  };
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async register(params: RegisterParams): Promise<{
    userId: number;
    profileId: number;
  }> {
    const wdb = this.getPuri("w");
    
    // User ์ƒ์„ฑ
    const userRef = wdb.ubRegister("users", {
      email: params.email,
      username: params.username,
      password: params.password,
      role: "normal",
    });
    
    // Profile ์ƒ์„ฑ
    wdb.ubRegister("profiles", {
      user_id: userRef,
      bio: params.profile.bio,
      avatar_url: params.profile.avatarUrl || null,
    });
    
    // ์ €์žฅ
    const [userId] = await wdb.ubUpsert("users");
    const [profileId] = await wdb.ubUpsert("profiles");
    
    return { userId, profileId };
  }
}

๋ฐฐ์น˜ ์ƒ์„ฑ

class UserModel extends BaseModelClass {
  @api({ httpMethod: "POST" })
  @transactional()
  async createBatch(users: CreateUserParams[]): Promise<{
    userIds: number[];
    count: number;
  }> {
    const wdb = this.getPuri("w");
    
    // ๋ชจ๋“  User ๋“ฑ๋ก
    users.forEach((user) => {
      wdb.ubRegister("users", {
        email: user.email,
        username: user.username,
        password: user.password,
        role: user.role,
      });
    });
    
    // ์ผ๊ด„ ์ €์žฅ
    const userIds = await wdb.ubUpsert("users");
    
    return {
      userIds,
      count: userIds.length,
    };
  }
}

// ํ˜ธ์ถœ: POST /api/user/createBatch
// Body: [
//   { "email": "[email protected]", "username": "user1", ... },
//   { "email": "[email protected]", "username": "user2", ... }
// ]
POST ๋ฉ”์„œ๋“œ ํŠน์ง•:
  • ๋น„์•ˆ์ „ํ•จ: ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•จ
  • ๋น„๋ฉฑ๋“ฑ์„ฑ: ๊ฐ™์€ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•˜๋ฉด ๋ฆฌ์†Œ์Šค๊ฐ€ ์ค‘๋ณต ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์Œ
  • ์บ์‹œ ๋ถˆ๊ฐ€๋Šฅ
  • ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” Body๋กœ ์ „๋‹ฌ
  • ์ผ๋ฐ˜์ ์œผ๋กœ ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์˜ ID๋ฅผ ๋ฐ˜ํ™˜

PUT - ์ˆ˜์ •

๊ธฐ์กด ๋ฆฌ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ „์ฒด ์ˆ˜์ •

interface UpdateUserParams {
  email: string;
  username: string;
  role: UserRole;
  bio: string | null;
}

class UserModel extends BaseModelClass {
  @api({ httpMethod: "PUT" })
  @transactional()
  async update(
    id: number,
    params: UpdateUserParams
  ): Promise<void> {
    const wdb = this.getPuri("w");
    
    // User ์กด์žฌ ํ™•์ธ
    const user = await wdb
      .table("users")
      .where("id", id)
      .first();
    
    if (!user) {
      throw new Error("User not found");
    }
    
    // ์ „์ฒด ํ•„๋“œ ์—…๋ฐ์ดํŠธ
    await wdb
      .table("users")
      .where("id", id)
      .update({
        email: params.email,
        username: params.username,
        role: params.role,
        bio: params.bio,
      });
  }
}

// ํ˜ธ์ถœ: PUT /api/user/update?id=1
// Body: { "email": "...", "username": "...", "role": "admin", "bio": "..." }

๋ถ€๋ถ„ ์ˆ˜์ • (PATCH ์Šคํƒ€์ผ)

class UserModel extends BaseModelClass {
  @api({ httpMethod: "PUT" })
  @transactional()
  async updatePartial(
    id: number,
    params: Partial<UpdateUserParams>
  ): Promise<void> {
    const wdb = this.getPuri("w");
    
    // User ์กด์žฌ ํ™•์ธ
    const user = await wdb
      .table("users")
      .where("id", id)
      .first();
    
    if (!user) {
      throw new Error("User not found");
    }
    
    // ์ „๋‹ฌ๋œ ํ•„๋“œ๋งŒ ์—…๋ฐ์ดํŠธ
    const updateData: any = {};
    
    if (params.email !== undefined) updateData.email = params.email;
    if (params.username !== undefined) updateData.username = params.username;
    if (params.role !== undefined) updateData.role = params.role;
    if (params.bio !== undefined) updateData.bio = params.bio;
    
    if (Object.keys(updateData).length > 0) {
      await wdb
        .table("users")
        .where("id", id)
        .update(updateData);
    }
  }
}

// ํ˜ธ์ถœ: PUT /api/user/updatePartial?id=1
// Body: { "bio": "Updated bio" }  // ๋‹ค๋ฅธ ํ•„๋“œ๋Š” ๋ณ€๊ฒฝ ์•ˆ ๋จ

์ƒํƒœ ๋ณ€๊ฒฝ

class OrderModel extends BaseModelClass {
  @api({ httpMethod: "PUT" })
  @transactional()
  async updateStatus(
    orderId: number,
    status: "pending" | "paid" | "shipped" | "delivered" | "cancelled"
  ): Promise<void> {
    const wdb = this.getPuri("w");
    
    const order = await wdb
      .table("orders")
      .where("id", orderId)
      .first();
    
    if (!order) {
      throw new Error("Order not found");
    }
    
    // ์ƒํƒœ ์ „ํ™˜ ๊ฒ€์ฆ
    if (!this.isValidStatusTransition(order.status, status)) {
      throw new Error(`Cannot change status from ${order.status} to ${status}`);
    }
    
    await wdb
      .table("orders")
      .where("id", orderId)
      .update({ status });
  }
  
  private isValidStatusTransition(
    from: string,
    to: string
  ): boolean {
    const transitions: Record<string, string[]> = {
      pending: ["paid", "cancelled"],
      paid: ["shipped", "cancelled"],
      shipped: ["delivered"],
      delivered: [],
      cancelled: [],
    };
    
    return transitions[from]?.includes(to) || false;
  }
}
PUT ๋ฉ”์„œ๋“œ ํŠน์ง•:
  • ๋น„์•ˆ์ „ํ•จ: ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•จ
  • ๋ฉฑ๋“ฑ์„ฑ: ๊ฐ™์€ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•ด๋„ ๊ฒฐ๊ณผ ๋™์ผ
  • ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฆฌ์†Œ์Šค ์ „์ฒด๋ฅผ ๊ต์ฒด
  • ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” Body๋กœ ์ „๋‹ฌ
  • ์„ฑ๊ณต ์‹œ ๋นˆ ์‘๋‹ต ๋˜๋Š” ์—…๋ฐ์ดํŠธ๋œ ๋ฆฌ์†Œ์Šค ๋ฐ˜ํ™˜

DELETE - ์‚ญ์ œ

๋ฆฌ์†Œ์Šค๋ฅผ ์‚ญ์ œํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๋‹จ์ผ ์‚ญ์ œ

class UserModel extends BaseModelClass {
  @api({ httpMethod: "DELETE" })
  @transactional()
  async remove(id: number): Promise<void> {
    const wdb = this.getPuri("w");
    
    // User ์กด์žฌ ํ™•์ธ
    const user = await wdb
      .table("users")
      .where("id", id)
      .first();
    
    if (!user) {
      throw new Error("User not found");
    }
    
    // ์‚ญ์ œ
    await wdb
      .table("users")
      .where("id", id)
      .delete();
  }
}

// ํ˜ธ์ถœ: DELETE /api/user/remove?id=1

์†Œํ”„ํŠธ ์‚ญ์ œ

class UserModel extends BaseModelClass {
  @api({ httpMethod: "DELETE" })
  @transactional()
  async softDelete(id: number): Promise<void> {
    const wdb = this.getPuri("w");
    
    const user = await wdb
      .table("users")
      .where("id", id)
      .whereNull("deleted_at")
      .first();
    
    if (!user) {
      throw new Error("User not found");
    }
    
    // deleted_at ์„ค์ • (์‹ค์ œ ์‚ญ์ œ ์•ˆ ํ•จ)
    await wdb
      .table("users")
      .where("id", id)
      .update({
        deleted_at: new Date(),
      });
  }
  
  // ์†Œํ”„ํŠธ ์‚ญ์ œ ๋ณต๊ตฌ
  @api({ httpMethod: "PUT" })
  @transactional()
  async restore(id: number): Promise<void> {
    const wdb = this.getPuri("w");
    
    const user = await wdb
      .table("users")
      .where("id", id)
      .whereNotNull("deleted_at")
      .first();
    
    if (!user) {
      throw new Error("User not found or not deleted");
    }
    
    await wdb
      .table("users")
      .where("id", id)
      .update({
        deleted_at: null,
      });
  }
}

๊ด€๋ จ ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์‚ญ์ œ

class UserModel extends BaseModelClass {
  @api({ httpMethod: "DELETE" })
  @transactional()
  async removeWithRelations(id: number): Promise<void> {
    const wdb = this.getPuri("w");
    
    const user = await wdb
      .table("users")
      .where("id", id)
      .first();
    
    if (!user) {
      throw new Error("User not found");
    }
    
    // 1. Profile ์‚ญ์ œ
    await wdb
      .table("profiles")
      .where("user_id", id)
      .delete();
    
    // 2. Posts ์‚ญ์ œ
    await wdb
      .table("posts")
      .where("user_id", id)
      .delete();
    
    // 3. User ์‚ญ์ œ
    await wdb
      .table("users")
      .where("id", id)
      .delete();
  }
}

๋ฐฐ์น˜ ์‚ญ์ œ

class UserModel extends BaseModelClass {
  @api({ httpMethod: "DELETE" })
  @transactional()
  async removeBatch(ids: number[]): Promise<{
    deletedCount: number;
  }> {
    const wdb = this.getPuri("w");
    
    const deletedCount = await wdb
      .table("users")
      .whereIn("id", ids)
      .delete();
    
    return { deletedCount };
  }
}

// ํ˜ธ์ถœ: DELETE /api/user/removeBatch
// Body: { "ids": [1, 2, 3] }
DELETE ๋ฉ”์„œ๋“œ ํŠน์ง•:
  • ๋น„์•ˆ์ „ํ•จ: ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•จ
  • ๋ฉฑ๋“ฑ์„ฑ: ์ด๋ฏธ ์‚ญ์ œ๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋‹ค์‹œ ์‚ญ์ œํ•ด๋„ ๊ฒฐ๊ณผ ๋™์ผ
  • ์„ฑ๊ณต ์‹œ ์ผ๋ฐ˜์ ์œผ๋กœ ๋นˆ ์‘๋‹ต (204 No Content)
  • ์†Œํ”„ํŠธ ์‚ญ์ œ ๊ณ ๋ ค (deleted_at ์ปฌ๋Ÿผ ์‚ฌ์šฉ)

๋ฉ”์„œ๋“œ ์„ ํƒ ๊ฐ€์ด๋“œ

์„ ํƒ ๊ธฐ์ค€

์ž‘์—…๋ฉ”์„œ๋“œ์˜ˆ์‹œ
๋‹จ์ผ ์กฐํšŒGET/api/user/get?id=1
๋ชฉ๋ก ์กฐํšŒGET/api/user/list?page=1
์ƒˆ๋กœ ์ƒ์„ฑPOST/api/user/create
์ „์ฒด ์ˆ˜์ •PUT/api/user/update?id=1
๋ถ€๋ถ„ ์ˆ˜์ •PUT/api/user/updatePartial?id=1
์‚ญ์ œDELETE/api/user/remove?id=1

RESTful ์›์น™

์ข‹์€ ์˜ˆ

class UserModel extends BaseModelClass {
  // โœ… GET: ์กฐํšŒ
  @api({ httpMethod: "GET" })
  async list(): Promise<User[]> { /* ... */ }
  
  // โœ… POST: ์ƒ์„ฑ
  @api({ httpMethod: "POST" })
  async create(params: CreateUserParams): Promise<{ userId: number }> { /* ... */ }
  
  // โœ… PUT: ์ˆ˜์ •
  @api({ httpMethod: "PUT" })
  async update(id: number, params: UpdateUserParams): Promise<void> { /* ... */ }
  
  // โœ… DELETE: ์‚ญ์ œ
  @api({ httpMethod: "DELETE" })
  async remove(id: number): Promise<void> { /* ... */ }
}

๋‚˜์œ ์˜ˆ

class UserModel extends BaseModelClass {
  // โŒ GET์œผ๋กœ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ
  @api({ httpMethod: "GET" })
  async deleteUser(id: number): Promise<void> {
    // GET์€ ์•ˆ์ „ํ•ด์•ผ ํ•จ (์ƒํƒœ ๋ณ€๊ฒฝ X)
  }
  
  // โŒ POST๋กœ ์กฐํšŒ
  @api({ httpMethod: "POST" })
  async getUser(id: number): Promise<User> {
    // POST๋Š” ์ƒ์„ฑ์šฉ
  }
  
  // โŒ DELETE๋กœ ์ˆ˜์ •
  @api({ httpMethod: "DELETE" })
  async updateUser(id: number, params: any): Promise<void> {
    // DELETE๋Š” ์‚ญ์ œ์šฉ
  }
}

๋‹ค์Œ ๋‹จ๊ณ„