메인 콘텐츠로 건너뛰기
Puri는 TypeScript로 안전하게 SQL 쿼리를 작성할 수 있는 타입 안전한 쿼리 빌더입니다. 이 문서는 SELECT, INSERT, UPDATE, DELETE의 기본 사용법을 설명합니다.

쿼리 시작하기

SELECT

데이터 조회하기select, selectAll, first

INSERT

데이터 추가하기insert, upsert, returning

UPDATE

데이터 수정하기update, increment, decrement

DELETE

데이터 삭제하기delete, truncate

SELECT - 데이터 조회

기본 SELECT

const users = await db.table("users").select({
  id: "id",
  name: "username",
  email: "email",
});
// 결과: { id: number; name: string; email: string; }[]
select 메서드는 객체 형태로 컬럼을 선택합니다. 키는 결과 필드명, 값은 실제 테이블 컬럼명입니다.

컬럼 별칭(Alias)

const posts = await db.table("posts").select({
  postId: "id",              // 별칭 사용
  postTitle: "title",
  authorName: "author_name", // snake_case → camelCase
  createdDate: "created_at",
});

// 결과 타입이 자동으로 추론됨
const firstPost = posts[0];
console.log(firstPost.postId);      // number
console.log(firstPost.postTitle);   // string

appendSelect - 컬럼 추가

이미 선택한 컬럼에 추가로 컬럼을 선택할 수 있습니다.
const query = db.table("users").select({
  id: "id",
  name: "username",
});

const users = await query.appendSelect({
  email: "email",
  role: "role",
});

// 결과: { id, name, email, role }

WHERE - 조건 필터링

기본 WHERE

const users = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .where("role", "admin");

복수 조건 (AND)

const users = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .where("role", "admin")
  .where("is_active", true)
  .where("age", ">=", 18);

// SQL: WHERE role = 'admin' AND is_active = true AND age >= 18

OR 조건

const users = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .where("role", "admin")
  .orWhere("role", "moderator");

// SQL: WHERE role = 'admin' OR role = 'moderator'
orWhere는 단순 OR 조건입니다. 복잡한 조건 그룹은 whereGroup을 사용하세요. 자세한 내용은 Advanced Patterns 참고

IN / NOT IN

const users = await db
  .table("users")
  .select({ id: "id" })
  .whereIn("role", ["admin", "moderator"]);

LIKE 검색

const users = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .where("username", "like", "%john%");

// SQL: WHERE username LIKE '%john%'

INSERT - 데이터 추가

단일 레코드 추가

const result = await db.table("users").insert({
  username: "john",
  email: "[email protected]",
  password: "hashed_password",
  role: "normal",
});

// result: number (삽입된 레코드 수)

RETURNING으로 삽입된 데이터 받기

const inserted = await db
  .table("users")
  .insert({
    username: "john",
    email: "[email protected]",
    password: "hashed_password",
    role: "normal",
  })
  .returning({ id: "id", name: "username" });

console.log(inserted);
// [{ id: 1, name: "john" }]

여러 레코드 추가

const result = await db.table("users").insert([
  {
    username: "john",
    email: "[email protected]",
    password: "hash1",
    role: "normal",
  },
  {
    username: "jane",
    email: "[email protected]",
    password: "hash2",
    role: "normal",
  },
]);

UPDATE - 데이터 수정

기본 UPDATE

const count = await db
  .table("users")
  .where("id", 1)
  .update({
    username: "updated_name",
    updated_at: new Date(),
  });

console.log(`${count} rows updated`);
WHERE 절 필수: UPDATE는 반드시 WHERE 조건과 함께 사용해야 합니다. 조건 없이 전체 데이터를 수정하면 에러가 발생할 수 있습니다.

increment / decrement

숫자 컬럼을 증가/감소시킬 수 있습니다.
await db
  .table("posts")
  .where("id", 1)
  .increment("view_count", 1);

// SQL: UPDATE posts SET view_count = view_count + 1 WHERE id = 1

여러 컬럼 동시 수정

await db
  .table("users")
  .where("id", 1)
  .update({
    username: "new_name",
    email: "[email protected]",
    updated_at: new Date(),
  });

DELETE - 데이터 삭제

기본 DELETE

const count = await db
  .table("users")
  .where("id", 1)
  .delete();

