ํ๋ผ๋ฏธํฐ ๊ฐ์
ํ์ ์์ ์ฑ
์ปดํ์ผ ํ์ ๊ฒ์ฆ์๋์์ฑ ์ง์
๋ช ํํ ๊ณ์ฝ
API ๋ช
์ธ ์๋ ์์ฑํด๋ผ์ด์ธํธ ๊ฐ์ด๋
๊ฒ์ฆ
์๋ ํ์
๊ฒ์ฆ์๋ฌ ์กฐ๊ธฐ ๋ฐ๊ฒฌ
๋ฌธ์ํ
์ฃผ์์ผ๋ก ์ค๋ช
์ถ๊ฐ์ ์ง๋ณด์ ์ฉ์ด
๊ธฐ๋ณธ ํ์
์์ ํ์
๋ณต์ฌ
class UserModel extends BaseModelClass {
// ์ซ์
@api({ httpMethod: "GET" })
async getUser(id: number): Promise<User> {
// GET /api/user/getUser?id=1
// ...
}
// ๋ฌธ์์ด
@api({ httpMethod: "GET" })
async findByEmail(email: string): Promise<User | null> {
// GET /api/user/[email protected]
// ...
}
// ๋ถ๋ฆฌ์ธ
@api({ httpMethod: "GET" })
async listActive(active: boolean): Promise<User[]> {
// GET /api/user/listActive?active=true
// ...
}
// ๋ ์ง
@api({ httpMethod: "GET" })
async listByDate(fromDate: Date, toDate: Date): Promise<User[]> {
// GET /api/user/listByDate?fromDate=2025-01-01&toDate=2025-12-31
// ...
}
}
๋ฐฐ์ด ํ์
๋ณต์ฌ
class UserModel extends BaseModelClass {
// ์ซ์ ๋ฐฐ์ด
@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("*");
}
// ๋ฌธ์์ด ๋ฐฐ์ด
@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 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");
// params.email, params.username ๋ฑ ์๋์์ฑ ๊ฐ๋ฅ
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": "[email protected]",
// "username": "testuser",
// "password": "hashedpass",
// "role": "normal"
// }
์ต์ ๋ ํ๋
๋ณต์ฌ
interface UserListParams {
// ํ์ ํ๋ผ๋ฏธํฐ
page: number;
pageSize: number;
// ์ต์
๋ ํ๋ผ๋ฏธํฐ
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");
// ์ต์
๋ ํ๋ผ๋ฏธํฐ ์ฒ๋ฆฌ
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);
}
// ์ ๋ ฌ (๊ธฐ๋ณธ๊ฐ ์ ๊ณต)
const sortBy = params.sortBy || "created_at";
const sortOrder = params.sortOrder || "desc";
query = query.orderBy(sortBy, sortOrder);
// ํ์ด์ง๋ค์ด์
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 };
}
}
์ค์ฒฉ ๊ฐ์ฒด
๋ณต์ฌ
interface RegisterParams {
// User ์ ๋ณด
email: string;
username: string;
password: string;
// Profile ์ ๋ณด (์ค์ฒฉ)
profile: {
bio: string;
avatarUrl?: string;
socialLinks?: {
twitter?: string;
github?: string;
linkedin?: string;
};
};
// ์ค์ (์ค์ฒฉ)
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");
// 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,
social_links: JSON.stringify(params.profile.socialLinks || {}),
});
// 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 };
}
}
์ ๋์จ ํ์ ๊ณผ Enum
Enum ์ฌ์ฉ
๋ณต์ฌ
// Enum ์ ์
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 ๊ฐ ๊ฒ์ฆ ์๋
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);
}
}
๋ฆฌํฐ๋ด ์ ๋์จ ํ์
๋ณต์ฌ
interface SearchParams {
query: string;
// ๋ฆฌํฐ๋ด ์ ๋์จ ํ์
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");
// 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("*");
}
}
์ ๋ค๋ฆญ ํ์
๊ณตํต ๋ฆฌ์คํธ ํ๋ผ๋ฏธํฐ
๋ณต์ฌ
// ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ ๋ค๋ฆญ ์ธํฐํ์ด์ค
interface ListParams<T> {
page: number;
pageSize: number;
sortBy?: keyof T;
sortOrder?: "asc" | "desc";
}
// User์ฉ ํ์ฅ
interface UserListParams extends ListParams<User> {
search?: string;
role?: UserRole;
}
// 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๋ User์ ํค๋ง ๊ฐ๋ฅ (ํ์
์์ )
// ...
}
}
๊ฒ์ฆ ๋ก์ง
์ปค์คํ ๊ฒ์ฆ
๋ณต์ฌ
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 }> {
// ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ
this.validateCreateParams(params);
const wdb = this.getPuri("w");
// ...
}
private validateCreateParams(params: CreateUserParams): void {
// ์ด๋ฉ์ผ ํ์ ๊ฒ์ฆ
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(params.email)) {
throw new Error("Invalid email format");
}
// ์ฌ์ฉ์๋ช
๊ธธ์ด ๊ฒ์ฆ
if (params.username.length < 3 || params.username.length > 20) {
throw new Error("Username must be between 3 and 20 characters");
}
// ๋น๋ฐ๋ฒํธ ๊ฐ๋ ๊ฒ์ฆ
if (params.password.length < 8) {
throw new Error("Password must be at least 8 characters");
}
// ๋์ด ๊ฒ์ฆ
if (params.age !== undefined && (params.age < 18 || params.age > 120)) {
throw new Error("Age must be between 18 and 120");
}
}
}
Zod๋ฅผ ์ฌ์ฉํ ๊ฒ์ฆ
๋ณต์ฌ
import { z } from "zod";
// Zod ์คํค๋ง ์ ์
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 }> {
// Zod๋ก ๊ฒ์ฆ
const validated = CreateUserSchema.parse(params);
const wdb = this.getPuri("w");
// validated๋ ํ์
์ด ๋ณด์ฅ๋จ
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 };
}
}
ํ๋ผ๋ฏธํฐ ๋ฌธ์ํ
JSDoc ์ฃผ์
๋ณต์ฌ
/**
* ์ฌ์ฉ์ ๋ชฉ๋ก์ ์กฐํํฉ๋๋ค.
*/
interface UserListParams {
/**
* ํ์ด์ง ๋ฒํธ (1๋ถํฐ ์์)
* @example 1
*/
page: number;
/**
* ํ์ด์ง๋น ํญ๋ชฉ ์
* @example 20
* @min 1
* @max 100
*/
pageSize: number;
/**
* ๊ฒ์ ํค์๋ (username, email์์ ๊ฒ์)
* @example "john"
*/
search?: string;
/**
* ์ฌ์ฉ์ ์ญํ ํํฐ
* @example "admin"
*/
role?: "admin" | "manager" | "normal";
/**
* ์ ๋ ฌ ๊ธฐ์ค ํ๋
* @default "created_at"
*/
sortBy?: "created_at" | "username" | "email";
/**
* ์ ๋ ฌ ์์
* @default "desc"
*/
sortOrder?: "asc" | "desc";
}
์ค์ ํจํด
ํ์ด์ง๋ค์ด์ ํ๋ผ๋ฏธํฐ
๋ณต์ฌ
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");
// ๊ธฐ๋ณธ๊ฐ ์ฒ๋ฆฌ
const page = Math.max(1, params.page);
const pageSize = Math.min(100, Math.max(1, params.pageSize));
// ...
}
}
ํํฐ ํ๋ผ๋ฏธํฐ
๋ณต์ฌ
interface ProductFilterParams {
// ์นดํ
๊ณ ๋ฆฌ
category?: string;
// ๊ฐ๊ฒฉ ๋ฒ์
minPrice?: number;
maxPrice?: number;
// ์ฌ๊ณ ์ํ
inStock?: boolean;
// ๋ธ๋๋
brands?: string[];
// ํ๊ทธ
tags?: string[];
// ๊ฒ์
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);
}
// ...
}
}