console.log(`${count} rows deleted`);
WHERE 절 필수: DELETE도 반드시 WHERE 조건과 함께 사용해야 합니다.

여러 레코드 삭제

const count = await db
  .table("users")
  .whereIn("status", ["deleted", "banned"])
  .delete();

LIMIT & OFFSET - 페이지네이션

LIMIT - 결과 개수 제한

const users = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .limit(10);

// 최대 10개만 조회

OFFSET - 건너뛰기

const users = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .limit(10)
  .offset(20);

// 20개를 건너뛰고 다음 10개 조회 (21~30번째)

페이지네이션 예제

function getUsers(page: number, pageSize: number) {
  return db
    .table("users")
    .select({ id: "id", name: "username" })
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

// 1페이지 (1~10)
await getUsers(1, 10);

// 2페이지 (11~20)
await getUsers(2, 10);

ORDER BY - 정렬

기본 정렬

const users = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .orderBy("created_at", "asc");

여러 컬럼 정렬

const users = await db
  .table("users")
  .select({ id: "id", name: "username", age: "age" })
  .orderBy("age", "desc")      // 1순위: 나이 내림차순
  .orderBy("created_at", "asc"); // 2순위: 생성일 오름차순

first() - 단일 결과 조회

first()는 첫 번째 결과만 반환합니다.
const user = await db
  .table("users")
  .select({ id: "id", name: "username" })
  .where("email", "[email protected]")
  .first();

if (user) {
  console.log(user.name); // string
} else {
  console.log("User not found");
}

// 타입: { id: number; name: string; } | undefined
first()는 결과가 없으면 undefined를 반환합니다. 반드시 존재 여부를 체크하세요.

pluck() - 단일 컬럼 추출

특정 컬럼의 값들만 배열로 가져옵니다.
const userIds = await db
  .table("users")
  .where("role", "admin")
  .pluck("id");

// [1, 2, 3, 4, 5]
// 타입: number[]

count() - 개수 세기

레코드 개수를 빠르게 조회합니다.
const count = await db
  .table("users")
  .where("role", "admin")
  .count();

console.log(`Total admins: ${count}`);
// 타입: number
count()는 집계 함수가 아닌 간단한 개수 조회 메서드입니다. 복잡한 집계는 Aggregations 참고

실전 예제

사용자 목록 조회 API

async findUsers(params: {
  role?: string;
  search?: string;
  page: number;
  pageSize: number;
}) {
  const { role, search, page, pageSize } = params;
  
  let query = this.getPuri("r")
    .table("users")
    .select({
      id: "id",
      username: "username",
      email: "email",
      role: "role",
      createdAt: "created_at",
    });
  
  // 조건 추가
  if (role) {
    query = query.where("role", role);
  }
  
  if (search) {
    query = query.where("username", "like", `%${search}%`);
  }
  
  // 페이지네이션
  const users = await query
    .orderBy("created_at", "desc")
    .limit(pageSize)
    .offset((page - 1) * pageSize);
  
  // 전체 개수
  const total = await this.getPuri("r")
    .table("users")
    .where("role", role)
    .count();
  
  return { users, total };
}

게시글 작성 API

async createPost(data: {
  title: string;
  content: string;
  userId: number;
}) {
  const inserted = await this.getPuri("w")
    .table("posts")
    .insert({
      title: data.title,
      content: data.content,
      user_id: data.userId,
      status: "draft",
      created_at: new Date(),
    })
    .returning({
      id: "id",
      title: "title",
      createdAt: "created_at",
    });
  
  return inserted[0];
}

조회수 증가

async incrementViewCount(postId: number) {
  await this.getPuri("w")
    .table("posts")
    .where("id", postId)
    .increment("view_count", 1);
}

쿼리 디버깅

debug() - SQL 출력

const users = await db
  .table("users")
  .select({ id: "id" })
  .where("role", "admin")
  .debug(); // 콘솔에 SQL 출력

// 출력:
// SELECT "users"."id" AS `id` FROM "users" WHERE "role" = 'admin'

rawQuery() - Knex QueryBuilder 얻기

내부 Knex 쿼리 빌더에 접근할 수 있습니다.
const knexQuery = db
  .table("users")
  .select({ id: "id" })
  .rawQuery();

console.log(knexQuery.toQuery());

다음 단계